OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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 // Namespace object for the utilities. | 5 // Namespace object for the utilities. |
6 function ImageUtil() {} | 6 var ImageUtil = {}; |
7 | 7 |
8 /** | 8 /** |
9 * Performance trace. | 9 * Performance trace. |
10 */ | 10 */ |
11 ImageUtil.trace = (function() { | 11 ImageUtil.trace = (function() { |
12 /** | |
13 * Performance trace. | |
14 * @constructor | |
15 * @struct | |
16 */ | |
12 function PerformanceTrace() { | 17 function PerformanceTrace() { |
13 this.lines_ = {}; | 18 this.lines_ = {}; |
14 this.timers_ = {}; | 19 this.timers_ = {}; |
15 this.container_ = null; | 20 this.container_ = null; |
16 } | 21 } |
17 | 22 |
18 PerformanceTrace.prototype.bindToDOM = function(container) { | 23 PerformanceTrace.prototype.bindToDOM = function(container) { |
19 this.container_ = container; | 24 this.container_ = container; |
20 }; | 25 }; |
21 | 26 |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
67 * @param {number} value Value to check. | 72 * @param {number} value Value to check. |
68 * @param {number} max Maximum value. | 73 * @param {number} max Maximum value. |
69 * @return {boolean} True if value is between. | 74 * @return {boolean} True if value is between. |
70 */ | 75 */ |
71 ImageUtil.between = function(min, value, max) { | 76 ImageUtil.between = function(min, value, max) { |
72 return (value - min) * (value - max) <= 0; | 77 return (value - min) * (value - max) <= 0; |
73 }; | 78 }; |
74 | 79 |
75 /** | 80 /** |
76 * Rectangle class. | 81 * Rectangle class. |
82 * | |
83 * @param {number} left Left. | |
84 * @param {number} top Top. | |
85 * @param {number} width Width. | |
86 * @param {number} height Height. | |
87 * @constructor | |
88 * @struct | |
77 */ | 89 */ |
78 | 90 function ImageRect(left, top, width, height) { |
79 /** | 91 this.left = left; |
80 * Rectangle constructor takes 0, 1, 2 or 4 arguments. | 92 this.top = top; |
81 * Supports following variants: | 93 this.width = width; |
82 * new ImageRect(left, top, width, height) | 94 this.height = height; |
83 * new ImageRect(width, height) | |
84 * new ImageRect(rect) // anything with left, top, width, height. | |
85 * new ImageRect(bounds) // anything with left, top, right, bottom. | |
86 * new ImageRect(canvas|image) // anything with width and height. | |
87 * new ImageRect() // empty rectangle. | |
88 * @constructor | |
89 */ | |
90 function ImageRect() { | |
91 switch (arguments.length) { | |
92 case 4: | |
93 this.left = arguments[0]; | |
94 this.top = arguments[1]; | |
95 this.width = arguments[2]; | |
96 this.height = arguments[3]; | |
97 return; | |
98 | |
99 case 2: | |
100 this.left = 0; | |
101 this.top = 0; | |
102 this.width = arguments[0]; | |
103 this.height = arguments[1]; | |
104 return; | |
105 | |
106 case 1: { | |
107 var source = arguments[0]; | |
108 if ('left' in source && 'top' in source) { | |
109 this.left = source.left; | |
110 this.top = source.top; | |
111 if ('right' in source && 'bottom' in source) { | |
112 this.width = source.right - source.left; | |
113 this.height = source.bottom - source.top; | |
114 return; | |
115 } | |
116 } else { | |
117 this.left = 0; | |
118 this.top = 0; | |
119 } | |
120 if ('width' in source && 'height' in source) { | |
121 this.width = source.width; | |
122 this.height = source.height; | |
123 return; | |
124 } | |
125 break; // Fall through to the error message. | |
126 } | |
127 | |
128 case 0: | |
129 this.left = 0; | |
130 this.top = 0; | |
131 this.width = 0; | |
132 this.height = 0; | |
133 return; | |
134 } | |
135 console.error('Invalid ImageRect constructor arguments:', | |
136 Array.apply(null, arguments)); | |
137 } | 95 } |
138 | 96 |
139 /** | 97 /** |
140 * Creates an image rect with a canvas. | 98 * Creates an image rect with a canvas. |
141 * @param {!HTMLCanvasElement} canvas A canvas. | 99 * @param {!HTMLCanvasElement} canvas A canvas. |
142 * @return {!ImageRect} | 100 * @return {!ImageRect} |
101 * | |
102 * TODO(yawano): Since createFromImage accepts HTMLCanvasElement, delete this | |
103 * method later. | |
143 */ | 104 */ |
144 ImageRect.createFromCanvas = function(canvas) { | 105 ImageRect.createFromCanvas = function(canvas) { |
145 return new ImageRect(canvas); | 106 return ImageRect.createFromImage(canvas); |
146 }; | 107 }; |
147 | 108 |
148 /** | 109 /** |
110 * Creates an image rect with an image or a canvas. | |
111 * @param {!(HTMLImageElement|HTMLCanvasElement)} image An image or a canvas. | |
112 * @return {!ImageRect} | |
113 */ | |
114 ImageRect.createFromImage = function(image) { | |
115 return new ImageRect(0, 0, image.width, image.height); | |
116 }; | |
117 | |
118 /** | |
119 * Clone an image rect. | |
120 * @param {!ImageRect} imageRect An image rect. | |
121 * @return {!ImageRect} | |
122 */ | |
123 ImageRect.clone = function(imageRect) { | |
124 return new ImageRect(imageRect.left, imageRect.top, imageRect.width, | |
125 imageRect.height); | |
126 }; | |
127 | |
128 /** | |
149 * Creates an image rect with a bound. | 129 * Creates an image rect with a bound. |
150 * @param {{left: number, top: number, right: number, bottom: number}} bound | 130 * @param {{left: number, top: number, right: number, bottom: number}} bound |
151 * A bound. | 131 * A bound. |
152 * @return {!ImageRect} | 132 * @return {!ImageRect} |
153 */ | 133 */ |
154 ImageRect.createFromBounds = function(bound) { | 134 ImageRect.createFromBounds = function(bound) { |
155 return new ImageRect(bound); | 135 return new ImageRect(bound.left, bound.top, |
136 bound.right - bound.left, bound.bottom - bound.top); | |
156 }; | 137 }; |
157 | 138 |
158 /** | 139 /** |
159 * Creates an image rect with width and height. | 140 * Creates an image rect with width and height. |
160 * @param {number} width Width. | 141 * @param {number} width Width. |
161 * @param {number} height Height. | 142 * @param {number} height Height. |
162 * @return {!ImageRect} | 143 * @return {!ImageRect} |
163 */ | 144 */ |
164 ImageRect.createFromWidthAndHeight = function(width, height) { | 145 ImageRect.createFromWidthAndHeight = function(width, height) { |
165 return new ImageRect(width, height); | 146 return new ImageRect(0, 0, width, height); |
166 }; | 147 }; |
167 | 148 |
168 /** | 149 /** |
169 * Creates an image rect with left, top, width and height. | 150 * Creates an image rect with left, top, width and height. |
170 * @param {number} left Left. | 151 * @param {number} left Left. |
171 * @param {number} top Top. | 152 * @param {number} top Top. |
172 * @param {number} width Width. | 153 * @param {number} width Width. |
173 * @param {number} height Height. | 154 * @param {number} height Height. |
174 * @return {!ImageRect} | 155 * @return {!ImageRect} |
156 * | |
157 * TODO(yawano): Remove createWith calls and call constructor directly. | |
175 */ | 158 */ |
176 ImageRect.createWith = function(left, top, width, height) { | 159 ImageRect.createWith = function(left, top, width, height) { |
177 return new ImageRect(left, top, width, height); | 160 return new ImageRect(left, top, width, height); |
178 }; | 161 }; |
179 | 162 |
180 ImageRect.prototype = { | 163 ImageRect.prototype = /** @struct */ ({ |
164 // TODO(yawano): Change getters to methods (e.g. getRight()). | |
165 | |
181 /** | 166 /** |
182 * Obtains the x coordinate of right edge. The most right pixels in the | 167 * Obtains the x coordinate of right edge. The most right pixels in the |
183 * rectangle are (x = right - 1) and the pixels (x = right) are not included | 168 * rectangle are (x = right - 1) and the pixels (x = right) are not included |
184 * in the rectangle. | 169 * in the rectangle. |
185 * @return {number} | 170 * @return {number} |
186 */ | 171 */ |
187 get right() { | 172 get right() { |
188 return this.left + this.width; | 173 return this.left + this.width; |
189 }, | 174 }, |
190 | 175 |
191 /** | 176 /** |
192 * Obtains the y coordinate of bottom edge. The most bottom pixels in the | 177 * Obtains the y coordinate of bottom edge. The most bottom pixels in the |
193 * rectangle are (y = bottom - 1) and the pixels (y = bottom) are not included | 178 * rectangle are (y = bottom - 1) and the pixels (y = bottom) are not included |
194 * in the rectangle. | 179 * in the rectangle. |
195 * @return {number} | 180 * @return {number} |
196 */ | 181 */ |
197 get bottom() { | 182 get bottom() { |
198 return this.top + this.height; | 183 return this.top + this.height; |
199 } | 184 } |
200 }; | 185 }); |
201 | 186 |
202 /** | 187 /** |
203 * @param {number} factor Factor to scale. | 188 * @param {number} factor Factor to scale. |
204 * @return {ImageRect} A rectangle with every dimension scaled. | 189 * @return {!ImageRect} A rectangle with every dimension scaled. |
205 */ | 190 */ |
206 ImageRect.prototype.scale = function(factor) { | 191 ImageRect.prototype.scale = function(factor) { |
207 return new ImageRect( | 192 return new ImageRect( |
208 this.left * factor, | 193 this.left * factor, |
209 this.top * factor, | 194 this.top * factor, |
210 this.width * factor, | 195 this.width * factor, |
211 this.height * factor); | 196 this.height * factor); |
212 }; | 197 }; |
213 | 198 |
214 /** | 199 /** |
215 * @param {number} dx Difference in X. | 200 * @param {number} dx Difference in X. |
216 * @param {number} dy Difference in Y. | 201 * @param {number} dy Difference in Y. |
217 * @return {ImageRect} A rectangle shifted by (dx,dy), same size. | 202 * @return {!ImageRect} A rectangle shifted by (dx,dy), same size. |
218 */ | 203 */ |
219 ImageRect.prototype.shift = function(dx, dy) { | 204 ImageRect.prototype.shift = function(dx, dy) { |
220 return new ImageRect(this.left + dx, this.top + dy, this.width, this.height); | 205 return new ImageRect(this.left + dx, this.top + dy, this.width, this.height); |
221 }; | 206 }; |
222 | 207 |
223 /** | 208 /** |
224 * @param {number} x Coordinate of the left top corner. | 209 * @param {number} x Coordinate of the left top corner. |
225 * @param {number} y Coordinate of the left top corner. | 210 * @param {number} y Coordinate of the left top corner. |
226 * @return {ImageRect} A rectangle with left==x and top==y, same size. | 211 * @return {!ImageRect} A rectangle with left==x and top==y, same size. |
227 */ | 212 */ |
228 ImageRect.prototype.moveTo = function(x, y) { | 213 ImageRect.prototype.moveTo = function(x, y) { |
229 return new ImageRect(x, y, this.width, this.height); | 214 return new ImageRect(x, y, this.width, this.height); |
230 }; | 215 }; |
231 | 216 |
232 /** | 217 /** |
233 * @param {number} dx Difference in X. | 218 * @param {number} dx Difference in X. |
234 * @param {number} dy Difference in Y. | 219 * @param {number} dy Difference in Y. |
235 * @return {!ImageRect} A rectangle inflated by (dx, dy), same center. | 220 * @return {!ImageRect} A rectangle inflated by (dx, dy), same center. |
236 */ | 221 */ |
237 ImageRect.prototype.inflate = function(dx, dy) { | 222 ImageRect.prototype.inflate = function(dx, dy) { |
238 return new ImageRect( | 223 return new ImageRect( |
239 this.left - dx, this.top - dy, this.width + 2 * dx, this.height + 2 * dy); | 224 this.left - dx, this.top - dy, this.width + 2 * dx, this.height + 2 * dy); |
240 }; | 225 }; |
241 | 226 |
242 /** | 227 /** |
243 * @param {number} x Coordinate of the point. | 228 * @param {number} x Coordinate of the point. |
244 * @param {number} y Coordinate of the point. | 229 * @param {number} y Coordinate of the point. |
245 * @return {boolean} True if the point lies inside the rectangle. | 230 * @return {boolean} True if the point lies inside the rectangle. |
246 */ | 231 */ |
247 ImageRect.prototype.inside = function(x, y) { | 232 ImageRect.prototype.inside = function(x, y) { |
248 return this.left <= x && x < this.left + this.width && | 233 return this.left <= x && x < this.left + this.width && |
249 this.top <= y && y < this.top + this.height; | 234 this.top <= y && y < this.top + this.height; |
250 }; | 235 }; |
251 | 236 |
252 /** | 237 /** |
253 * @param {ImageRect} rect Rectangle to check. | 238 * @param {!ImageRect} rect Rectangle to check. |
254 * @return {boolean} True if this rectangle intersects with the |rect|. | 239 * @return {boolean} True if this rectangle intersects with the |rect|. |
255 */ | 240 */ |
256 ImageRect.prototype.intersects = function(rect) { | 241 ImageRect.prototype.intersects = function(rect) { |
257 return (this.left + this.width) > rect.left && | 242 return (this.left + this.width) > rect.left && |
258 (rect.left + rect.width) > this.left && | 243 (rect.left + rect.width) > this.left && |
259 (this.top + this.height) > rect.top && | 244 (this.top + this.height) > rect.top && |
260 (rect.top + rect.height) > this.top; | 245 (rect.top + rect.height) > this.top; |
261 }; | 246 }; |
262 | 247 |
263 /** | 248 /** |
264 * @param {ImageRect} rect Rectangle to check. | 249 * @param {!ImageRect} rect Rectangle to check. |
265 * @return {boolean} True if this rectangle containing the |rect|. | 250 * @return {boolean} True if this rectangle containing the |rect|. |
266 */ | 251 */ |
267 ImageRect.prototype.contains = function(rect) { | 252 ImageRect.prototype.contains = function(rect) { |
268 return (this.left <= rect.left) && | 253 return (this.left <= rect.left) && |
269 (rect.left + rect.width) <= (this.left + this.width) && | 254 (rect.left + rect.width) <= (this.left + this.width) && |
270 (this.top <= rect.top) && | 255 (this.top <= rect.top) && |
271 (rect.top + rect.height) <= (this.top + this.height); | 256 (rect.top + rect.height) <= (this.top + this.height); |
272 }; | 257 }; |
273 | 258 |
274 /** | 259 /** |
275 * @return {boolean} True if rectangle is empty. | 260 * @return {boolean} True if rectangle is empty. |
276 */ | 261 */ |
277 ImageRect.prototype.isEmpty = function() { | 262 ImageRect.prototype.isEmpty = function() { |
278 return this.width === 0 || this.height === 0; | 263 return this.width === 0 || this.height === 0; |
279 }; | 264 }; |
280 | 265 |
281 /** | 266 /** |
282 * Clamp the rectangle to the bounds by moving it. | 267 * Clamp the rectangle to the bounds by moving it. |
283 * Decrease the size only if necessary. | 268 * Decrease the size only if necessary. |
284 * @param {ImageRect} bounds Bounds. | 269 * @param {!ImageRect} bounds Bounds. |
285 * @return {ImageRect} Calculated rectangle. | 270 * @return {!ImageRect} Calculated rectangle. |
286 */ | 271 */ |
287 ImageRect.prototype.clamp = function(bounds) { | 272 ImageRect.prototype.clamp = function(bounds) { |
288 var rect = new ImageRect(this); | 273 var rect = ImageRect.clone(this); |
289 | 274 |
290 if (rect.width > bounds.width) { | 275 if (rect.width > bounds.width) { |
291 rect.left = bounds.left; | 276 rect.left = bounds.left; |
292 rect.width = bounds.width; | 277 rect.width = bounds.width; |
293 } else if (rect.left < bounds.left) { | 278 } else if (rect.left < bounds.left) { |
294 rect.left = bounds.left; | 279 rect.left = bounds.left; |
295 } else if (rect.left + rect.width > | 280 } else if (rect.left + rect.width > |
296 bounds.left + bounds.width) { | 281 bounds.left + bounds.width) { |
297 rect.left = bounds.left + bounds.width - rect.width; | 282 rect.left = bounds.left + bounds.width - rect.width; |
298 } | 283 } |
(...skipping 17 matching lines...) Expand all Loading... | |
316 ImageRect.prototype.toString = function() { | 301 ImageRect.prototype.toString = function() { |
317 return '(' + this.left + ',' + this.top + '):' + | 302 return '(' + this.left + ',' + this.top + '):' + |
318 '(' + (this.left + this.width) + ',' + (this.top + this.height) + ')'; | 303 '(' + (this.left + this.width) + ',' + (this.top + this.height) + ')'; |
319 }; | 304 }; |
320 /* | 305 /* |
321 * Useful shortcuts for drawing (static functions). | 306 * Useful shortcuts for drawing (static functions). |
322 */ | 307 */ |
323 | 308 |
324 /** | 309 /** |
325 * Draw the image in context with appropriate scaling. | 310 * Draw the image in context with appropriate scaling. |
326 * @param {CanvasRenderingContext2D} context Context to draw. | 311 * @param {!CanvasRenderingContext2D} context Context to draw. |
327 * @param {!(HTMLCanvasElement|HTMLImageElement)} image Image to draw. | 312 * @param {!(HTMLCanvasElement|HTMLImageElement)} image Image to draw. |
328 * @param {ImageRect=} opt_dstRect Rectangle in the canvas (whole canvas by | 313 * @param {ImageRect=} opt_dstRect Rectangle in the canvas (whole canvas by |
329 * default). | 314 * default). |
330 * @param {ImageRect=} opt_srcRect Rectangle in the image (whole image by | 315 * @param {ImageRect=} opt_srcRect Rectangle in the image (whole image by |
331 * default). | 316 * default). |
332 */ | 317 */ |
333 ImageRect.drawImage = function(context, image, opt_dstRect, opt_srcRect) { | 318 ImageRect.drawImage = function(context, image, opt_dstRect, opt_srcRect) { |
334 opt_dstRect = opt_dstRect || new ImageRect(context.canvas); | 319 opt_dstRect = opt_dstRect || |
335 opt_srcRect = opt_srcRect || new ImageRect(image); | 320 ImageRect.createFromImage(assert(context.canvas)); |
321 opt_srcRect = opt_srcRect || ImageRect.createFromImage(image); | |
336 if (opt_dstRect.isEmpty() || opt_srcRect.isEmpty()) | 322 if (opt_dstRect.isEmpty() || opt_srcRect.isEmpty()) |
337 return; | 323 return; |
338 context.drawImage(image, | 324 context.drawImage(image, |
339 opt_srcRect.left, opt_srcRect.top, opt_srcRect.width, opt_srcRect.height, | 325 opt_srcRect.left, opt_srcRect.top, opt_srcRect.width, opt_srcRect.height, |
340 opt_dstRect.left, opt_dstRect.top, opt_dstRect.width, opt_dstRect.height); | 326 opt_dstRect.left, opt_dstRect.top, opt_dstRect.width, opt_dstRect.height); |
341 }; | 327 }; |
342 | 328 |
343 /** | 329 /** |
344 * Draw a box around the rectangle. | 330 * Draw a box around the rectangle. |
345 * @param {CanvasRenderingContext2D} context Context to draw. | 331 * @param {!CanvasRenderingContext2D} context Context to draw. |
346 * @param {ImageRect} rect Rectangle. | 332 * @param {!ImageRect} rect Rectangle. |
347 */ | 333 */ |
348 ImageRect.outline = function(context, rect) { | 334 ImageRect.outline = function(context, rect) { |
349 context.strokeRect( | 335 context.strokeRect( |
350 rect.left - 0.5, rect.top - 0.5, rect.width + 1, rect.height + 1); | 336 rect.left - 0.5, rect.top - 0.5, rect.width + 1, rect.height + 1); |
351 }; | 337 }; |
352 | 338 |
353 /** | 339 /** |
354 * Fill the rectangle. | 340 * Fill the rectangle. |
355 * @param {CanvasRenderingContext2D} context Context to draw. | 341 * @param {!CanvasRenderingContext2D} context Context to draw. |
356 * @param {ImageRect} rect Rectangle. | 342 * @param {!ImageRect} rect Rectangle. |
357 */ | 343 */ |
358 ImageRect.fill = function(context, rect) { | 344 ImageRect.fill = function(context, rect) { |
359 context.fillRect(rect.left, rect.top, rect.width, rect.height); | 345 context.fillRect(rect.left, rect.top, rect.width, rect.height); |
360 }; | 346 }; |
361 | 347 |
362 /** | 348 /** |
363 * Fills the space between the two rectangles. | 349 * Fills the space between the two rectangles. |
364 * @param {CanvasRenderingContext2D} context Context to draw. | 350 * @param {!CanvasRenderingContext2D} context Context to draw. |
365 * @param {ImageRect} inner Inner rectangle. | 351 * @param {!ImageRect} inner Inner rectangle. |
366 * @param {ImageRect} outer Outer rectangle. | 352 * @param {!ImageRect} outer Outer rectangle. |
367 */ | 353 */ |
368 ImageRect.fillBetween = function(context, inner, outer) { | 354 ImageRect.fillBetween = function(context, inner, outer) { |
369 var innerRight = inner.left + inner.width; | 355 var innerRight = inner.left + inner.width; |
370 var innerBottom = inner.top + inner.height; | 356 var innerBottom = inner.top + inner.height; |
371 var outerRight = outer.left + outer.width; | 357 var outerRight = outer.left + outer.width; |
372 var outerBottom = outer.top + outer.height; | 358 var outerBottom = outer.top + outer.height; |
373 if (inner.top > outer.top) { | 359 if (inner.top > outer.top) { |
374 context.fillRect( | 360 context.fillRect( |
375 outer.left, outer.top, outer.width, inner.top - outer.top); | 361 outer.left, outer.top, outer.width, inner.top - outer.top); |
376 } | 362 } |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
409 */ | 395 */ |
410 Circle.prototype.inside = function(x, y) { | 396 Circle.prototype.inside = function(x, y) { |
411 x -= this.x; | 397 x -= this.x; |
412 y -= this.y; | 398 y -= this.y; |
413 return x * x + y * y <= this.squaredR; | 399 return x * x + y * y <= this.squaredR; |
414 }; | 400 }; |
415 | 401 |
416 /** | 402 /** |
417 * Copy an image applying scaling and rotation. | 403 * Copy an image applying scaling and rotation. |
418 * | 404 * |
419 * @param {HTMLCanvasElement} dst Destination. | 405 * @param {!HTMLCanvasElement} dst Destination. |
420 * @param {HTMLCanvasElement|HTMLImageElement} src Source. | 406 * @param {!(HTMLCanvasElement|HTMLImageElement)} src Source. |
421 * @param {number} scaleX Y scale transformation. | 407 * @param {number} scaleX Y scale transformation. |
422 * @param {number} scaleY X scale transformation. | 408 * @param {number} scaleY X scale transformation. |
423 * @param {number} angle (in radians). | 409 * @param {number} angle (in radians). |
424 */ | 410 */ |
425 ImageUtil.drawImageTransformed = function(dst, src, scaleX, scaleY, angle) { | 411 ImageUtil.drawImageTransformed = function(dst, src, scaleX, scaleY, angle) { |
426 var context = dst.getContext('2d'); | 412 var context = dst.getContext('2d'); |
427 context.save(); | 413 context.save(); |
428 context.translate(context.canvas.width / 2, context.canvas.height / 2); | 414 context.translate(context.canvas.width / 2, context.canvas.height / 2); |
429 context.rotate(angle); | 415 context.rotate(angle); |
430 context.scale(scaleX, scaleY); | 416 context.scale(scaleX, scaleY); |
431 context.drawImage(src, -src.width / 2, -src.height / 2); | 417 context.drawImage(src, -src.width / 2, -src.height / 2); |
432 context.restore(); | 418 context.restore(); |
433 }; | 419 }; |
434 | 420 |
435 /** | 421 /** |
436 * Adds or removes an attribute to/from an HTML element. | 422 * Adds or removes an attribute to/from an HTML element. |
437 * @param {HTMLElement} element To be applied to. | 423 * @param {!HTMLElement} element To be applied to. |
438 * @param {string} attribute Name of attribute. | 424 * @param {string} attribute Name of attribute. |
439 * @param {boolean} on True if add, false if remove. | 425 * @param {boolean} on True if add, false if remove. |
440 */ | 426 */ |
441 ImageUtil.setAttribute = function(element, attribute, on) { | 427 ImageUtil.setAttribute = function(element, attribute, on) { |
442 if (on) | 428 if (on) |
443 element.setAttribute(attribute, ''); | 429 element.setAttribute(attribute, ''); |
444 else | 430 else |
445 element.removeAttribute(attribute); | 431 element.removeAttribute(attribute); |
446 }; | 432 }; |
447 | 433 |
448 /** | 434 /** |
449 * Adds or removes CSS class to/from an HTML element. | 435 * Adds or removes CSS class to/from an HTML element. |
450 * @param {HTMLElement} element To be applied to. | 436 * @param {!HTMLElement} element To be applied to. |
451 * @param {string} className Name of CSS class. | 437 * @param {string} className Name of CSS class. |
452 * @param {boolean} on True if add, false if remove. | 438 * @param {boolean} on True if add, false if remove. |
453 */ | 439 */ |
454 ImageUtil.setClass = function(element, className, on) { | 440 ImageUtil.setClass = function(element, className, on) { |
455 var cl = element.classList; | 441 var cl = element.classList; |
456 if (on) | 442 if (on) |
457 cl.add(className); | 443 cl.add(className); |
458 else | 444 else |
459 cl.remove(className); | 445 cl.remove(className); |
460 }; | 446 }; |
461 | 447 |
462 /** | 448 /** |
463 * ImageLoader loads an image from a given Entry into a canvas in two steps: | 449 * ImageLoader loads an image from a given Entry into a canvas in two steps: |
464 * 1. Loads the image into an HTMLImageElement. | 450 * 1. Loads the image into an HTMLImageElement. |
465 * 2. Copies pixels from HTMLImageElement to HTMLCanvasElement. This is done | 451 * 2. Copies pixels from HTMLImageElement to HTMLCanvasElement. This is done |
466 * stripe-by-stripe to avoid freezing up the UI. The transform is taken into | 452 * stripe-by-stripe to avoid freezing up the UI. The transform is taken into |
467 * account. | 453 * account. |
468 * | 454 * |
469 * @param {HTMLDocument} document Owner document. | 455 * @param {!HTMLDocument} document Owner document. |
470 * @constructor | 456 * @constructor |
457 * @struct | |
471 */ | 458 */ |
472 ImageUtil.ImageLoader = function(document) { | 459 ImageUtil.ImageLoader = function(document) { |
473 this.document_ = document; | 460 this.document_ = document; |
474 this.image_ = new Image(); | 461 this.image_ = new Image(); |
475 this.generation_ = 0; | 462 this.generation_ = 0; |
463 | |
464 /** | |
465 * @type {number} | |
466 * @private | |
467 */ | |
468 this.timeout_ = 0; | |
469 | |
470 /** | |
471 * @type {?function(!HTMLCanvasElement, string=)} | |
472 * @private | |
473 */ | |
474 this.callback_ = null; | |
475 | |
476 /** | |
477 * @type {FileEntry} | |
478 * @private | |
479 */ | |
480 this.entry_ = null; | |
476 }; | 481 }; |
477 | 482 |
478 /** | 483 /** |
479 * Loads an image. | 484 * Loads an image. |
480 * TODO(mtomasz): Simplify, or even get rid of this class and merge with the | 485 * TODO(mtomasz): Simplify, or even get rid of this class and merge with the |
481 * ThumbnaiLoader class. | 486 * ThumbnaiLoader class. |
482 * | 487 * |
483 * @param {Gallery.Item} item Item representing the image to be loaded. | 488 * @param {!Gallery.Item} item Item representing the image to be loaded. |
484 * @param {function(!HTMLCanvasElement, string=)} callback Callback to be | 489 * @param {function(!HTMLCanvasElement, string=)} callback Callback to be |
485 * called when loaded. The second optional argument is an error identifier. | 490 * called when loaded. The second optional argument is an error identifier. |
486 * @param {number=} opt_delay Load delay in milliseconds, useful to let the | 491 * @param {number=} opt_delay Load delay in milliseconds, useful to let the |
487 * animations play out before the computation heavy image loading starts. | 492 * animations play out before the computation heavy image loading starts. |
488 */ | 493 */ |
489 ImageUtil.ImageLoader.prototype.load = function(item, callback, opt_delay) { | 494 ImageUtil.ImageLoader.prototype.load = function(item, callback, opt_delay) { |
490 var entry = item.getEntry(); | 495 var entry = item.getEntry(); |
491 | 496 |
492 this.cancel(); | 497 this.cancel(); |
493 this.entry_ = entry; | 498 this.entry_ = entry; |
494 this.callback_ = callback; | 499 this.callback_ = callback; |
495 | 500 |
496 // The transform fetcher is not cancellable so we need a generation counter. | 501 // The transform fetcher is not cancellable so we need a generation counter. |
497 var generation = ++this.generation_; | 502 var generation = ++this.generation_; |
498 var onTransform = function(image, transform) { | 503 |
504 /** | |
505 * @param {!HTMLImageElement} image Image to be transformed. | |
506 * @param {Object=} opt_transform Transformation. | |
507 */ | |
508 var onTransform = function(image, opt_transform) { | |
499 if (generation === this.generation_) { | 509 if (generation === this.generation_) { |
500 this.convertImage_( | 510 this.convertImage_( |
501 image, transform || { scaleX: 1, scaleY: 1, rotate90: 0}); | 511 image, opt_transform || { scaleX: 1, scaleY: 1, rotate90: 0}); |
502 } | 512 } |
503 }.bind(this); | 513 }; |
514 onTransform = onTransform.bind(this); | |
504 | 515 |
516 /** | |
517 * @param {string=} opt_error Error. | |
518 */ | |
505 var onError = function(opt_error) { | 519 var onError = function(opt_error) { |
506 this.image_.onerror = null; | 520 this.image_.onerror = null; |
507 this.image_.onload = null; | 521 this.image_.onload = null; |
508 var tmpCallback = this.callback_; | 522 var tmpCallback = this.callback_; |
509 this.callback_ = null; | 523 this.callback_ = null; |
510 var emptyCanvas = this.document_.createElement('canvas'); | 524 var emptyCanvas = assertInstanceof(this.document_.createElement('canvas'), |
525 HTMLCanvasElement); | |
511 emptyCanvas.width = 0; | 526 emptyCanvas.width = 0; |
512 emptyCanvas.height = 0; | 527 emptyCanvas.height = 0; |
513 tmpCallback(emptyCanvas, opt_error); | 528 tmpCallback(emptyCanvas, opt_error); |
514 }.bind(this); | 529 }; |
530 onError = onError.bind(this); | |
515 | 531 |
516 var loadImage = function() { | 532 var loadImage = function() { |
517 ImageUtil.metrics.startInterval(ImageUtil.getMetricName('LoadTime')); | 533 ImageUtil.metrics.startInterval(ImageUtil.getMetricName('LoadTime')); |
518 this.timeout_ = null; | 534 this.timeout_ = null; |
519 | 535 |
520 this.image_.onload = function() { | 536 this.image_.onload = function() { |
521 this.image_.onerror = null; | 537 this.image_.onerror = null; |
522 this.image_.onload = null; | 538 this.image_.onload = null; |
523 item.getFetchedMedia().then(function(fetchedMediaMetadata) { | 539 item.getFetchedMedia().then(function(fetchedMediaMetadata) { |
524 onTransform(this.image_, fetchedMediaMetadata.imageTransform); | 540 if (fetchedMediaMetadata.imageTransform) |
fukino
2014/12/12 02:21:03
Do we need this if statement? That is, did onTrans
yawano
2014/12/12 03:17:45
If we write it as onTransform(this.image_, fetched
fukino
2014/12/12 05:03:44
Got it.
| |
541 onTransform(this.image_, fetchedMediaMetadata.imageTransform); | |
542 else | |
543 onTransform(this.image_); | |
525 }.bind(this)).catch(function(error) { | 544 }.bind(this)).catch(function(error) { |
526 console.error(error.stack || error); | 545 console.error(error.stack || error); |
527 }); | 546 }); |
528 }.bind(this); | 547 }.bind(this); |
529 | 548 |
530 // The error callback has an optional error argument, which in case of a | 549 // The error callback has an optional error argument, which in case of a |
531 // general error should not be specified | 550 // general error should not be specified |
532 this.image_.onerror = onError.bind(this, 'GALLERY_IMAGE_ERROR'); | 551 this.image_.onerror = onError.bind(this, 'GALLERY_IMAGE_ERROR'); |
533 | 552 |
534 // Load the image directly. The query parameter is workaround for | 553 // Load the image directly. The query parameter is workaround for |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
602 }; | 621 }; |
603 | 622 |
604 /** | 623 /** |
605 * Stops loading image. | 624 * Stops loading image. |
606 */ | 625 */ |
607 ImageUtil.ImageLoader.prototype.cancel = function() { | 626 ImageUtil.ImageLoader.prototype.cancel = function() { |
608 if (!this.callback_) return; | 627 if (!this.callback_) return; |
609 this.callback_ = null; | 628 this.callback_ = null; |
610 if (this.timeout_) { | 629 if (this.timeout_) { |
611 clearTimeout(this.timeout_); | 630 clearTimeout(this.timeout_); |
612 this.timeout_ = null; | 631 this.timeout_ = 0; |
613 } | 632 } |
614 if (this.image_) { | 633 if (this.image_) { |
615 this.image_.onload = function() {}; | 634 this.image_.onload = function() {}; |
616 this.image_.onerror = function() {}; | 635 this.image_.onerror = function() {}; |
617 this.image_.src = ''; | 636 this.image_.src = ''; |
618 } | 637 } |
619 this.generation_++; // Silence the transform fetcher if it is in progress. | 638 this.generation_++; // Silence the transform fetcher if it is in progress. |
620 }; | 639 }; |
621 | 640 |
622 /** | 641 /** |
623 * @param {HTMLImageElement} image Image to be transformed. | 642 * @param {!HTMLImageElement} image Image to be transformed. |
624 * @param {Object} transform transformation description to apply to the image. | 643 * @param {!Object} transform transformation description to apply to the image. |
625 * @private | 644 * @private |
626 */ | 645 */ |
627 ImageUtil.ImageLoader.prototype.convertImage_ = function(image, transform) { | 646 ImageUtil.ImageLoader.prototype.convertImage_ = function(image, transform) { |
628 var canvas = this.document_.createElement('canvas'); | 647 var canvas = this.document_.createElement('canvas'); |
629 | 648 |
630 if (transform.rotate90 & 1) { // Rotated +/-90deg, swap the dimensions. | 649 if (transform.rotate90 & 1) { // Rotated +/-90deg, swap the dimensions. |
631 canvas.width = image.height; | 650 canvas.width = image.height; |
632 canvas.height = image.width; | 651 canvas.height = image.width; |
633 } else { | 652 } else { |
634 canvas.width = image.width; | 653 canvas.width = image.width; |
635 canvas.height = image.height; | 654 canvas.height = image.height; |
636 } | 655 } |
637 | 656 |
638 var context = canvas.getContext('2d'); | 657 var context = canvas.getContext('2d'); |
639 context.save(); | 658 context.save(); |
640 context.translate(canvas.width / 2, canvas.height / 2); | 659 context.translate(canvas.width / 2, canvas.height / 2); |
641 context.rotate(transform.rotate90 * Math.PI / 2); | 660 context.rotate(transform.rotate90 * Math.PI / 2); |
642 context.scale(transform.scaleX, transform.scaleY); | 661 context.scale(transform.scaleX, transform.scaleY); |
643 | 662 |
644 var stripCount = Math.ceil(image.width * image.height / (1 << 21)); | 663 var stripCount = Math.ceil(image.width * image.height / (1 << 21)); |
645 var step = Math.max(16, Math.ceil(image.height / stripCount)) & 0xFFFFF0; | 664 var step = Math.max(16, Math.ceil(image.height / stripCount)) & 0xFFFFF0; |
646 | 665 |
647 this.copyStrip_(context, image, 0, step); | 666 this.copyStrip_(context, image, 0, step); |
648 }; | 667 }; |
649 | 668 |
650 /** | 669 /** |
651 * @param {CanvasRenderingContext2D} context Context to draw. | 670 * @param {!CanvasRenderingContext2D} context Context to draw. |
652 * @param {HTMLImageElement} image Image to draw. | 671 * @param {!HTMLImageElement} image Image to draw. |
653 * @param {number} firstRow Number of the first pixel row to draw. | 672 * @param {number} firstRow Number of the first pixel row to draw. |
654 * @param {number} rowCount Count of pixel rows to draw. | 673 * @param {number} rowCount Count of pixel rows to draw. |
655 * @private | 674 * @private |
656 */ | 675 */ |
657 ImageUtil.ImageLoader.prototype.copyStrip_ = function( | 676 ImageUtil.ImageLoader.prototype.copyStrip_ = function( |
658 context, image, firstRow, rowCount) { | 677 context, image, firstRow, rowCount) { |
659 var lastRow = Math.min(firstRow + rowCount, image.height); | 678 var lastRow = Math.min(firstRow + rowCount, image.height); |
660 | 679 |
661 context.drawImage( | 680 context.drawImage( |
662 image, 0, firstRow, image.width, lastRow - firstRow, | 681 image, 0, firstRow, image.width, lastRow - firstRow, |
663 -image.width / 2, firstRow - image.height / 2, | 682 -image.width / 2, firstRow - image.height / 2, |
664 image.width, lastRow - firstRow); | 683 image.width, lastRow - firstRow); |
665 | 684 |
666 if (lastRow === image.height) { | 685 if (lastRow === image.height) { |
667 context.restore(); | 686 context.restore(); |
668 if (this.entry_.toURL().substr(0, 5) !== 'data:') { // Ignore data urls. | 687 if (this.entry_.toURL().substr(0, 5) !== 'data:') { // Ignore data urls. |
669 ImageUtil.metrics.recordInterval(ImageUtil.getMetricName('LoadTime')); | 688 ImageUtil.metrics.recordInterval(ImageUtil.getMetricName('LoadTime')); |
670 } | 689 } |
671 try { | 690 try { |
672 setTimeout(this.callback_, 0, context.canvas); | 691 setTimeout(this.callback_, 0, context.canvas); |
673 } catch (e) { | 692 } catch (e) { |
674 console.error(e); | 693 console.error(e); |
675 } | 694 } |
676 this.callback_ = null; | 695 this.callback_ = null; |
677 } else { | 696 } else { |
678 var self = this; | 697 var self = this; |
679 this.timeout_ = setTimeout( | 698 this.timeout_ = setTimeout( |
680 function() { | 699 function() { |
681 self.timeout_ = null; | 700 self.timeout_ = 0; |
682 self.copyStrip_(context, image, lastRow, rowCount); | 701 self.copyStrip_(context, image, lastRow, rowCount); |
683 }, 0); | 702 }, 0); |
684 } | 703 } |
685 }; | 704 }; |
686 | 705 |
687 /** | 706 /** |
688 * @param {HTMLElement} element To remove children from. | 707 * @param {!HTMLElement} element To remove children from. |
689 */ | 708 */ |
690 ImageUtil.removeChildren = function(element) { | 709 ImageUtil.removeChildren = function(element) { |
691 element.textContent = ''; | 710 element.textContent = ''; |
692 }; | 711 }; |
693 | 712 |
694 /** | 713 /** |
695 * @param {string} name File name (with extension). | 714 * @param {string} name File name (with extension). |
696 * @return {string} File name without extension. | 715 * @return {string} File name without extension. |
697 */ | 716 */ |
698 ImageUtil.getDisplayNameFromName = function(name) { | 717 ImageUtil.getDisplayNameFromName = function(name) { |
(...skipping 11 matching lines...) Expand all Loading... | |
710 ImageUtil.getExtensionFromFullName = function(name) { | 729 ImageUtil.getExtensionFromFullName = function(name) { |
711 var index = name.lastIndexOf('.'); | 730 var index = name.lastIndexOf('.'); |
712 if (index !== -1) | 731 if (index !== -1) |
713 return name.substring(index); | 732 return name.substring(index); |
714 else | 733 else |
715 return ''; | 734 return ''; |
716 }; | 735 }; |
717 | 736 |
718 /** | 737 /** |
719 * Metrics (from metrics.js) itnitialized by the File Manager from owner frame. | 738 * Metrics (from metrics.js) itnitialized by the File Manager from owner frame. |
720 * @type {Object?} | 739 * @type {Object} |
721 */ | 740 */ |
722 ImageUtil.metrics = null; | 741 ImageUtil.metrics = null; |
723 | 742 |
724 /** | 743 /** |
725 * @param {string} name Local name. | 744 * @param {string} name Local name. |
726 * @return {string} Full name. | 745 * @return {string} Full name. |
727 */ | 746 */ |
728 ImageUtil.getMetricName = function(name) { | 747 ImageUtil.getMetricName = function(name) { |
729 return 'PhotoEditor.' + name; | 748 return 'PhotoEditor.' + name; |
730 }; | 749 }; |
731 | 750 |
732 /** | 751 /** |
733 * Used for metrics reporting, keep in sync with the histogram description. | 752 * Used for metrics reporting, keep in sync with the histogram description. |
753 * @type {Array.<string>} | |
754 * @const | |
734 */ | 755 */ |
735 ImageUtil.FILE_TYPES = ['jpg', 'png', 'gif', 'bmp', 'webp']; | 756 ImageUtil.FILE_TYPES = ['jpg', 'png', 'gif', 'bmp', 'webp']; |
OLD | NEW |