Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(16)

Side by Side Diff: chrome/browser/resources/file_manager/js/media/media_util.js

Issue 39123003: [Files.app] Split the JavaScript files into subdirectories: common, background, and foreground (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fixed test failure. Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 'use strict';
6
7 /**
8 * Loads a thumbnail using provided url. In CANVAS mode, loaded images
9 * are attached as <canvas> element, while in IMAGE mode as <img>.
10 * <canvas> renders faster than <img>, however has bigger memory overhead.
11 *
12 * @param {string} url File URL.
13 * @param {ThumbnailLoader.LoaderType=} opt_loaderType Canvas or Image loader,
14 * default: IMAGE.
15 * @param {Object=} opt_metadata Metadata object.
16 * @param {string=} opt_mediaType Media type.
17 * @param {ThumbnailLoader.UseEmbedded=} opt_useEmbedded If to use embedded
18 * jpeg thumbnail if available. Default: USE_EMBEDDED.
19 * @param {number=} opt_priority Priority, the highest is 0. default: 2.
20 * @constructor
21 */
22 function ThumbnailLoader(url, opt_loaderType, opt_metadata, opt_mediaType,
23 opt_useEmbedded, opt_priority) {
24 opt_useEmbedded = opt_useEmbedded || ThumbnailLoader.UseEmbedded.USE_EMBEDDED;
25
26 this.mediaType_ = opt_mediaType || FileType.getMediaType(url);
27 this.loaderType_ = opt_loaderType || ThumbnailLoader.LoaderType.IMAGE;
28 this.metadata_ = opt_metadata;
29 this.priority_ = (opt_priority !== undefined) ? opt_priority : 2;
30 this.transform_ = null;
31
32 if (!opt_metadata) {
33 this.thumbnailUrl_ = url; // Use the URL directly.
34 return;
35 }
36
37 this.fallbackUrl_ = null;
38 this.thumbnailUrl_ = null;
39 if (opt_metadata.drive && opt_metadata.drive.customIconUrl)
40 this.fallbackUrl_ = opt_metadata.drive.customIconUrl;
41
42 // Fetch the rotation from the Drive metadata (if available).
43 var driveTransform;
44 if (opt_metadata.drive && opt_metadata.drive.imageRotation !== undefined) {
45 driveTransform = {
46 scaleX: 1,
47 scaleY: 1,
48 rotate90: opt_metadata.drive.imageRotation / 90
49 };
50 }
51
52 if (opt_metadata.thumbnail && opt_metadata.thumbnail.url &&
53 opt_useEmbedded == ThumbnailLoader.UseEmbedded.USE_EMBEDDED) {
54 this.thumbnailUrl_ = opt_metadata.thumbnail.url;
55 this.transform_ = driveTransform !== undefined ? driveTransform :
56 opt_metadata.thumbnail.transform;
57 } else if (FileType.isImage(url)) {
58 this.thumbnailUrl_ = url;
59 this.transform_ = driveTransform !== undefined ? driveTransform :
60 opt_metadata.media && opt_metadata.media.imageTransform;
61 } else if (this.fallbackUrl_) {
62 // Use fallback as the primary thumbnail.
63 this.thumbnailUrl_ = this.fallbackUrl_;
64 this.fallbackUrl_ = null;
65 } // else the generic thumbnail based on the media type will be used.
66 }
67
68 /**
69 * In percents (0.0 - 1.0), how much area can be cropped to fill an image
70 * in a container, when loading a thumbnail in FillMode.AUTO mode.
71 * The specified 30% value allows to fill 16:9, 3:2 pictures in 4:3 element.
72 * @type {number}
73 */
74 ThumbnailLoader.AUTO_FILL_THRESHOLD = 0.3;
75
76 /**
77 * Type of displaying a thumbnail within a box.
78 * @enum {number}
79 */
80 ThumbnailLoader.FillMode = {
81 FILL: 0, // Fill whole box. Image may be cropped.
82 FIT: 1, // Keep aspect ratio, do not crop.
83 OVER_FILL: 2, // Fill whole box with possible stretching.
84 AUTO: 3 // Try to fill, but if incompatible aspect ratio, then fit.
85 };
86
87 /**
88 * Optimization mode for downloading thumbnails.
89 * @enum {number}
90 */
91 ThumbnailLoader.OptimizationMode = {
92 NEVER_DISCARD: 0, // Never discards downloading. No optimization.
93 DISCARD_DETACHED: 1 // Canceled if the container is not attached anymore.
94 };
95
96 /**
97 * Type of element to store the image.
98 * @enum {number}
99 */
100 ThumbnailLoader.LoaderType = {
101 IMAGE: 0,
102 CANVAS: 1
103 };
104
105 /**
106 * Whether to use the embedded thumbnail, or not. The embedded thumbnail may
107 * be small.
108 * @enum {number}
109 */
110 ThumbnailLoader.UseEmbedded = {
111 USE_EMBEDDED: 0,
112 NO_EMBEDDED: 1
113 };
114
115 /**
116 * Maximum thumbnail's width when generating from the full resolution image.
117 * @const
118 * @type {number}
119 */
120 ThumbnailLoader.THUMBNAIL_MAX_WIDTH = 500;
121
122 /**
123 * Maximum thumbnail's height when generating from the full resolution image.
124 * @const
125 * @type {number}
126 */
127 ThumbnailLoader.THUMBNAIL_MAX_HEIGHT = 500;
128
129 /**
130 * Loads and attaches an image.
131 *
132 * @param {HTMLElement} box Container element.
133 * @param {ThumbnailLoader.FillMode} fillMode Fill mode.
134 * @param {ThumbnailLoader.OptimizationMode=} opt_optimizationMode Optimization
135 * for downloading thumbnails. By default optimizations are disabled.
136 * @param {function(Image, Object)} opt_onSuccess Success callback,
137 * accepts the image and the transform.
138 * @param {function} opt_onError Error callback.
139 * @param {function} opt_onGeneric Callback for generic image used.
140 */
141 ThumbnailLoader.prototype.load = function(box, fillMode, opt_optimizationMode,
142 opt_onSuccess, opt_onError, opt_onGeneric) {
143 opt_optimizationMode = opt_optimizationMode ||
144 ThumbnailLoader.OptimizationMode.NEVER_DISCARD;
145
146 if (!this.thumbnailUrl_) {
147 // Relevant CSS rules are in file_types.css.
148 box.setAttribute('generic-thumbnail', this.mediaType_);
149 if (opt_onGeneric) opt_onGeneric();
150 return;
151 }
152
153 this.cancel();
154 this.canvasUpToDate_ = false;
155 this.image_ = new Image();
156 this.image_.onload = function() {
157 this.attachImage(box, fillMode);
158 if (opt_onSuccess)
159 opt_onSuccess(this.image_, this.transform_);
160 }.bind(this);
161 this.image_.onerror = function() {
162 if (opt_onError)
163 opt_onError();
164 if (this.fallbackUrl_) {
165 new ThumbnailLoader(this.fallbackUrl_,
166 this.loaderType_,
167 null, // No metadata.
168 this.mediaType_,
169 undefined, // Default value for use-embedded.
170 this.priority_).
171 load(box, fillMode, opt_optimizationMode, opt_onSuccess);
172 } else {
173 box.setAttribute('generic-thumbnail', this.mediaType_);
174 }
175 }.bind(this);
176
177 if (this.image_.src) {
178 console.warn('Thumbnail already loaded: ' + this.thumbnailUrl_);
179 return;
180 }
181
182 // TODO(mtomasz): Smarter calculation of the requested size.
183 var wasAttached = box.ownerDocument.contains(box);
184 var modificationTime = this.metadata_ &&
185 this.metadata_.filesystem &&
186 this.metadata_.filesystem.modificationTime &&
187 this.metadata_.filesystem.modificationTime.getTime();
188 this.taskId_ = util.loadImage(
189 this.image_,
190 this.thumbnailUrl_,
191 { maxWidth: ThumbnailLoader.THUMBNAIL_MAX_WIDTH,
192 maxHeight: ThumbnailLoader.THUMBNAIL_MAX_HEIGHT,
193 cache: true,
194 priority: this.priority_,
195 timestamp: modificationTime },
196 function() {
197 if (opt_optimizationMode ==
198 ThumbnailLoader.OptimizationMode.DISCARD_DETACHED &&
199 !box.ownerDocument.contains(box)) {
200 // If the container is not attached, then invalidate the download.
201 return false;
202 }
203 return true;
204 });
205 };
206
207 /**
208 * Cancels loading the current image.
209 */
210 ThumbnailLoader.prototype.cancel = function() {
211 if (this.taskId_) {
212 this.image_.onload = function() {};
213 this.image_.onerror = function() {};
214 util.cancelLoadImage(this.taskId_);
215 this.taskId_ = null;
216 }
217 };
218
219 /**
220 * @return {boolean} True if a valid image is loaded.
221 */
222 ThumbnailLoader.prototype.hasValidImage = function() {
223 return !!(this.image_ && this.image_.width && this.image_.height);
224 };
225
226 /**
227 * @return {boolean} True if the image is rotated 90 degrees left or right.
228 * @private
229 */
230 ThumbnailLoader.prototype.isRotated_ = function() {
231 return this.transform_ && (this.transform_.rotate90 % 2 == 1);
232 };
233
234 /**
235 * @return {number} Image width (corrected for rotation).
236 */
237 ThumbnailLoader.prototype.getWidth = function() {
238 return this.isRotated_() ? this.image_.height : this.image_.width;
239 };
240
241 /**
242 * @return {number} Image height (corrected for rotation).
243 */
244 ThumbnailLoader.prototype.getHeight = function() {
245 return this.isRotated_() ? this.image_.width : this.image_.height;
246 };
247
248 /**
249 * Load an image but do not attach it.
250 *
251 * @param {function(boolean)} callback Callback, parameter is true if the image
252 * has loaded successfully or a stock icon has been used.
253 */
254 ThumbnailLoader.prototype.loadDetachedImage = function(callback) {
255 if (!this.thumbnailUrl_) {
256 callback(true);
257 return;
258 }
259
260 this.cancel();
261 this.canvasUpToDate_ = false;
262 this.image_ = new Image();
263 this.image_.onload = callback.bind(null, true);
264 this.image_.onerror = callback.bind(null, false);
265
266 // TODO(mtomasz): Smarter calculation of the requested size.
267 var modificationTime = this.metadata_ &&
268 this.metadata_.filesystem &&
269 this.metadata_.filesystem.modificationTime &&
270 this.metadata_.filesystem.modificationTime.getTime();
271 this.taskId_ = util.loadImage(
272 this.image_,
273 this.thumbnailUrl_,
274 { maxWidth: ThumbnailLoader.THUMBNAIL_MAX_WIDTH,
275 maxHeight: ThumbnailLoader.THUMBNAIL_MAX_HEIGHT,
276 cache: true,
277 priority: this.priority_,
278 timestamp: modificationTime });
279 };
280
281 /**
282 * Renders the thumbnail into either canvas or an image element.
283 * @private
284 */
285 ThumbnailLoader.prototype.renderMedia_ = function() {
286 if (this.loaderType_ != ThumbnailLoader.LoaderType.CANVAS)
287 return;
288
289 if (!this.canvas_)
290 this.canvas_ = document.createElement('canvas');
291
292 // Copy the image to a canvas if the canvas is outdated.
293 if (!this.canvasUpToDate_) {
294 this.canvas_.width = this.image_.width;
295 this.canvas_.height = this.image_.height;
296 var context = this.canvas_.getContext('2d');
297 context.drawImage(this.image_, 0, 0);
298 this.canvasUpToDate_ = true;
299 }
300 };
301
302 /**
303 * Attach the image to a given element.
304 * @param {Element} container Parent element.
305 * @param {ThumbnailLoader.FillMode} fillMode Fill mode.
306 */
307 ThumbnailLoader.prototype.attachImage = function(container, fillMode) {
308 if (!this.hasValidImage()) {
309 container.setAttribute('generic-thumbnail', this.mediaType_);
310 return;
311 }
312
313 this.renderMedia_();
314 util.applyTransform(container, this.transform_);
315 var attachableMedia = this.loaderType_ == ThumbnailLoader.LoaderType.CANVAS ?
316 this.canvas_ : this.image_;
317
318 ThumbnailLoader.centerImage_(
319 container, attachableMedia, fillMode, this.isRotated_());
320
321 if (attachableMedia.parentNode != container) {
322 container.textContent = '';
323 container.appendChild(attachableMedia);
324 }
325
326 if (!this.taskId_)
327 attachableMedia.classList.add('cached');
328 };
329
330 /**
331 * Gets the loaded image.
332 * TODO(mtomasz): Apply transformations.
333 *
334 * @return {Image|HTMLCanvasElement} Either image or a canvas object.
335 */
336 ThumbnailLoader.prototype.getImage = function() {
337 this.renderMedia_();
338 return this.loaderType_ == ThumbnailLoader.LoaderType.CANVAS ? this.canvas_ :
339 this.image_;
340 };
341
342 /**
343 * Update the image style to fit/fill the container.
344 *
345 * Using webkit center packing does not align the image properly, so we need
346 * to wait until the image loads and its dimensions are known, then manually
347 * position it at the center.
348 *
349 * @param {HTMLElement} box Containing element.
350 * @param {Image|HTMLCanvasElement} img Element containing an image.
351 * @param {ThumbnailLoader.FillMode} fillMode Fill mode.
352 * @param {boolean} rotate True if the image should be rotated 90 degrees.
353 * @private
354 */
355 ThumbnailLoader.centerImage_ = function(box, img, fillMode, rotate) {
356 var imageWidth = img.width;
357 var imageHeight = img.height;
358
359 var fractionX;
360 var fractionY;
361
362 var boxWidth = box.clientWidth;
363 var boxHeight = box.clientHeight;
364
365 var fill;
366 switch (fillMode) {
367 case ThumbnailLoader.FillMode.FILL:
368 case ThumbnailLoader.FillMode.OVER_FILL:
369 fill = true;
370 break;
371 case ThumbnailLoader.FillMode.FIT:
372 fill = false;
373 break;
374 case ThumbnailLoader.FillMode.AUTO:
375 var imageRatio = imageWidth / imageHeight;
376 var boxRatio = 1.0;
377 if (boxWidth && boxHeight)
378 boxRatio = boxWidth / boxHeight;
379 // Cropped area in percents.
380 var ratioFactor = boxRatio / imageRatio;
381 fill = (ratioFactor >= 1.0 - ThumbnailLoader.AUTO_FILL_THRESHOLD) &&
382 (ratioFactor <= 1.0 + ThumbnailLoader.AUTO_FILL_THRESHOLD);
383 break;
384 }
385
386 if (boxWidth && boxHeight) {
387 // When we know the box size we can position the image correctly even
388 // in a non-square box.
389 var fitScaleX = (rotate ? boxHeight : boxWidth) / imageWidth;
390 var fitScaleY = (rotate ? boxWidth : boxHeight) / imageHeight;
391
392 var scale = fill ?
393 Math.max(fitScaleX, fitScaleY) :
394 Math.min(fitScaleX, fitScaleY);
395
396 if (fillMode != ThumbnailLoader.FillMode.OVER_FILL)
397 scale = Math.min(scale, 1); // Never overscale.
398
399 fractionX = imageWidth * scale / boxWidth;
400 fractionY = imageHeight * scale / boxHeight;
401 } else {
402 // We do not know the box size so we assume it is square.
403 // Compute the image position based only on the image dimensions.
404 // First try vertical fit or horizontal fill.
405 fractionX = imageWidth / imageHeight;
406 fractionY = 1;
407 if ((fractionX < 1) == !!fill) { // Vertical fill or horizontal fit.
408 fractionY = 1 / fractionX;
409 fractionX = 1;
410 }
411 }
412
413 function percent(fraction) {
414 return (fraction * 100).toFixed(2) + '%';
415 }
416
417 img.style.width = percent(fractionX);
418 img.style.height = percent(fractionY);
419 img.style.left = percent((1 - fractionX) / 2);
420 img.style.top = percent((1 - fractionY) / 2);
421 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698