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. |
(...skipping 20 matching lines...) Expand all Loading... |
31 if (apps[i].docIcon && apps[i].isPrimary) { | 31 if (apps[i].docIcon && apps[i].isPrimary) { |
32 this.fallbackUrl_ = apps[i].docIcon; | 32 this.fallbackUrl_ = apps[i].docIcon; |
33 break; | 33 break; |
34 } | 34 } |
35 } | 35 } |
36 } | 36 } |
37 | 37 |
38 if (opt_metadata.thumbnail && opt_metadata.thumbnail.url) { | 38 if (opt_metadata.thumbnail && opt_metadata.thumbnail.url) { |
39 this.thumbnailUrl_ = opt_metadata.thumbnail.url; | 39 this.thumbnailUrl_ = opt_metadata.thumbnail.url; |
40 this.transform_ = opt_metadata.thumbnail.transform; | 40 this.transform_ = opt_metadata.thumbnail.transform; |
41 } else if (FileType.isImage(url) && | 41 } else if (FileType.isImage(url)) { |
42 ThumbnailLoader.canUseImageUrl_(opt_metadata)) { | |
43 this.thumbnailUrl_ = url; | 42 this.thumbnailUrl_ = url; |
44 this.transform_ = opt_metadata.media && opt_metadata.media.imageTransform; | 43 this.transform_ = opt_metadata.media && opt_metadata.media.imageTransform; |
45 } else if (this.fallbackUrl_) { | 44 } else if (this.fallbackUrl_) { |
46 // Use fallback as the primary thumbnail. | 45 // Use fallback as the primary thumbnail. |
47 this.thumbnailUrl_ = this.fallbackUrl_; | 46 this.thumbnailUrl_ = this.fallbackUrl_; |
48 this.fallbackUrl_ = null; | 47 this.fallbackUrl_ = null; |
49 } // else the generic thumbnail based on the media type will be used. | 48 } // else the generic thumbnail based on the media type will be used. |
50 } | 49 } |
51 | 50 |
52 /** | 51 /** |
53 * Files with more pixels won't have thumbnails. | |
54 */ | |
55 ThumbnailLoader.MAX_PIXEL_COUNT = 1 << 21; // 2 MPix | |
56 | |
57 /** | |
58 * Files of bigger size won't have thumbnails. | |
59 */ | |
60 ThumbnailLoader.MAX_FILE_SIZE = 1 << 20; // 1 Mb | |
61 | |
62 /** | |
63 * In percents (0.0 - 1.0), how much area can be cropped to fill an image | 52 * In percents (0.0 - 1.0), how much area can be cropped to fill an image |
64 * in a container, when loading a thumbnail in FillMode.AUTO mode. | 53 * in a container, when loading a thumbnail in FillMode.AUTO mode. |
65 * The specified 30% value allows to fill 16:9, 3:2 pictures in 4:3 element. | 54 * The specified 30% value allows to fill 16:9, 3:2 pictures in 4:3 element. |
66 * @type {number} | 55 * @type {number} |
67 */ | 56 */ |
68 ThumbnailLoader.AUTO_FILL_THRESHOLD = 0.3; | 57 ThumbnailLoader.AUTO_FILL_THRESHOLD = 0.3; |
69 | 58 |
70 /** | 59 /** |
71 * Type of displaying a thumbnail within a box. | 60 * Type of displaying a thumbnail within a box. |
72 * @enum | 61 * @enum |
73 */ | 62 */ |
74 ThumbnailLoader.FillMode = { | 63 ThumbnailLoader.FillMode = { |
75 FILL: 0, // Fill whole box. Image may be cropped. | 64 FILL: 0, // Fill whole box. Image may be cropped. |
76 FIT: 1, // Keep aspect ratio, do not crop. | 65 FIT: 1, // Keep aspect ratio, do not crop. |
77 AUTO: 2 // Try to fill, but if incompatible aspect ratio, then fit. | 66 AUTO: 2 // Try to fill, but if incompatible aspect ratio, then fit. |
78 }; | 67 }; |
79 | 68 |
80 /** | 69 /** |
| 70 * Optimization mode for downloading thumbnails. |
| 71 * @enum |
| 72 */ |
| 73 ThumbnailLoader.OptimizationMode = { |
| 74 NEVER_DISCARD: 0, // Never discards downloading. No optimization. |
| 75 DISCARD_DETACHED: 1 // Canceled if the container is not attached anymore. |
| 76 }; |
| 77 |
| 78 /** |
81 * Type of element to store the image. | 79 * Type of element to store the image. |
82 * @enum | 80 * @enum |
83 */ | 81 */ |
84 ThumbnailLoader.LoaderType = { | 82 ThumbnailLoader.LoaderType = { |
85 IMAGE: 0, | 83 IMAGE: 0, |
86 CANVAS: 1 | 84 CANVAS: 1 |
87 }; | 85 }; |
88 | 86 |
89 /** | 87 /** |
90 * If an image file does not have an embedded thumbnail we might want to use | 88 * Maximum thumbnail's width when generating from the full resolution image. |
91 * the image itself as a thumbnail. If the image is too large it hurts | 89 * @const |
92 * the performance a lot so we allow it only for moderately sized files. | 90 * @type {number} |
93 * | |
94 * @param {Object} metadata Metadata object. | |
95 * @return {boolean} Whether it is OK to use the image url for a preview. | |
96 * @private | |
97 */ | 91 */ |
98 ThumbnailLoader.canUseImageUrl_ = function(metadata) { | 92 ThumbnailLoader.THUMBNAIL_MAX_WIDTH = 500; |
99 return (metadata.filesystem && metadata.filesystem.size && | |
100 metadata.filesystem.size <= ThumbnailLoader.MAX_FILE_SIZE) || | |
101 (metadata.media && metadata.media.width && metadata.media.height && | |
102 metadata.media.width * metadata.media.height <= | |
103 ThumbnailLoader.MAX_PIXEL_COUNT); | |
104 }; | |
105 | 93 |
106 /** | 94 /** |
| 95 * Maximum thumbnail's height when generating from the full resolution image. |
| 96 * @const |
| 97 * @type {number} |
| 98 */ |
| 99 ThumbnailLoader.THUMBNAIL_MAX_HEIGHT = 500; |
| 100 |
| 101 /** |
| 102 * Loads and attaches an image. |
107 * | 103 * |
108 * @param {HTMLElement} box Container element. | 104 * @param {HTMLElement} box Container element. |
109 * @param {ThumbnailLoader.FillMode} fillMode Fill mode. | 105 * @param {ThumbnailLoader.FillMode} fillMode Fill mode. |
| 106 * @param {ThumbnailLoader.OptimizationMode=} opt_optimizationMode Optimization |
| 107 * for downloading thumbnails. By default optimizations are disabled. |
110 * @param {function(Image, object} opt_onSuccess Success callback, | 108 * @param {function(Image, object} opt_onSuccess Success callback, |
111 * accepts the image and the transform. | 109 * accepts the image and the transform. |
112 * @param {function} opt_onError Error callback. | 110 * @param {function} opt_onError Error callback. |
113 * @param {function} opt_onGeneric Callback for generic image used. | 111 * @param {function} opt_onGeneric Callback for generic image used. |
114 */ | 112 */ |
115 ThumbnailLoader.prototype.load = function( | 113 ThumbnailLoader.prototype.load = function(box, fillMode, opt_optimizationMode, |
116 box, fillMode, opt_onSuccess, opt_onError, opt_onGeneric) { | 114 opt_onSuccess, opt_onError, opt_onGeneric) { |
| 115 opt_optimizationMode = opt_optimizationMode || |
| 116 ThumbnailLoader.OptimizationMode.NEVER_DISCARD; |
| 117 |
117 if (!this.thumbnailUrl_) { | 118 if (!this.thumbnailUrl_) { |
118 // Relevant CSS rules are in file_types.css. | 119 // Relevant CSS rules are in file_types.css. |
119 box.setAttribute('generic-thumbnail', this.mediaType_); | 120 box.setAttribute('generic-thumbnail', this.mediaType_); |
120 if (opt_onGeneric) opt_onGeneric(); | 121 if (opt_onGeneric) opt_onGeneric(); |
121 return; | 122 return; |
122 } | 123 } |
123 | 124 |
124 this.canvasUpToDate_ = false; | 125 this.canvasUpToDate_ = false; |
125 this.image_ = new Image(); | 126 this.image_ = new Image(); |
126 this.image_.onload = function() { | 127 this.image_.onload = function() { |
127 this.attachImage(box, fillMode); | 128 this.attachImage(box, fillMode); |
128 if (opt_onSuccess) | 129 if (opt_onSuccess) |
129 opt_onSuccess(this.image_, this.transform_); | 130 opt_onSuccess(this.image_, this.transform_); |
130 }.bind(this); | 131 }.bind(this); |
131 this.image_.onerror = function() { | 132 this.image_.onerror = function() { |
132 if (opt_onError) | 133 if (opt_onError) |
133 opt_onError(); | 134 opt_onError(); |
134 if (this.fallbackUrl_) { | 135 if (this.fallbackUrl_) { |
135 new ThumbnailLoader(this.fallbackUrl_, | 136 new ThumbnailLoader(this.fallbackUrl_, |
136 this.loaderType_, | 137 this.loaderType_, |
137 null, | 138 null, |
138 this.mediaType_). | 139 this.mediaType_). |
139 load(box, fillMode, opt_onSuccess); | 140 load(box, fillMode, opt_onSuccess); |
140 } else { | 141 } else { |
141 box.setAttribute('generic-thumbnail', this.mediaType_); | 142 box.setAttribute('generic-thumbnail', this.mediaType_); |
142 } | 143 } |
143 }.bind(this); | 144 }.bind(this); |
144 | 145 |
145 if (this.image_.src == this.thumbnailUrl_) { | 146 if (this.image_.src) { |
146 console.warn('Thumnbnail already loaded: ' + this.thumbnailUrl_); | 147 console.warn('Thumbnail already loaded: ' + this.thumbnailUrl_); |
147 return; | 148 return; |
148 } | 149 } |
149 | 150 |
150 util.loadImage(this.image_, this.thumbnailUrl_); | 151 // TODO(mtomasz): Smarter calculation of the requested size. |
| 152 var wasAttached = box.ownerDocument.contains(box); |
| 153 var taskId = util.loadImage( |
| 154 this.image_, |
| 155 this.thumbnailUrl_, |
| 156 { maxWidth: ThumbnailLoader.THUMBNAIL_MAX_WIDTH, |
| 157 maxHeight: ThumbnailLoader.THUMBNAIL_MAX_HEIGHT, |
| 158 cache: true }, |
| 159 function() { |
| 160 if (opt_optimizationMode == |
| 161 ThumbnailLoader.OptimizationMode.DISCARD_DETACHED && |
| 162 !box.ownerDocument.contains(box)) { |
| 163 // If the container is not attached, then invalidate the download. |
| 164 return false; |
| 165 } |
| 166 return true; |
| 167 }); |
| 168 |
| 169 if (!taskId) |
| 170 this.image_.classList.add('cached'); |
151 }; | 171 }; |
152 | 172 |
153 /** | 173 /** |
154 * @return {boolean} True if a valid image is loaded. | 174 * @return {boolean} True if a valid image is loaded. |
155 */ | 175 */ |
156 ThumbnailLoader.prototype.hasValidImage = function() { | 176 ThumbnailLoader.prototype.hasValidImage = function() { |
157 return !!(this.image_ && this.image_.width && this.image_.height); | 177 return !!(this.image_ && this.image_.width && this.image_.height); |
158 }; | 178 }; |
159 | 179 |
160 /** | 180 /** |
(...skipping 27 matching lines...) Expand all Loading... |
188 ThumbnailLoader.prototype.loadDetachedImage = function(callback) { | 208 ThumbnailLoader.prototype.loadDetachedImage = function(callback) { |
189 if (!this.thumbnailUrl_) { | 209 if (!this.thumbnailUrl_) { |
190 callback(true); | 210 callback(true); |
191 return; | 211 return; |
192 } | 212 } |
193 | 213 |
194 this.canvasUpToDate_ = false; | 214 this.canvasUpToDate_ = false; |
195 this.image_ = new Image(); | 215 this.image_ = new Image(); |
196 this.image_.onload = callback.bind(null, true); | 216 this.image_.onload = callback.bind(null, true); |
197 this.image_.onerror = callback.bind(null, false); | 217 this.image_.onerror = callback.bind(null, false); |
198 util.loadImage(this.image_, this.thumbnailUrl_); | 218 |
| 219 // TODO(mtomasz): Smarter calculation of the requested size. |
| 220 var taskId = util.loadImage( |
| 221 this.image_, |
| 222 this.thumbnailUrl_, |
| 223 { maxWidth: ThumbnailLoader.THUMBNAIL_MAX_WIDTH, |
| 224 maxHeight: ThumbnailLoader.THUMBNAIL_MAX_HEIGHT, |
| 225 cache: true }); |
| 226 |
| 227 if (!taskId) |
| 228 this.image_.classList.add('cached'); |
199 }; | 229 }; |
200 | 230 |
201 /** | 231 /** |
202 * Attach the image to a given element. | 232 * Attach the image to a given element. |
203 * @param {Element} container Parent element. | 233 * @param {Element} container Parent element. |
204 * @param {ThumbnailLoader.FillMode} fillMode Fill mode. | 234 * @param {ThumbnailLoader.FillMode} fillMode Fill mode. |
205 */ | 235 */ |
206 ThumbnailLoader.prototype.attachImage = function(container, fillMode) { | 236 ThumbnailLoader.prototype.attachImage = function(container, fillMode) { |
207 if (!this.hasValidImage()) { | 237 if (!this.hasValidImage()) { |
208 container.setAttribute('generic-thumbnail', this.mediaType_); | 238 container.setAttribute('generic-thumbnail', this.mediaType_); |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
310 | 340 |
311 function percent(fraction) { | 341 function percent(fraction) { |
312 return (fraction * 100).toFixed(2) + '%'; | 342 return (fraction * 100).toFixed(2) + '%'; |
313 } | 343 } |
314 | 344 |
315 img.style.width = percent(fractionX); | 345 img.style.width = percent(fractionX); |
316 img.style.height = percent(fractionY); | 346 img.style.height = percent(fractionY); |
317 img.style.left = percent((1 - fractionX) / 2); | 347 img.style.left = percent((1 - fractionX) / 2); |
318 img.style.top = percent((1 - fractionY) / 2); | 348 img.style.top = percent((1 - fractionY) / 2); |
319 }; | 349 }; |
OLD | NEW |