1 /** 2 * The MIT License (MIT) 3 * 4 * Copyright (c) 2016 DeNA Co., Ltd. 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to deal 8 * in the Software without restriction, including without limitation the rights 9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 * copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in 14 * all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 * SOFTWARE. 23 */ 24 25 /// <reference path="base.js"/> 26 /// <reference path="display_object.js"/> 27 /// <reference path="rectangle.js"/> 28 /// <reference path="composer.js"/> 29 /// <reference path="config.js"/> 30 /// <reference path="image_factory.js"/> 31 32 /** 33 * A class that encapsulates an <img> element or a <canvas> element into a 34 * CreateJS object. This class should not be used for rendering <video> 35 * elements. Mobile Safari (<= iOS 9) cannot play <video> elements inline and it 36 * needs workarounds to render <video> elements inline on Mobile Safari. The 37 * createjs.Video class implements these workarounds needed for encapsulating a 38 * <video> element into a CreateJS object. 39 * @extends {createjs.DisplayObject} 40 * @implements {EventListener} 41 * @constructor 42 */ 43 createjs.Bitmap = function(value) { 44 /// <signature> 45 /// <param type="HTMLImageElement" name="image"/> 46 /// </signature> 47 /// <signature> 48 /// <param type="HTMLCanvasElement" name="canvas"/> 49 /// </signature> 50 /// <signature> 51 /// <param type="string" name="source"/> 52 /// </signature> 53 createjs.DisplayObject.call(this); 54 55 // Initialize this object only if its parameter is non-null. Flash-generated 56 // classes use 'derived.prototype = new createjs.Bitmap' to copy the methods 57 // of this class and its properties. 58 if (value != null) { 59 var image = null; 60 var width = 0; 61 var height = 0; 62 if (createjs.isString(value)) { 63 var path = createjs.getString(value); 64 // Retrieve an image from the ImageFactory object and copy its size only 65 // if it is loaded. 66 image = createjs.ImageFactory.get( 67 path, path, this, createjs.DEFAULT_TEXTURE); 68 if (image.complete) { 69 width = image.width; 70 height = image.height; 71 } 72 } else { 73 image = /** @type {HTMLImageElement} */ (value); 74 width = image.width; 75 height = image.height; 76 } 77 78 /** 79 * The image to be rendered. (This variable may be an HTMLCanvasElement 80 * object or an HTMLVideoElement.) 81 * @type {HTMLImageElement} 82 * @private 83 */ 84 this.image_ = image; 85 86 /** 87 * A renderer can draw the image attached to this bitmap. (The input image 88 * may be an HTMLCanvasElement object and it cannot use the 'complete' 89 * property.) 90 * @type {boolean} 91 * @private 92 */ 93 this.ready_ = !!width; 94 95 /** 96 * The offset position of this image. This position is used for aligning 97 * this image to the center of its nominal bounds. 98 * @type {createjs.Point} 99 * @private 100 */ 101 this.offset_ = new createjs.Point(0, 0); 102 103 if (!this.source_) { 104 /** 105 * The source rectangle of this image. 106 * @type {createjs.Rectangle} 107 * @private 108 */ 109 this.source_ = new createjs.Rectangle(0, 0, width, height); 110 } 111 112 // Layout this image to the center of the nominal bounds. 113 width = this.source_.width; 114 height = this.source_.height; 115 var nominalBounds = 116 /** @type {createjs.Rectangle} */ (this['nominalBounds']); 117 if (nominalBounds) { 118 // Align this image to the center of its nominal bounds. 119 var offsetX = nominalBounds.width - width; 120 if (offsetX > 0) { 121 this.offset_.x = offsetX >> 1; 122 } 123 var offsetY = nominalBounds.height - height; 124 if (offsetY > 0) { 125 this.offset_.y = offsetY >> 1; 126 } 127 } 128 // Synchronize the image size with the bounding-box size of this object. 129 this.setBoundingBox(0, 0, width, height); 130 } 131 }; 132 createjs.inherits('Bitmap', createjs.Bitmap, createjs.DisplayObject); 133 134 /** 135 * The <canvas> element used for hit-testing. 136 * @type {HTMLCanvasElement} 137 * @private 138 */ 139 createjs.Bitmap.hitTestCanvas_ = null; 140 141 /** 142 * The rendering context of the above <canvas> element, i.e. the rendering 143 * context used for hit-testing. 144 * @type {CanvasRenderingContext2D} 145 * @private 146 */ 147 createjs.Bitmap.hitTestContext_ = null; 148 149 /** 150 * An image composer used for applying a color filter to this image. 151 * @type {createjs.Composer} 152 * @private 153 */ 154 createjs.Bitmap.prototype.composer_ = null; 155 156 /** 157 * The renderer that draws this object. 158 * @type {createjs.Renderer} 159 * @private 160 */ 161 createjs.Bitmap.prototype.output_ = null; 162 163 /** 164 * The position where this bitmap was hit-tested last time. 165 * @type {createjs.Point} 166 * @private 167 */ 168 createjs.Bitmap.prototype.hitTestPoint_ = null; 169 170 /** 171 * The result of the last hit-testing. 172 * @type {createjs.DisplayObject} 173 * @private 174 */ 175 createjs.Bitmap.prototype.hitTestResult_ = null; 176 177 /** 178 * Returns the composer which applies filters to this bitmap. 179 * @return {createjs.Composer} 180 * @private 181 */ 182 createjs.Bitmap.prototype.getComposer_ = function() { 183 /// <returns type="createjs.Composer"/> 184 if (!this.composer_) { 185 this.composer_ = new createjs.Composer(); 186 } 187 return this.composer_; 188 }; 189 190 /** 191 * Retrieves the image of this bitmap used by the specified renderer. 192 * @return {HTMLImageElement|HTMLCanvasElement} 193 * @private 194 */ 195 createjs.Bitmap.prototype.getSourceImage_ = function() { 196 /// <returns type="HTMLImageElement"/> 197 if (this.composer_) { 198 return this.composer_.getOutput(); 199 } 200 return this.image_; 201 }; 202 203 /** 204 * Returns the HTMLImageElement object associated with this bitmap. 205 * @return {HTMLImageElement} 206 */ 207 createjs.Bitmap.prototype.getImage = function() { 208 /// <returns type="HTMLImageElement"/> 209 return this.image_; 210 }; 211 212 /** 213 * Changes the HTMLImageElement object associated with this bitmap. 214 * @param {HTMLImageElement} image 215 */ 216 createjs.Bitmap.prototype.setImage = function(image) { 217 /// <param type="HTMLImageElement" name="image"/> 218 this.handleDetach(); 219 this.image_ = image; 220 this.ready_ = !!(image && image.width); 221 this.handleAttach(1); 222 }; 223 224 /** 225 * Returns the source rectangle of this bitmap. 226 * @return {createjs.Rectangle} 227 * @const 228 */ 229 createjs.Bitmap.prototype.getSourceRect = function() { 230 /// <returns type="createjs.Rectangle"/> 231 return this.source_; 232 }; 233 234 /** 235 * Sets the source rectangle. 236 * @param {createjs.Rectangle} rectangle 237 * @const 238 */ 239 createjs.Bitmap.prototype.setSourceRect = function(rectangle) { 240 /// <param type="createjs.Rectangle" name="rectangle"/> 241 this.setBoundingBox(0, 0, rectangle.width, rectangle.height); 242 this.source_ = rectangle; 243 }; 244 245 /** @override */ 246 createjs.Bitmap.prototype.handleEvent = function(event) { 247 // Update the size of this object and invalidate it to redraw this object. 248 var type = event.type; 249 var image = /** @type {HTMLImageElement} */ (event.target); 250 createjs.ImageFactory.removeListeners(image, this); 251 this.ready_ = type == 'load'; 252 if (!this.source_ || !this.ready_) { 253 this.image_ = null; 254 return; 255 } 256 var width = image.width; 257 var height = image.height; 258 this.source_.width = width; 259 this.source_.height = height; 260 // Align this image to the center of its nominal bounds. 261 var nominalBounds = /** @type {createjs.Rectangle} */ (this['nominalBounds']); 262 if (nominalBounds) { 263 var offsetX = nominalBounds.width - width; 264 if (offsetX > 0) { 265 this.offset_.x = offsetX >> 1; 266 } 267 var offsetY = nominalBounds.height - height; 268 if (offsetY > 0) { 269 this.offset_.y = offsetY >> 1; 270 } 271 } 272 this.setBoundingBox(0, 0, width, height); 273 this.handleAttach(1); 274 }; 275 276 /** @override */ 277 createjs.Bitmap.prototype.handleAttach = function(flag) { 278 /// <param type="number" name="flag"/> 279 if (!flag || !this.ready_) { 280 return; 281 } 282 var alphaMapFilter = this.getAlphaMapFilter(); 283 if (alphaMapFilter) { 284 var alpha = alphaMapFilter.image; 285 this.getComposer_().applyAlphaMap(this.image_, alpha); 286 } 287 }; 288 289 /** @override */ 290 createjs.Bitmap.prototype.removeAllChildren = function(opt_destroy) { 291 /// <param type="boolean" optional="true" name="opt_destroy"/> 292 this.handleDetach(); 293 this.image_ = null; 294 this.source_ = null; 295 this.ready_ = false; 296 }; 297 298 /** @override */ 299 createjs.Bitmap.prototype.handleDetach = function() { 300 var image = this.getSourceImage_(); 301 if (image && this.output_) { 302 // Detach the WebGLTexture object attached to this image. 303 this.output_.uncache(image); 304 this.output_ = null; 305 } 306 if (this.composer_) { 307 this.composer_.destroy(); 308 this.composer_ = null; 309 } 310 }; 311 312 if (createjs.USE_PIXEL_TEST) { 313 /** @override */ 314 createjs.Bitmap.prototype.hitTestObject = function(point, types, bubble) { 315 var object = createjs.DisplayObject.prototype.hitTestObject.call( 316 this, point, types, bubble); 317 if (object) { 318 // Return the cached result if the given point is sufficiently close to 319 // the last one. This method is often called twice with the same position 320 // when a user taps on this bitmap: one is for a 'touchdown' event, and 321 // the other is for a 'touchup' event. 322 if (this.hitTestPoint_) { 323 var dx = this.hitTestPoint_.x - point.x; 324 var dy = this.hitTestPoint_.y - point.y; 325 if (dx * dx + dy * dy <= 4) { 326 return this.hitTestResult_; 327 } 328 this.hitTestPoint_.x = point.x; 329 this.hitTestPoint_.y = point.y; 330 } else { 331 this.hitTestPoint_ = new createjs.Point(point.x, point.y); 332 } 333 var local = new createjs.Point(point.x, point.y); 334 this.getInverse().transformPoint(local); 335 if (this.source_) { 336 local.x += this.source_.x; 337 local.y += this.source_.y; 338 } 339 // Draw this bitmap to the 1x1 <canvas> element used for hit-testing and 340 // read its pixel. (This <canvas> element uses the "copy" composite 341 // operation to discard the destination pixels in drawing this bitmap, 342 // i.e. it is unnecessary to clear the <canvas> element explicitly 343 // before drawing this bitmap.) 344 var context = createjs.Bitmap.hitTestContext_; 345 if (!context) { 346 var canvas = createjs.createCanvas(); 347 canvas.width = 1; 348 canvas.height = 1; 349 createjs.Bitmap.hitTestCanvas_ = canvas; 350 context = createjs.getRenderingContext2D(canvas); 351 context.globalCompositeOperation = 'copy'; 352 createjs.Bitmap.hitTestContext_ = context; 353 } 354 context.drawImage(this.image_, -local.x, -local.y); 355 var pixels = context.getImageData(0, 0, 1, 1); 356 if (!pixels.data[3]) { 357 object = null; 358 } 359 this.hitTestResult_ = object; 360 } 361 return object; 362 }; 363 } 364 365 /** @override */ 366 createjs.Bitmap.prototype.paintObject = function(renderer) { 367 /// <param type="createjs.Renderer" name="renderer"/> 368 var matrix = this.getColorMatrix(); 369 if (matrix) { 370 renderer.setColorMatrix(matrix); 371 } 372 var image = /** @type {HTMLImageElement} */ (this.getSourceImage_()); 373 var source = this.source_; 374 var x = this.offset_.x; 375 var y = this.offset_.y; 376 var width = source.width; 377 var height = source.height; 378 renderer.drawPartial(image, 379 source.x, source.y, width, height, 380 x, y, width, height); 381 if (matrix) { 382 renderer.setColorMatrix(null); 383 } 384 }; 385 386 /** @override */ 387 createjs.Bitmap.prototype.isVisible = function() { 388 /// <returns type="boolean"/> 389 return this.ready_ && createjs.Bitmap.superClass_.isVisible.call(this); 390 }; 391 392 // Add a getter and a setter for applications to access internal variables. 393 Object.defineProperties(createjs.Bitmap.prototype, { 394 'image': { 395 get: createjs.Bitmap.prototype.getImage, 396 set: createjs.Bitmap.prototype.setImage 397 }, 398 'sourceRect': { 399 get: createjs.Bitmap.prototype.getSourceRect, 400 set: createjs.Bitmap.prototype.setSourceRect 401 } 402 }); 403 404 // Export the createjs.Bitmap object to the global namespace. 405 createjs.exportObject('createjs.Bitmap', createjs.Bitmap, { 406 }); 407