blob: b44d102d9b14fb606fd19c33cb3022ab7641a746 [file] [log] [blame]
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001// 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
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00005'use strict';
6
Torne (Richard Coles)58218062012-11-14 11:43:16 +00007
8// Namespace object for the utilities.
9function ImageUtil() {}
10
11/**
12 * Performance trace.
13 */
14ImageUtil.trace = (function() {
15 function PerformanceTrace() {
16 this.lines_ = {};
17 this.timers_ = {};
18 this.container_ = null;
19 }
20
21 PerformanceTrace.prototype.bindToDOM = function(container) {
22 this.container_ = container;
23 };
24
25 PerformanceTrace.prototype.report = function(key, value) {
26 if (!(key in this.lines_)) {
27 if (this.container_) {
28 var div = this.lines_[key] = document.createElement('div');
29 this.container_.appendChild(div);
30 } else {
31 this.lines_[key] = {};
32 }
33 }
34 this.lines_[key].textContent = key + ': ' + value;
35 if (ImageUtil.trace.log) this.dumpLine(key);
36 };
37
38 PerformanceTrace.prototype.resetTimer = function(key) {
39 this.timers_[key] = Date.now();
40 };
41
42 PerformanceTrace.prototype.reportTimer = function(key) {
43 this.report(key, (Date.now() - this.timers_[key]) + 'ms');
44 };
45
46 PerformanceTrace.prototype.dump = function() {
47 for (var key in this.lines_)
48 this.dumpLine(key);
49 };
50
51 PerformanceTrace.prototype.dumpLine = function(key) {
52 console.log('trace.' + this.lines_[key].textContent);
53 };
54
55 return new PerformanceTrace();
56})();
57
58/**
59 * @param {number} min Minimum value.
60 * @param {number} value Value to adjust.
61 * @param {number} max Maximum value.
62 * @return {number} The closest to the |value| number in span [min, max].
63 */
64ImageUtil.clamp = function(min, value, max) {
65 return Math.max(min, Math.min(max, value));
66};
67
68/**
69 * @param {number} min Minimum value.
70 * @param {number} value Value to check.
71 * @param {number} max Maximum value.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000072 * @return {boolean} True if value is between.
Torne (Richard Coles)58218062012-11-14 11:43:16 +000073 */
74ImageUtil.between = function(min, value, max) {
75 return (value - min) * (value - max) <= 0;
76};
77
78/**
79 * Rectangle class.
80 */
81
82/**
Torne (Richard Coles)4e180b62013-10-18 15:46:22 +010083 * Rectangle constructor takes 0, 1, 2 or 4 arguments.
Torne (Richard Coles)58218062012-11-14 11:43:16 +000084 * Supports following variants:
85 * new Rect(left, top, width, height)
86 * new Rect(width, height)
87 * new Rect(rect) // anything with left, top, width, height properties
88 * new Rect(bounds) // anything with left, top, right, bottom properties
89 * new Rect(canvas|image) // anything with width and height properties.
90 * new Rect() // empty rectangle.
91 * @constructor
92 */
93function Rect() {
94 switch (arguments.length) {
95 case 4:
96 this.left = arguments[0];
97 this.top = arguments[1];
98 this.width = arguments[2];
99 this.height = arguments[3];
100 return;
101
102 case 2:
103 this.left = 0;
104 this.top = 0;
105 this.width = arguments[0];
106 this.height = arguments[1];
107 return;
108
109 case 1: {
110 var source = arguments[0];
111 if ('left' in source && 'top' in source) {
112 this.left = source.left;
113 this.top = source.top;
114 if ('right' in source && 'bottom' in source) {
115 this.width = source.right - source.left;
116 this.height = source.bottom - source.top;
117 return;
118 }
119 } else {
120 this.left = 0;
121 this.top = 0;
122 }
123 if ('width' in source && 'height' in source) {
124 this.width = source.width;
125 this.height = source.height;
126 return;
127 }
128 break; // Fall through to the error message.
129 }
130
131 case 0:
132 this.left = 0;
133 this.top = 0;
134 this.width = 0;
135 this.height = 0;
136 return;
137 }
138 console.error('Invalid Rect constructor arguments:',
139 Array.apply(null, arguments));
140}
141
142/**
143 * @param {number} factor Factor to scale.
144 * @return {Rect} A rectangle with every dimension scaled.
145 */
146Rect.prototype.scale = function(factor) {
147 return new Rect(
148 this.left * factor,
149 this.top * factor,
150 this.width * factor,
151 this.height * factor);
152};
153
154/**
155 * @param {number} dx Difference in X.
156 * @param {number} dy Difference in Y.
157 * @return {Rect} A rectangle shifted by (dx,dy), same size.
158 */
159Rect.prototype.shift = function(dx, dy) {
160 return new Rect(this.left + dx, this.top + dy, this.width, this.height);
161};
162
163/**
164 * @param {number} x Coordinate of the left top corner.
165 * @param {number} y Coordinate of the left top corner.
166 * @return {Rect} A rectangle with left==x and top==y, same size.
167 */
168Rect.prototype.moveTo = function(x, y) {
169 return new Rect(x, y, this.width, this.height);
170};
171
172/**
173 * @param {number} dx Difference in X.
174 * @param {number} dy Difference in Y.
175 * @return {Rect} A rectangle inflated by (dx, dy), same center.
176 */
177Rect.prototype.inflate = function(dx, dy) {
178 return new Rect(
179 this.left - dx, this.top - dy, this.width + 2 * dx, this.height + 2 * dy);
180};
181
182/**
183 * @param {number} x Coordinate of the point.
184 * @param {number} y Coordinate of the point.
Torne (Richard Coles)4e180b62013-10-18 15:46:22 +0100185 * @return {boolean} True if the point lies inside the rectangle.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000186 */
187Rect.prototype.inside = function(x, y) {
188 return this.left <= x && x < this.left + this.width &&
189 this.top <= y && y < this.top + this.height;
190};
191
192/**
193 * @param {Rect} rect Rectangle to check.
Torne (Richard Coles)4e180b62013-10-18 15:46:22 +0100194 * @return {boolean} True if this rectangle intersects with the |rect|.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000195 */
196Rect.prototype.intersects = function(rect) {
197 return (this.left + this.width) > rect.left &&
198 (rect.left + rect.width) > this.left &&
199 (this.top + this.height) > rect.top &&
200 (rect.top + rect.height) > this.top;
201};
202
203/**
204 * @param {Rect} rect Rectangle to check.
Torne (Richard Coles)4e180b62013-10-18 15:46:22 +0100205 * @return {boolean} True if this rectangle containing the |rect|.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000206 */
207Rect.prototype.contains = function(rect) {
208 return (this.left <= rect.left) &&
209 (rect.left + rect.width) <= (this.left + this.width) &&
210 (this.top <= rect.top) &&
211 (rect.top + rect.height) <= (this.top + this.height);
212};
213
214/**
215 * @return {boolean} True if rectangle is empty.
216 */
217Rect.prototype.isEmpty = function() {
218 return this.width == 0 || this.height == 0;
219};
220
221/**
222 * Clamp the rectangle to the bounds by moving it.
223 * Decrease the size only if necessary.
224 * @param {Rect} bounds Bounds.
Torne (Richard Coles)4e180b62013-10-18 15:46:22 +0100225 * @return {Rect} Calculated rectangle.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000226 */
227Rect.prototype.clamp = function(bounds) {
228 var rect = new Rect(this);
229
230 if (rect.width > bounds.width) {
231 rect.left = bounds.left;
232 rect.width = bounds.width;
233 } else if (rect.left < bounds.left) {
234 rect.left = bounds.left;
235 } else if (rect.left + rect.width >
236 bounds.left + bounds.width) {
237 rect.left = bounds.left + bounds.width - rect.width;
238 }
239
240 if (rect.height > bounds.height) {
241 rect.top = bounds.top;
242 rect.height = bounds.height;
243 } else if (rect.top < bounds.top) {
244 rect.top = bounds.top;
245 } else if (rect.top + rect.height >
246 bounds.top + bounds.height) {
247 rect.top = bounds.top + bounds.height - rect.height;
248 }
249
250 return rect;
251};
252
253/**
254 * @return {string} String representation.
255 */
256Rect.prototype.toString = function() {
257 return '(' + this.left + ',' + this.top + '):' +
258 '(' + (this.left + this.width) + ',' + (this.top + this.height) + ')';
259};
260/*
261 * Useful shortcuts for drawing (static functions).
262 */
263
264/**
265 * Draw the image in context with appropriate scaling.
266 * @param {CanvasRenderingContext2D} context Context to draw.
267 * @param {Image} image Image to draw.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000268 * @param {Rect=} opt_dstRect Rectangle in the canvas (whole canvas by default).
Torne (Richard Coles)4e180b62013-10-18 15:46:22 +0100269 * @param {Rect=} opt_srcRect Rectangle in the image (whole image by default).
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000270 */
271Rect.drawImage = function(context, image, opt_dstRect, opt_srcRect) {
272 opt_dstRect = opt_dstRect || new Rect(context.canvas);
273 opt_srcRect = opt_srcRect || new Rect(image);
274 if (opt_dstRect.isEmpty() || opt_srcRect.isEmpty())
275 return;
276 context.drawImage(image,
277 opt_srcRect.left, opt_srcRect.top, opt_srcRect.width, opt_srcRect.height,
278 opt_dstRect.left, opt_dstRect.top, opt_dstRect.width, opt_dstRect.height);
279};
280
281/**
282 * Draw a box around the rectangle.
283 * @param {CanvasRenderingContext2D} context Context to draw.
284 * @param {Rect} rect Rectangle.
285 */
286Rect.outline = function(context, rect) {
287 context.strokeRect(
288 rect.left - 0.5, rect.top - 0.5, rect.width + 1, rect.height + 1);
289};
290
291/**
292 * Fill the rectangle.
293 * @param {CanvasRenderingContext2D} context Context to draw.
294 * @param {Rect} rect Rectangle.
295 */
296Rect.fill = function(context, rect) {
297 context.fillRect(rect.left, rect.top, rect.width, rect.height);
298};
299
300/**
301 * Fills the space between the two rectangles.
302 * @param {CanvasRenderingContext2D} context Context to draw.
303 * @param {Rect} inner Inner rectangle.
304 * @param {Rect} outer Outer rectangle.
305 */
306Rect.fillBetween = function(context, inner, outer) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000307 var innerRight = inner.left + inner.width;
308 var innerBottom = inner.top + inner.height;
309 var outerRight = outer.left + outer.width;
310 var outerBottom = outer.top + outer.height;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000311 if (inner.top > outer.top) {
312 context.fillRect(
313 outer.left, outer.top, outer.width, inner.top - outer.top);
314 }
315 if (inner.left > outer.left) {
316 context.fillRect(
317 outer.left, inner.top, inner.left - outer.left, inner.height);
318 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000319 if (inner.width < outerRight) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000320 context.fillRect(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000321 innerRight, inner.top, outerRight - innerRight, inner.height);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000322 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000323 if (inner.height < outerBottom) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000324 context.fillRect(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000325 outer.left, innerBottom, outer.width, outerBottom - innerBottom);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000326 }
327};
328
329/**
330 * Circle class.
331 * @param {number} x X coordinate of circle center.
332 * @param {number} y Y coordinate of circle center.
333 * @param {number} r Radius.
334 * @constructor
335 */
336function Circle(x, y, r) {
337 this.x = x;
338 this.y = y;
339 this.squaredR = r * r;
340}
341
342/**
343 * Check if the point is inside the circle.
344 * @param {number} x X coordinate of the point.
345 * @param {number} y Y coordinate of the point.
346 * @return {boolean} True if the point is inside.
347 */
348Circle.prototype.inside = function(x, y) {
349 x -= this.x;
350 y -= this.y;
351 return x * x + y * y <= this.squaredR;
352};
353
354/**
355 * Copy an image applying scaling and rotation.
356 *
357 * @param {HTMLCanvasElement} dst Destination.
358 * @param {HTMLCanvasElement|HTMLImageElement} src Source.
359 * @param {number} scaleX Y scale transformation.
360 * @param {number} scaleY X scale transformation.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000361 * @param {number} angle (in radians).
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000362 */
363ImageUtil.drawImageTransformed = function(dst, src, scaleX, scaleY, angle) {
364 var context = dst.getContext('2d');
365 context.save();
366 context.translate(context.canvas.width / 2, context.canvas.height / 2);
367 context.rotate(angle);
368 context.scale(scaleX, scaleY);
369 context.drawImage(src, -src.width / 2, -src.height / 2);
370 context.restore();
371};
372
373/**
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000374 * Adds or removes an attribute to/from an HTML element.
375 * @param {HTMLElement} element To be applied to.
376 * @param {string} attribute Name of attribute.
377 * @param {boolean} on True if add, false if remove.
378 */
379ImageUtil.setAttribute = function(element, attribute, on) {
380 if (on)
381 element.setAttribute(attribute, '');
382 else
383 element.removeAttribute(attribute);
384};
385
386/**
387 * Adds or removes CSS class to/from an HTML element.
388 * @param {HTMLElement} element To be applied to.
389 * @param {string} className Name of CSS class.
390 * @param {boolean} on True if add, false if remove.
391 */
392ImageUtil.setClass = function(element, className, on) {
393 var cl = element.classList;
394 if (on)
395 cl.add(className);
396 else
397 cl.remove(className);
398};
399
400/**
401 * ImageLoader loads an image from a given URL into a canvas in two steps:
402 * 1. Loads the image into an HTMLImageElement.
403 * 2. Copies pixels from HTMLImageElement to HTMLCanvasElement. This is done
404 * stripe-by-stripe to avoid freezing up the UI. The transform is taken into
405 * account.
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100406 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000407 * @param {HTMLDocument} document Owner document.
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100408 * @param {MetadataCache=} opt_metadataCache Metadata cache. Required for
409 * caching. If not passed, caching will be disabled.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000410 * @constructor
411 */
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100412ImageUtil.ImageLoader = function(document, opt_metadataCache) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000413 this.document_ = document;
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100414 this.metadataCache_ = opt_metadataCache || null;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000415 this.image_ = new Image();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000416 this.generation_ = 0;
417};
418
419/**
420 * Max size of image to be displayed (in pixels)
421 */
422ImageUtil.ImageLoader.IMAGE_SIZE_LIMIT = 25 * 1000 * 1000;
423
424/**
Torne (Richard Coles)4e180b62013-10-18 15:46:22 +0100425 * @param {number} width Width of the image.
426 * @param {number} height Height of the image.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000427 * @return {boolean} True if the image is too large to be loaded.
428 */
Torne (Richard Coles)4e180b62013-10-18 15:46:22 +0100429ImageUtil.ImageLoader.isTooLarge = function(width, height) {
430 return width * height > ImageUtil.ImageLoader.IMAGE_SIZE_LIMIT;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000431};
432
433/**
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100434 * Loads an image.
435 * TODO(mtomasz): Simplify, or even get rid of this class and merge with the
436 * ThumbnaiLoader class.
437 *
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000438 * @param {string} url Image URL.
439 * @param {function(function(object))} transformFetcher function to get
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000440 * the image transform (which we need for the image orientation).
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100441 * @param {function(HTMLCanvasElement, string=)} callback Callback to be
442 * called when loaded. The second optional argument is an error identifier.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000443 * @param {number=} opt_delay Load delay in milliseconds, useful to let the
444 * animations play out before the computation heavy image loading starts.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000445 */
446ImageUtil.ImageLoader.prototype.load = function(
447 url, transformFetcher, callback, opt_delay) {
448 this.cancel();
449
450 this.url_ = url;
451 this.callback_ = callback;
452
453 // The transform fetcher is not cancellable so we need a generation counter.
454 var generation = ++this.generation_;
455 var onTransform = function(image, transform) {
456 if (generation == this.generation_) {
457 this.convertImage_(
458 image, transform || { scaleX: 1, scaleY: 1, rotate90: 0});
459 }
460 };
461
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100462 var onError = function(opt_error) {
463 this.image_.onerror = null;
464 this.image_.onload = null;
465 var tmpCallback = this.callback_;
466 this.callback_ = null;
467 var emptyCanvas = this.document_.createElement('canvas');
468 emptyCanvas.width = 0;
469 emptyCanvas.height = 0;
470 tmpCallback(emptyCanvas, opt_error);
471 }.bind(this);
472
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100473 var loadImage = function(opt_metadata) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000474 ImageUtil.metrics.startInterval(ImageUtil.getMetricName('LoadTime'));
475 this.timeout_ = null;
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100476
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000477 this.image_.onload = function(e) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000478 this.image_.onerror = null;
479 this.image_.onload = null;
Torne (Richard Coles)4e180b62013-10-18 15:46:22 +0100480 if (ImageUtil.ImageLoader.isTooLarge(this.image_.width,
481 this.image_.height)) {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100482 onError('IMAGE_TOO_BIG_ERROR');
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000483 return;
484 }
485 transformFetcher(url, onTransform.bind(this, e.target));
486 }.bind(this);
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100487
488 // The error callback has an optional error argument, which in case of a
489 // general error should not be specified
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100490 this.image_.onerror = onError.bind(this, 'IMAGE_ERROR');
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100491
492 // Extract the last modification date to determine if the cached image
493 // is outdated.
494 var modificationTime = opt_metadata &&
495 opt_metadata.modificationTime &&
496 opt_metadata.modificationTime.getTime();
497
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100498 // Load the image directly.
499 this.image_.src = url;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000500 }.bind(this);
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100501
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100502 // Loads the image. If already loaded, then forces a reload.
503 var startLoad = this.resetImage_.bind(this, function() {
504 // Fetch metadata to detect last modification time for the caching purpose.
505 if (this.metadataCache_)
506 this.metadataCache_.get(url, 'filesystem', loadImage);
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100507 else
508 loadImage();
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100509 }.bind(this), onError);
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100510
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000511 if (opt_delay) {
512 this.timeout_ = setTimeout(startLoad, opt_delay);
513 } else {
514 startLoad();
515 }
516};
517
518/**
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100519 * Resets the image by forcing the garbage collection and clearing the src
520 * attribute.
521 *
522 * @param {function()} onSuccess Success callback.
523 * @param {function(opt_string)} onError Failure callback with an optional
524 * error identifier.
525 * @private
526 */
527ImageUtil.ImageLoader.prototype.resetImage_ = function(onSuccess, onError) {
528 var clearSrc = function() {
529 this.image_.onload = onSuccess;
530 this.image_.onerror = onSuccess;
531 this.image_.src = '';
532 }.bind(this);
533
534 var emptyImage = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAA' +
535 'AAABAAEAAAICTAEAOw==';
536
537 if (this.image_.src != emptyImage) {
538 // Load an empty image, then clear src.
539 this.image_.onload = clearSrc;
540 this.image_.onerror = onError.bind(this, 'IMAGE_ERROR');
541 this.image_.src = emptyImage;
542 } else {
543 // Empty image already loaded, so clear src immediately.
544 clearSrc();
545 }
546};
547
548/**
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000549 * @return {boolean} True if an image is loading.
550 */
551ImageUtil.ImageLoader.prototype.isBusy = function() {
552 return !!this.callback_;
553};
554
555/**
556 * @param {string} url Image url.
557 * @return {boolean} True if loader loads this image.
558 */
559ImageUtil.ImageLoader.prototype.isLoading = function(url) {
560 return this.isBusy() && (this.url_ == url);
561};
562
563/**
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000564 * @param {function} callback To be called when the image loaded.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000565 */
566ImageUtil.ImageLoader.prototype.setCallback = function(callback) {
567 this.callback_ = callback;
568};
569
570/**
571 * Stops loading image.
572 */
573ImageUtil.ImageLoader.prototype.cancel = function() {
574 if (!this.callback_) return;
575 this.callback_ = null;
576 if (this.timeout_) {
577 clearTimeout(this.timeout_);
578 this.timeout_ = null;
579 }
580 if (this.image_) {
581 this.image_.onload = function() {};
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000582 this.image_.onerror = function() {};
583 this.image_.src = '';
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000584 }
585 this.generation_++; // Silence the transform fetcher if it is in progress.
586};
587
588/**
589 * @param {HTMLImageElement} image Image to be transformed.
Torne (Richard Coles)4e180b62013-10-18 15:46:22 +0100590 * @param {Object} transform transformation description to apply to the image.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000591 * @private
592 */
593ImageUtil.ImageLoader.prototype.convertImage_ = function(image, transform) {
594 var canvas = this.document_.createElement('canvas');
595
596 if (transform.rotate90 & 1) { // Rotated +/-90deg, swap the dimensions.
597 canvas.width = image.height;
598 canvas.height = image.width;
599 } else {
600 canvas.width = image.width;
601 canvas.height = image.height;
602 }
603
604 var context = canvas.getContext('2d');
605 context.save();
606 context.translate(canvas.width / 2, canvas.height / 2);
607 context.rotate(transform.rotate90 * Math.PI / 2);
608 context.scale(transform.scaleX, transform.scaleY);
609
610 var stripCount = Math.ceil(image.width * image.height / (1 << 21));
611 var step = Math.max(16, Math.ceil(image.height / stripCount)) & 0xFFFFF0;
612
613 this.copyStrip_(context, image, 0, step);
614};
615
616/**
617 * @param {CanvasRenderingContext2D} context Context to draw.
618 * @param {HTMLImageElement} image Image to draw.
619 * @param {number} firstRow Number of the first pixel row to draw.
620 * @param {number} rowCount Count of pixel rows to draw.
621 * @private
622 */
623ImageUtil.ImageLoader.prototype.copyStrip_ = function(
624 context, image, firstRow, rowCount) {
625 var lastRow = Math.min(firstRow + rowCount, image.height);
626
627 context.drawImage(
628 image, 0, firstRow, image.width, lastRow - firstRow,
629 -image.width / 2, firstRow - image.height / 2,
630 image.width, lastRow - firstRow);
631
632 if (lastRow == image.height) {
633 context.restore();
634 if (this.url_.substr(0, 5) != 'data:') { // Ignore data urls.
635 ImageUtil.metrics.recordInterval(ImageUtil.getMetricName('LoadTime'));
636 }
637 try {
638 setTimeout(this.callback_, 0, context.canvas);
639 } catch (e) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100640 console.error(e);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000641 }
642 this.callback_ = null;
643 } else {
644 var self = this;
645 this.timeout_ = setTimeout(
646 function() {
647 self.timeout_ = null;
648 self.copyStrip_(context, image, lastRow, rowCount);
649 }, 0);
650 }
651};
652
653/**
654 * @param {HTMLElement} element To remove children from.
655 */
656ImageUtil.removeChildren = function(element) {
657 element.textContent = '';
658};
659
660/**
661 * @param {string} url "filesystem:" URL.
662 * @return {string} File name.
663 */
664ImageUtil.getFullNameFromUrl = function(url) {
665 url = decodeURIComponent(url);
666 if (url.indexOf('/') != -1)
667 return url.substr(url.lastIndexOf('/') + 1);
668 else
669 return url;
670};
671
672/**
673 * @param {string} name File name (with extension).
674 * @return {string} File name without extension.
675 */
676ImageUtil.getFileNameFromFullName = function(name) {
677 var index = name.lastIndexOf('.');
678 if (index != -1)
679 return name.substr(0, index);
680 else
681 return name;
682};
683
684/**
685 * @param {string} url "filesystem:" URL.
686 * @return {string} File name.
687 */
688ImageUtil.getFileNameFromUrl = function(url) {
689 return ImageUtil.getFileNameFromFullName(ImageUtil.getFullNameFromUrl(url));
690};
691
692/**
693 * @param {string} fullName Original file name.
694 * @param {string} name New file name without extension.
695 * @return {string} New file name with base of |name| and extension of
696 * |fullName|.
697 */
698ImageUtil.replaceFileNameInFullName = function(fullName, name) {
699 var index = fullName.lastIndexOf('.');
700 if (index != -1)
701 return name + fullName.substr(index);
702 else
703 return name;
704};
705
706/**
707 * @param {string} name File name.
708 * @return {string} File extension.
709 */
710ImageUtil.getExtensionFromFullName = function(name) {
711 var index = name.lastIndexOf('.');
712 if (index != -1)
713 return name.substring(index);
714 else
715 return '';
716};
717
718/**
719 * Metrics (from metrics.js) itnitialized by the File Manager from owner frame.
720 * @type {Object?}
721 */
722ImageUtil.metrics = null;
723
724/**
725 * @param {string} name Local name.
726 * @return {string} Full name.
727 */
728ImageUtil.getMetricName = function(name) {
729 return 'PhotoEditor.' + name;
730};
731
732/**
733 * Used for metrics reporting, keep in sync with the histogram description.
734 */
735ImageUtil.FILE_TYPES = ['jpg', 'png', 'gif', 'bmp', 'webp'];