OLD | NEW |
| (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 // Namespace object for the utilities. | |
9 function ImageUtil() {} | |
10 | |
11 /** | |
12 * Performance trace. | |
13 */ | |
14 ImageUtil.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 */ | |
64 ImageUtil.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. | |
72 * @return {boolean} True if value is between. | |
73 */ | |
74 ImageUtil.between = function(min, value, max) { | |
75 return (value - min) * (value - max) <= 0; | |
76 }; | |
77 | |
78 /** | |
79 * Rectangle class. | |
80 */ | |
81 | |
82 /** | |
83 * Rectangle constructor takes 0, 1, 2 or 4 arguments. | |
84 * 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 */ | |
93 function 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 */ | |
146 Rect.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 */ | |
159 Rect.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 */ | |
168 Rect.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 */ | |
177 Rect.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. | |
185 * @return {boolean} True if the point lies inside the rectangle. | |
186 */ | |
187 Rect.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. | |
194 * @return {boolean} True if this rectangle intersects with the |rect|. | |
195 */ | |
196 Rect.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. | |
205 * @return {boolean} True if this rectangle containing the |rect|. | |
206 */ | |
207 Rect.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 */ | |
217 Rect.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. | |
225 * @return {Rect} Calculated rectangle. | |
226 */ | |
227 Rect.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 */ | |
256 Rect.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. | |
268 * @param {Rect=} opt_dstRect Rectangle in the canvas (whole canvas by default). | |
269 * @param {Rect=} opt_srcRect Rectangle in the image (whole image by default). | |
270 */ | |
271 Rect.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 */ | |
286 Rect.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 */ | |
296 Rect.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 */ | |
306 Rect.fillBetween = function(context, inner, outer) { | |
307 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; | |
311 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 } | |
319 if (inner.width < outerRight) { | |
320 context.fillRect( | |
321 innerRight, inner.top, outerRight - innerRight, inner.height); | |
322 } | |
323 if (inner.height < outerBottom) { | |
324 context.fillRect( | |
325 outer.left, innerBottom, outer.width, outerBottom - innerBottom); | |
326 } | |
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 */ | |
336 function 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 */ | |
348 Circle.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. | |
361 * @param {number} angle (in radians). | |
362 */ | |
363 ImageUtil.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 /** | |
374 * 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 */ | |
379 ImageUtil.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 */ | |
392 ImageUtil.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. | |
406 * | |
407 * @param {HTMLDocument} document Owner document. | |
408 * @param {MetadataCache=} opt_metadataCache Metadata cache. Required for | |
409 * caching. If not passed, caching will be disabled. | |
410 * @constructor | |
411 */ | |
412 ImageUtil.ImageLoader = function(document, opt_metadataCache) { | |
413 this.document_ = document; | |
414 this.metadataCache_ = opt_metadataCache || null; | |
415 this.image_ = new Image(); | |
416 this.generation_ = 0; | |
417 }; | |
418 | |
419 /** | |
420 * Max size of image to be displayed (in pixels) | |
421 */ | |
422 ImageUtil.ImageLoader.IMAGE_SIZE_LIMIT = 25 * 1000 * 1000; | |
423 | |
424 /** | |
425 * @param {number} width Width of the image. | |
426 * @param {number} height Height of the image. | |
427 * @return {boolean} True if the image is too large to be loaded. | |
428 */ | |
429 ImageUtil.ImageLoader.isTooLarge = function(width, height) { | |
430 return width * height > ImageUtil.ImageLoader.IMAGE_SIZE_LIMIT; | |
431 }; | |
432 | |
433 /** | |
434 * Loads an image. | |
435 * TODO(mtomasz): Simplify, or even get rid of this class and merge with the | |
436 * ThumbnaiLoader class. | |
437 * | |
438 * @param {string} url Image URL. | |
439 * @param {function(function(object))} transformFetcher function to get | |
440 * the image transform (which we need for the image orientation). | |
441 * @param {function(HTMLCanvasElement, string=)} callback Callback to be | |
442 * called when loaded. The second optional argument is an error identifier. | |
443 * @param {number=} opt_delay Load delay in milliseconds, useful to let the | |
444 * animations play out before the computation heavy image loading starts. | |
445 */ | |
446 ImageUtil.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 | |
462 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 | |
473 var loadImage = function(opt_metadata) { | |
474 ImageUtil.metrics.startInterval(ImageUtil.getMetricName('LoadTime')); | |
475 this.timeout_ = null; | |
476 | |
477 this.image_.onload = function(e) { | |
478 this.image_.onerror = null; | |
479 this.image_.onload = null; | |
480 if (ImageUtil.ImageLoader.isTooLarge(this.image_.width, | |
481 this.image_.height)) { | |
482 onError('IMAGE_TOO_BIG_ERROR'); | |
483 return; | |
484 } | |
485 transformFetcher(url, onTransform.bind(this, e.target)); | |
486 }.bind(this); | |
487 | |
488 // The error callback has an optional error argument, which in case of a | |
489 // general error should not be specified | |
490 this.image_.onerror = onError.bind(this, 'IMAGE_ERROR'); | |
491 | |
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 | |
498 // Load the image directly. | |
499 this.image_.src = url; | |
500 }.bind(this); | |
501 | |
502 // 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); | |
507 else | |
508 loadImage(); | |
509 }.bind(this), onError); | |
510 | |
511 if (opt_delay) { | |
512 this.timeout_ = setTimeout(startLoad, opt_delay); | |
513 } else { | |
514 startLoad(); | |
515 } | |
516 }; | |
517 | |
518 /** | |
519 * 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 */ | |
527 ImageUtil.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 /** | |
549 * @return {boolean} True if an image is loading. | |
550 */ | |
551 ImageUtil.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 */ | |
559 ImageUtil.ImageLoader.prototype.isLoading = function(url) { | |
560 return this.isBusy() && (this.url_ == url); | |
561 }; | |
562 | |
563 /** | |
564 * @param {function} callback To be called when the image loaded. | |
565 */ | |
566 ImageUtil.ImageLoader.prototype.setCallback = function(callback) { | |
567 this.callback_ = callback; | |
568 }; | |
569 | |
570 /** | |
571 * Stops loading image. | |
572 */ | |
573 ImageUtil.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() {}; | |
582 this.image_.onerror = function() {}; | |
583 this.image_.src = ''; | |
584 } | |
585 this.generation_++; // Silence the transform fetcher if it is in progress. | |
586 }; | |
587 | |
588 /** | |
589 * @param {HTMLImageElement} image Image to be transformed. | |
590 * @param {Object} transform transformation description to apply to the image. | |
591 * @private | |
592 */ | |
593 ImageUtil.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 */ | |
623 ImageUtil.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) { | |
640 console.error(e); | |
641 } | |
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 */ | |
656 ImageUtil.removeChildren = function(element) { | |
657 element.textContent = ''; | |
658 }; | |
659 | |
660 /** | |
661 * @param {string} url "filesystem:" URL. | |
662 * @return {string} File name. | |
663 */ | |
664 ImageUtil.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 */ | |
676 ImageUtil.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 */ | |
688 ImageUtil.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 */ | |
698 ImageUtil.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 */ | |
710 ImageUtil.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 */ | |
722 ImageUtil.metrics = null; | |
723 | |
724 /** | |
725 * @param {string} name Local name. | |
726 * @return {string} Full name. | |
727 */ | |
728 ImageUtil.getMetricName = function(name) { | |
729 return 'PhotoEditor.' + name; | |
730 }; | |
731 | |
732 /** | |
733 * Used for metrics reporting, keep in sync with the histogram description. | |
734 */ | |
735 ImageUtil.FILE_TYPES = ['jpg', 'png', 'gif', 'bmp', 'webp']; | |
OLD | NEW |