| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * Loads a thumbnail using provided url. In CANVAS mode, loaded images | 6 * Loads a thumbnail using provided url. In CANVAS mode, loaded images |
| 7 * are attached as <canvas> element, while in IMAGE mode as <img>. | 7 * are attached as <canvas> element, while in IMAGE mode as <img>. |
| 8 * <canvas> renders faster than <img>, however has bigger memory overhead. | 8 * <canvas> renders faster than <img>, however has bigger memory overhead. |
| 9 * | 9 * |
| 10 * @param {string} url File URL. | 10 * @param {string} url File URL. |
| 11 * @param {ThumbnailLoader.LoaderType=} opt_loaderType Canvas or Image loader, | 11 * @param {ThumbnailLoader.LoaderType=} opt_loaderType Canvas or Image loader, |
| 12 * default: IMAGE. | 12 * default: IMAGE. |
| 13 * @param {Object=} opt_metadata Metadata object. | 13 * @param {Object=} opt_metadata Metadata object. |
| 14 * @param {string=} opt_mediaType Media type. | 14 * @param {string=} opt_mediaType Media type. |
| 15 * @param {ThumbnailLoader.UseEmbedded=} opt_useEmbedded If to use embedded |
| 16 * jpeg thumbnail if available. Default: USE_EMBEDDED. |
| 15 * @constructor | 17 * @constructor |
| 16 */ | 18 */ |
| 17 function ThumbnailLoader(url, opt_loaderType, opt_metadata, opt_mediaType) { | 19 function ThumbnailLoader( |
| 20 url, opt_loaderType, opt_metadata, opt_mediaType, opt_useEmbedded) { |
| 21 opt_useEmbedded = opt_useEmbedded || ThumbnailLoader.UseEmbedded.USE_EMBEDDED; |
| 22 |
| 18 this.mediaType_ = opt_mediaType || FileType.getMediaType(url); | 23 this.mediaType_ = opt_mediaType || FileType.getMediaType(url); |
| 19 this.loaderType_ = opt_loaderType || ThumbnailLoader.LoaderType.IMAGE; | 24 this.loaderType_ = opt_loaderType || ThumbnailLoader.LoaderType.IMAGE; |
| 20 this.metadata_ = opt_metadata; | 25 this.metadata_ = opt_metadata; |
| 21 | 26 |
| 22 if (!opt_metadata) { | 27 if (!opt_metadata) { |
| 23 this.thumbnailUrl_ = url; // Use the URL directly. | 28 this.thumbnailUrl_ = url; // Use the URL directly. |
| 24 return; | 29 return; |
| 25 } | 30 } |
| 26 | 31 |
| 27 this.fallbackUrl_ = null; | 32 this.fallbackUrl_ = null; |
| 28 this.thumbnailUrl_ = null; | 33 this.thumbnailUrl_ = null; |
| 29 if (opt_metadata.drive) { | 34 if (opt_metadata.drive) { |
| 30 var apps = opt_metadata.drive.driveApps; | 35 var apps = opt_metadata.drive.driveApps; |
| 31 for (var i = 0; i < apps.length; ++i) { | 36 for (var i = 0; i < apps.length; ++i) { |
| 32 if (apps[i].docIcon && apps[i].isPrimary) { | 37 if (apps[i].docIcon && apps[i].isPrimary) { |
| 33 this.fallbackUrl_ = apps[i].docIcon; | 38 this.fallbackUrl_ = apps[i].docIcon; |
| 34 break; | 39 break; |
| 35 } | 40 } |
| 36 } | 41 } |
| 37 } | 42 } |
| 38 | 43 |
| 39 if (opt_metadata.thumbnail && opt_metadata.thumbnail.url) { | 44 if (opt_metadata.thumbnail && opt_metadata.thumbnail.url && |
| 45 opt_useEmbedded == ThumbnailLoader.UseEmbedded.USE_EMBEDDED) { |
| 40 this.thumbnailUrl_ = opt_metadata.thumbnail.url; | 46 this.thumbnailUrl_ = opt_metadata.thumbnail.url; |
| 41 this.transform_ = opt_metadata.thumbnail.transform; | 47 this.transform_ = opt_metadata.thumbnail.transform; |
| 42 } else if (FileType.isImage(url)) { | 48 } else if (FileType.isImage(url)) { |
| 43 this.thumbnailUrl_ = url; | 49 this.thumbnailUrl_ = url; |
| 44 this.transform_ = opt_metadata.media && opt_metadata.media.imageTransform; | 50 this.transform_ = opt_metadata.media && opt_metadata.media.imageTransform; |
| 45 } else if (this.fallbackUrl_) { | 51 } else if (this.fallbackUrl_) { |
| 46 // Use fallback as the primary thumbnail. | 52 // Use fallback as the primary thumbnail. |
| 47 this.thumbnailUrl_ = this.fallbackUrl_; | 53 this.thumbnailUrl_ = this.fallbackUrl_; |
| 48 this.fallbackUrl_ = null; | 54 this.fallbackUrl_ = null; |
| 49 } // else the generic thumbnail based on the media type will be used. | 55 } // else the generic thumbnail based on the media type will be used. |
| 50 } | 56 } |
| 51 | 57 |
| 52 /** | 58 /** |
| 53 * In percents (0.0 - 1.0), how much area can be cropped to fill an image | 59 * In percents (0.0 - 1.0), how much area can be cropped to fill an image |
| 54 * in a container, when loading a thumbnail in FillMode.AUTO mode. | 60 * in a container, when loading a thumbnail in FillMode.AUTO mode. |
| 55 * The specified 30% value allows to fill 16:9, 3:2 pictures in 4:3 element. | 61 * The specified 30% value allows to fill 16:9, 3:2 pictures in 4:3 element. |
| 56 * @type {number} | 62 * @type {number} |
| 57 */ | 63 */ |
| 58 ThumbnailLoader.AUTO_FILL_THRESHOLD = 0.3; | 64 ThumbnailLoader.AUTO_FILL_THRESHOLD = 0.3; |
| 59 | 65 |
| 60 /** | 66 /** |
| 61 * Type of displaying a thumbnail within a box. | 67 * Type of displaying a thumbnail within a box. |
| 62 * @enum | 68 * @enum {number} |
| 63 */ | 69 */ |
| 64 ThumbnailLoader.FillMode = { | 70 ThumbnailLoader.FillMode = { |
| 65 FILL: 0, // Fill whole box. Image may be cropped. | 71 FILL: 0, // Fill whole box. Image may be cropped. |
| 66 FIT: 1, // Keep aspect ratio, do not crop. | 72 FIT: 1, // Keep aspect ratio, do not crop. |
| 67 AUTO: 2 // Try to fill, but if incompatible aspect ratio, then fit. | 73 AUTO: 2 // Try to fill, but if incompatible aspect ratio, then fit. |
| 68 }; | 74 }; |
| 69 | 75 |
| 70 /** | 76 /** |
| 71 * Optimization mode for downloading thumbnails. | 77 * Optimization mode for downloading thumbnails. |
| 72 * @enum | 78 * @enum {number} |
| 73 */ | 79 */ |
| 74 ThumbnailLoader.OptimizationMode = { | 80 ThumbnailLoader.OptimizationMode = { |
| 75 NEVER_DISCARD: 0, // Never discards downloading. No optimization. | 81 NEVER_DISCARD: 0, // Never discards downloading. No optimization. |
| 76 DISCARD_DETACHED: 1 // Canceled if the container is not attached anymore. | 82 DISCARD_DETACHED: 1 // Canceled if the container is not attached anymore. |
| 77 }; | 83 }; |
| 78 | 84 |
| 79 /** | 85 /** |
| 80 * Type of element to store the image. | 86 * Type of element to store the image. |
| 81 * @enum | 87 * @enum {number} |
| 82 */ | 88 */ |
| 83 ThumbnailLoader.LoaderType = { | 89 ThumbnailLoader.LoaderType = { |
| 84 IMAGE: 0, | 90 IMAGE: 0, |
| 85 CANVAS: 1 | 91 CANVAS: 1 |
| 86 }; | 92 }; |
| 87 | 93 |
| 88 /** | 94 /** |
| 95 * Whether to use the embedded thumbnail, or not. The embedded thumbnail may |
| 96 * be small. |
| 97 * @enum {number} |
| 98 */ |
| 99 ThumbnailLoader.UseEmbedded = { |
| 100 USE_EMBEDDED: 0, |
| 101 NO_EMBEDDED: 1 |
| 102 }; |
| 103 |
| 104 /** |
| 89 * Maximum thumbnail's width when generating from the full resolution image. | 105 * Maximum thumbnail's width when generating from the full resolution image. |
| 90 * @const | 106 * @const |
| 91 * @type {number} | 107 * @type {number} |
| 92 */ | 108 */ |
| 93 ThumbnailLoader.THUMBNAIL_MAX_WIDTH = 500; | 109 ThumbnailLoader.THUMBNAIL_MAX_WIDTH = 500; |
| 94 | 110 |
| 95 /** | 111 /** |
| 96 * Maximum thumbnail's height when generating from the full resolution image. | 112 * Maximum thumbnail's height when generating from the full resolution image. |
| 97 * @const | 113 * @const |
| 98 * @type {number} | 114 * @type {number} |
| (...skipping 17 matching lines...) Expand all Loading... |
| 116 opt_optimizationMode = opt_optimizationMode || | 132 opt_optimizationMode = opt_optimizationMode || |
| 117 ThumbnailLoader.OptimizationMode.NEVER_DISCARD; | 133 ThumbnailLoader.OptimizationMode.NEVER_DISCARD; |
| 118 | 134 |
| 119 if (!this.thumbnailUrl_) { | 135 if (!this.thumbnailUrl_) { |
| 120 // Relevant CSS rules are in file_types.css. | 136 // Relevant CSS rules are in file_types.css. |
| 121 box.setAttribute('generic-thumbnail', this.mediaType_); | 137 box.setAttribute('generic-thumbnail', this.mediaType_); |
| 122 if (opt_onGeneric) opt_onGeneric(); | 138 if (opt_onGeneric) opt_onGeneric(); |
| 123 return; | 139 return; |
| 124 } | 140 } |
| 125 | 141 |
| 142 this.cancel(); |
| 126 this.canvasUpToDate_ = false; | 143 this.canvasUpToDate_ = false; |
| 127 this.image_ = new Image(); | 144 this.image_ = new Image(); |
| 128 this.image_.onload = function() { | 145 this.image_.onload = function() { |
| 129 this.attachImage(box, fillMode); | 146 this.attachImage(box, fillMode); |
| 130 if (opt_onSuccess) | 147 if (opt_onSuccess) |
| 131 opt_onSuccess(this.image_, this.transform_); | 148 opt_onSuccess(this.image_, this.transform_); |
| 132 }.bind(this); | 149 }.bind(this); |
| 133 this.image_.onerror = function() { | 150 this.image_.onerror = function() { |
| 134 if (opt_onError) | 151 if (opt_onError) |
| 135 opt_onError(); | 152 opt_onError(); |
| (...skipping 12 matching lines...) Expand all Loading... |
| 148 console.warn('Thumbnail already loaded: ' + this.thumbnailUrl_); | 165 console.warn('Thumbnail already loaded: ' + this.thumbnailUrl_); |
| 149 return; | 166 return; |
| 150 } | 167 } |
| 151 | 168 |
| 152 // TODO(mtomasz): Smarter calculation of the requested size. | 169 // TODO(mtomasz): Smarter calculation of the requested size. |
| 153 var wasAttached = box.ownerDocument.contains(box); | 170 var wasAttached = box.ownerDocument.contains(box); |
| 154 var modificationTime = this.metadata_ && | 171 var modificationTime = this.metadata_ && |
| 155 this.metadata_.filesystem && | 172 this.metadata_.filesystem && |
| 156 this.metadata_.filesystem.modificationTime && | 173 this.metadata_.filesystem.modificationTime && |
| 157 this.metadata_.filesystem.modificationTime.getTime(); | 174 this.metadata_.filesystem.modificationTime.getTime(); |
| 158 var taskId = util.loadImage( | 175 this.taskId_ = util.loadImage( |
| 159 this.image_, | 176 this.image_, |
| 160 this.thumbnailUrl_, | 177 this.thumbnailUrl_, |
| 161 { maxWidth: ThumbnailLoader.THUMBNAIL_MAX_WIDTH, | 178 { maxWidth: ThumbnailLoader.THUMBNAIL_MAX_WIDTH, |
| 162 maxHeight: ThumbnailLoader.THUMBNAIL_MAX_HEIGHT, | 179 maxHeight: ThumbnailLoader.THUMBNAIL_MAX_HEIGHT, |
| 163 cache: true, | 180 cache: true, |
| 164 timestamp: modificationTime }, | 181 timestamp: modificationTime }, |
| 165 function() { | 182 function() { |
| 166 if (opt_optimizationMode == | 183 if (opt_optimizationMode == |
| 167 ThumbnailLoader.OptimizationMode.DISCARD_DETACHED && | 184 ThumbnailLoader.OptimizationMode.DISCARD_DETACHED && |
| 168 !box.ownerDocument.contains(box)) { | 185 !box.ownerDocument.contains(box)) { |
| 169 // If the container is not attached, then invalidate the download. | 186 // If the container is not attached, then invalidate the download. |
| 170 return false; | 187 return false; |
| 171 } | 188 } |
| 172 return true; | 189 return true; |
| 173 }); | 190 }); |
| 174 | 191 |
| 175 if (!taskId) | 192 if (!this.taskId_) |
| 176 this.image_.classList.add('cached'); | 193 this.image_.classList.add('cached'); |
| 177 }; | 194 }; |
| 178 | 195 |
| 179 /** | 196 /** |
| 197 * Cancels loading the current image. |
| 198 */ |
| 199 ThumbnailLoader.prototype.cancel = function() { |
| 200 if (this.taskId_) { |
| 201 this.image_.onload = function() {}; |
| 202 this.image_.onerror = function() {}; |
| 203 util.cancelLoadImage(this.taskId_); |
| 204 } |
| 205 }; |
| 206 |
| 207 /** |
| 180 * @return {boolean} True if a valid image is loaded. | 208 * @return {boolean} True if a valid image is loaded. |
| 181 */ | 209 */ |
| 182 ThumbnailLoader.prototype.hasValidImage = function() { | 210 ThumbnailLoader.prototype.hasValidImage = function() { |
| 183 return !!(this.image_ && this.image_.width && this.image_.height); | 211 return !!(this.image_ && this.image_.width && this.image_.height); |
| 184 }; | 212 }; |
| 185 | 213 |
| 186 /** | 214 /** |
| 187 * @return {boolean} True if the image is rotated 90 degrees left or right. | 215 * @return {boolean} True if the image is rotated 90 degrees left or right. |
| 188 * @private | 216 * @private |
| 189 */ | 217 */ |
| (...skipping 20 matching lines...) Expand all Loading... |
| 210 * | 238 * |
| 211 * @param {function(boolean)} callback Callback, parameter is true if the image | 239 * @param {function(boolean)} callback Callback, parameter is true if the image |
| 212 * has loaded successfully or a stock icon has been used. | 240 * has loaded successfully or a stock icon has been used. |
| 213 */ | 241 */ |
| 214 ThumbnailLoader.prototype.loadDetachedImage = function(callback) { | 242 ThumbnailLoader.prototype.loadDetachedImage = function(callback) { |
| 215 if (!this.thumbnailUrl_) { | 243 if (!this.thumbnailUrl_) { |
| 216 callback(true); | 244 callback(true); |
| 217 return; | 245 return; |
| 218 } | 246 } |
| 219 | 247 |
| 248 this.cancel(); |
| 220 this.canvasUpToDate_ = false; | 249 this.canvasUpToDate_ = false; |
| 221 this.image_ = new Image(); | 250 this.image_ = new Image(); |
| 222 this.image_.onload = callback.bind(null, true); | 251 this.image_.onload = callback.bind(null, true); |
| 223 this.image_.onerror = callback.bind(null, false); | 252 this.image_.onerror = callback.bind(null, false); |
| 224 | 253 |
| 225 // TODO(mtomasz): Smarter calculation of the requested size. | 254 // TODO(mtomasz): Smarter calculation of the requested size. |
| 226 var modificationTime = this.metadata_ && | 255 var modificationTime = this.metadata_ && |
| 227 this.metadata_.filesystem && | 256 this.metadata_.filesystem && |
| 228 this.metadata_.filesystem.modificationTime && | 257 this.metadata_.filesystem.modificationTime && |
| 229 this.metadata_.filesystem.modificationTime.getTime(); | 258 this.metadata_.filesystem.modificationTime.getTime(); |
| 230 var taskId = util.loadImage( | 259 this.taskId_ = util.loadImage( |
| 231 this.image_, | 260 this.image_, |
| 232 this.thumbnailUrl_, | 261 this.thumbnailUrl_, |
| 233 { maxWidth: ThumbnailLoader.THUMBNAIL_MAX_WIDTH, | 262 { maxWidth: ThumbnailLoader.THUMBNAIL_MAX_WIDTH, |
| 234 maxHeight: ThumbnailLoader.THUMBNAIL_MAX_HEIGHT, | 263 maxHeight: ThumbnailLoader.THUMBNAIL_MAX_HEIGHT, |
| 235 cache: true, | 264 cache: true, |
| 236 timestamp: modificationTime }); | 265 timestamp: modificationTime }); |
| 237 | 266 |
| 238 if (!taskId) | 267 if (!this.taskId_) |
| 239 this.image_.classList.add('cached'); | 268 this.image_.classList.add('cached'); |
| 240 }; | 269 }; |
| 241 | 270 |
| 242 /** | 271 /** |
| 243 * Attach the image to a given element. | 272 * Attach the image to a given element. |
| 244 * @param {Element} container Parent element. | 273 * @param {Element} container Parent element. |
| 245 * @param {ThumbnailLoader.FillMode} fillMode Fill mode. | 274 * @param {ThumbnailLoader.FillMode} fillMode Fill mode. |
| 246 */ | 275 */ |
| 247 ThumbnailLoader.prototype.attachImage = function(container, fillMode) { | 276 ThumbnailLoader.prototype.attachImage = function(container, fillMode) { |
| 248 if (!this.hasValidImage()) { | 277 if (!this.hasValidImage()) { |
| (...skipping 18 matching lines...) Expand all Loading... |
| 267 // Canvas will be attached. | 296 // Canvas will be attached. |
| 268 attachableMedia = this.canvas_; | 297 attachableMedia = this.canvas_; |
| 269 } else { | 298 } else { |
| 270 // Image will be attached. | 299 // Image will be attached. |
| 271 attachableMedia = this.image_; | 300 attachableMedia = this.image_; |
| 272 } | 301 } |
| 273 | 302 |
| 274 util.applyTransform(container, this.transform_); | 303 util.applyTransform(container, this.transform_); |
| 275 ThumbnailLoader.centerImage_( | 304 ThumbnailLoader.centerImage_( |
| 276 container, attachableMedia, fillMode, this.isRotated_()); | 305 container, attachableMedia, fillMode, this.isRotated_()); |
| 277 if (this.image_.parentNode != container) { | 306 if (attachableMedia.parentNode != container) { |
| 278 container.textContent = ''; | 307 container.textContent = ''; |
| 279 container.appendChild(attachableMedia); | 308 container.appendChild(attachableMedia); |
| 280 } | 309 } |
| 281 }; | 310 }; |
| 282 | 311 |
| 283 /** | 312 /** |
| 284 * Update the image style to fit/fill the container. | 313 * Update the image style to fit/fill the container. |
| 285 * | 314 * |
| 286 * Using webkit center packing does not align the image properly, so we need | 315 * Using webkit center packing does not align the image properly, so we need |
| 287 * to wait until the image loads and its dimensions are known, then manually | 316 * to wait until the image loads and its dimensions are known, then manually |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 351 | 380 |
| 352 function percent(fraction) { | 381 function percent(fraction) { |
| 353 return (fraction * 100).toFixed(2) + '%'; | 382 return (fraction * 100).toFixed(2) + '%'; |
| 354 } | 383 } |
| 355 | 384 |
| 356 img.style.width = percent(fractionX); | 385 img.style.width = percent(fractionX); |
| 357 img.style.height = percent(fractionY); | 386 img.style.height = percent(fractionY); |
| 358 img.style.left = percent((1 - fractionX) / 2); | 387 img.style.left = percent((1 - fractionX) / 2); |
| 359 img.style.top = percent((1 - fractionY) / 2); | 388 img.style.top = percent((1 - fractionY) / 2); |
| 360 }; | 389 }; |
| OLD | NEW |