Chromium Code Reviews| 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 |