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="object.js"/> 27 /// <reference path="renderer.js"/> 28 /// <reference path="shadow.js"/> 29 /// <reference path="rectangle.js"/> 30 /// <reference path="counter.js"/> 31 /// <reference path="event.js"/> 32 /// <reference path="user_agent.js"/> 33 /// <reference path="ticker.js"/> 34 35 /** 36 * A class that implements the createjs.Renderer interface with Canvas 2D. 37 * @param {HTMLCanvasElement} canvas 38 * @param {createjs.BoundingBox} scissor 39 * @extends {createjs.Renderer} 40 * @implements {EventListener} 41 * @constructor 42 */ 43 createjs.CanvasRenderer = function(canvas, scissor) { 44 /// <param type="HTMLCanvasElement" name="canvas"/> 45 /// <param type="createjs.BoundingBox" name="scissor"/> 46 createjs.Renderer.call(this, canvas, canvas.width, canvas.height); 47 48 /** 49 * The 2D rendering context attached to the output <canvas> element. 50 * @type {CanvasRenderingContext2D} 51 * @private 52 */ 53 this.context_ = createjs.getRenderingContext2D(canvas); 54 55 // Write the context used by this renderer. 56 canvas.setAttribute('dena-context', '2d'); 57 58 // Listen keyup events to enable debugging features or disable them. 59 if (createjs.DEBUG) { 60 document.addEventListener('keyup', this, false); 61 } 62 63 // Create the clip specified by an application. (This renderer does NOT delete 64 // this clip.) 65 if (scissor) { 66 this.context_.beginPath(); 67 this.context_.rect(scissor.getLeft(), scissor.getTop(), 68 scissor.getWidth(), scissor.getHeight()); 69 this.context_.clip(); 70 } 71 }; 72 createjs.inherits('CanvasRenderer', createjs.CanvasRenderer, createjs.Renderer); 73 74 /** 75 * The current alpha value of the output <canvas> element. 76 * @type {number} 77 * @private 78 */ 79 createjs.CanvasRenderer.prototype.alpha_ = 1; 80 81 /** 82 * The current composition value of the output <canvas> element. 83 * @type {number} 84 * @private 85 */ 86 createjs.CanvasRenderer.prototype.compositeOperation_ = 87 createjs.Renderer.Composition.SOURCE_OVER; 88 89 /** 90 * The scissor rectangle currently used by this renderer. 91 * @type {createjs.BoundingBox} 92 * @private 93 */ 94 createjs.CanvasRenderer.prototype.scissor_ = null; 95 96 /** 97 * The scissor rectangle currently used by this renderer. 98 * @type {createjs.CanvasRenderer} 99 * @private 100 */ 101 createjs.CanvasRenderer.prototype.mask_ = null; 102 103 /** 104 * Saves the current rendering context. 105 * @private 106 */ 107 createjs.CanvasRenderer.prototype.saveContext_ = function() { 108 this.context_.save(); 109 }; 110 111 /** 112 * Restores the rendering context. 113 * @private 114 */ 115 createjs.CanvasRenderer.prototype.restoreContext_ = function() { 116 this.context_.restore(); 117 this.alpha_ = -1; 118 this.compositeOperation_ = -1; 119 }; 120 121 /** 122 * Updates the scissor rectangle. 123 * @param {createjs.BoundingBox} scissor 124 * @private 125 */ 126 createjs.CanvasRenderer.prototype.updateScissor_ = function(scissor) { 127 /// <param type="createjs.CanvasRenderer" name="renderer"/> 128 /// <param type="createjs.BoundingBox" name="scissor"/> 129 if (this.scissor_) { 130 if (this.scissor_.isEqual(scissor)) { 131 return; 132 } 133 this.restoreContext_(); 134 } 135 this.scissor_ = scissor; 136 this.saveContext_(); 137 this.setTransformation(1, 0, 0, 1, 0, 0); 138 this.context_.beginPath(); 139 this.context_.rect(scissor.getLeft(), scissor.getTop(), 140 scissor.getWidth(), scissor.getHeight()); 141 this.context_.clip(); 142 }; 143 144 /** 145 * Destroys the scissor rectangle. 146 * @private 147 */ 148 createjs.CanvasRenderer.prototype.destroyScissor_ = function() { 149 /// <param type="createjs.CanvasRenderer" name="renderer"/> 150 if (this.scissor_) { 151 this.restoreContext_(); 152 this.scissor_ = null; 153 } 154 }; 155 156 if (createjs.DEBUG) { 157 /** 158 * Whether to show a red rectangle around a painted objects in a rendering 159 * cycle. 160 * @type {boolean} 161 * @private 162 */ 163 createjs.CanvasRenderer.prototype.showPainted_ = true; 164 165 /** 166 * @type {Array.<createjs.Renderer.RenderObject>} 167 * @private 168 */ 169 createjs.CanvasRenderer.prototype.painted_ = null; 170 171 /** 172 * Draws debug information on this renderer. 173 * @param {Array.<createjs.Renderer.RenderObject>} painted 174 * @private 175 */ 176 createjs.CanvasRenderer.prototype.drawDebug_ = function(painted) { 177 /// <param type="Array" elementType="createjs.Renderer.RenderObject" 178 /// name="painted"/> 179 var context = this.context_; 180 if (!createjs.UserAgent.isIPhone()) { 181 context.fillStyle = '#f00'; 182 var HEIGHT = 24; 183 context.font = HEIGHT + 'px arial'; 184 var text = createjs.Counter.paintedObjects + '/' + 185 createjs.Counter.visibleObjects + '/' + 186 createjs.Counter.totalObjects + ' ' + 187 createjs.Counter.updatedTweens + '/' + 188 createjs.Counter.runningTweens + ' ' + 189 createjs.Counter.cachedRenderers + '/' + 190 createjs.Counter.totalRenderers; 191 context.fillText(text, 0, HEIGHT, 10000); 192 } 193 if (this.showPainted_) { 194 context.strokeStyle = '#f00'; 195 context.setTransform(1, 0, 0, 1, 0, 0); 196 for (var i = 0; i < painted.length; ++i) { 197 var object = painted[i]; 198 var box = object.getRenderBox(); 199 context.strokeRect( 200 box.getLeft(), box.getTop(), box.getWidth(), box.getHeight()); 201 } 202 } 203 }; 204 205 /** @override */ 206 createjs.CanvasRenderer.prototype.addDirtyObject = function(object) { 207 this.painted_.push(object); 208 }; 209 210 /** @override */ 211 createjs.CanvasRenderer.prototype.handleEvent = function(event) { 212 createjs.assert(event.type == 'keyup'); 213 214 /// <var type="KeyboardEvent" name="keyEvent"/> 215 var keyEvent = /** @type {KeyboardEvent} */ (event); 216 var keyCode = keyEvent.keyCode; 217 if (keyCode == createjs.Event.KeyCodes.Q) { 218 this.showPainted_ = !this.showPainted_; 219 } 220 }; 221 } 222 223 /** 224 * Clears the invalidated rectangles. 225 * @protected 226 */ 227 createjs.CanvasRenderer.prototype.clearScreen = function() { 228 // There is a bug on stock browsers of Android 4.1.x and 4.2.x where the 229 // clearRect() method crashes or does not clear the target canvas. To work 230 // around this bug, this method calls the clearRect() method with a size 231 // bigger than the canvas size. 232 this.context_.setTransform(1, 0, 0, 1, 0, 0); 233 this.context_.clearRect(0, 0, this.getWidth() + 1, this.getHeight() + 1); 234 }; 235 236 /** 237 * Returns a renderer used for composing a render object with a mask. 238 * @return {createjs.CanvasRenderer} 239 * @protected 240 */ 241 createjs.CanvasRenderer.prototype.getMask = function() { 242 /// <returns type="createjs.CanvasRenderer"/> 243 if (!this.mask_) { 244 var canvas = createjs.createCanvas(); 245 canvas.width = this.getWidth(); 246 canvas.height = this.getHeight(); 247 this.mask_ = new createjs.CanvasRenderer(canvas, null); 248 } else { 249 this.mask_.clearScreen(); 250 } 251 return this.mask_; 252 }; 253 254 /** 255 * Deletes the renderer used for composing a render object with a mask. 256 * @protected 257 * @const 258 */ 259 createjs.CanvasRenderer.prototype.destroyMask = function() { 260 if (this.mask_) { 261 this.mask_.destroy(); 262 this.mask_ = null; 263 } 264 }; 265 266 /** 267 * Copies the image of a mask renderer, a renderer used for composing a render 268 * object with a mask object, to this renderer. 269 * @param {createjs.CanvasRenderer} mask 270 * @param {number} composition 271 * @protected 272 * @const 273 */ 274 createjs.CanvasRenderer.prototype.drawMask = function(mask, composition) { 275 /// <param type="createjs.CanvasRenderer" name="mask"/> 276 /// <param type="number" name="composition"/> 277 this.setTransformation(1, 0, 0, 1, 0, 0); 278 this.setAlpha(1); 279 this.setComposition(createjs.Renderer.Composition.SOURCE_OVER); 280 this.drawCanvas( 281 mask.getCanvas(), 0, 0, this.getWidth(), this.getHeight()); 282 }; 283 284 /** @override */ 285 createjs.CanvasRenderer.prototype.destroy = function() { 286 this.destroyMask(); 287 this.context_ = null; 288 this.resetCanvas(); 289 }; 290 291 /** @override */ 292 createjs.CanvasRenderer.prototype.setTransformation = 293 function(a, b, c, d, tx, ty) { 294 /// <param type="number" name="a"/> 295 /// <param type="number" name="b"/> 296 /// <param type="number" name="c"/> 297 /// <param type="number" name="d"/> 298 /// <param type="number" name="tx"/> 299 /// <param type="number" name="ty"/> 300 this.context_.setTransform(a, b, c, d, tx, ty); 301 }; 302 303 /** @override */ 304 createjs.CanvasRenderer.prototype.setAlpha = function(alpha) { 305 /// <param type="number" name="alpha"/> 306 if (this.alpha_ != alpha) { 307 this.alpha_ = alpha; 308 this.context_.globalAlpha = alpha; 309 } 310 }; 311 312 /** @override */ 313 createjs.CanvasRenderer.prototype.setComposition = function(operation) { 314 /// <param type="number" name="operation"/> 315 if (this.compositeOperation_ != operation) { 316 this.compositeOperation_ = operation; 317 this.context_.globalCompositeOperation = 318 createjs.Renderer.getCompositionName(operation); 319 } 320 }; 321 322 /** @override */ 323 createjs.CanvasRenderer.prototype.drawCanvas = 324 function(canvas, x, y, width, height) { 325 /// <param type="HTMLCanvasElement" name="canvas"/> 326 /// <param type="number" name="x"/> 327 /// <param type="number" name="y"/> 328 /// <param type="number" name="width"/> 329 /// <param type="number" name="height"/> 330 this.context_.drawImage(canvas, x, y, width, height); 331 }; 332 333 /** @override */ 334 createjs.CanvasRenderer.prototype.drawVideo = 335 function (video, x, y, width, height) { 336 /// <param type="HTMLVideoElement" name="video"/> 337 /// <param type="number" name="x"/> 338 /// <param type="number" name="y"/> 339 /// <param type="number" name="width"/> 340 /// <param type="number" name="height"/> 341 this.context_.drawImage(video, x, y, width, height); 342 }; 343 344 /** @override */ 345 createjs.CanvasRenderer.prototype.drawPartial = 346 function(image, srcX, srcY, srcWidth, srcHeight, x, y, width, height) { 347 /// <param type="HTMLImageElement" name="image"/> 348 /// <param type="number" name="srcX"/> 349 /// <param type="number" name="srcY"/> 350 /// <param type="number" name="srcWidth"/> 351 /// <param type="number" name="srcHeight"/> 352 /// <param type="number" name="x"/> 353 /// <param type="number" name="y"/> 354 /// <param type="number" name="width"/> 355 /// <param type="number" name="height"/> 356 this.context_.drawImage( 357 image, srcX, srcY, srcWidth, srcHeight, x, y, width, height); 358 }; 359 360 /** @override */ 361 createjs.CanvasRenderer.prototype.addObject = function(object) { 362 /// <param type="createjs.Renderer.RenderObject" name="object"/> 363 // Skip rendering the specified object if it is not in the output <canvas>. 364 // Even when this renderer skips rendering an object, it still needs to 365 // call its 'beginPaintObject()' method and to update properties of this 366 // renderer so it can render succeeding objects. 367 var box = object.getRenderBox(); 368 if (box.maxX <= 0 || box.maxY <= 0 || 369 this.getWidth() <= box.minX || this.getHeight() <= box.minY) { 370 object.beginPaintObject(this); 371 return; 372 } 373 if (createjs.DEBUG) { 374 if (createjs.CanvasRenderer.showPainted_) { 375 this.addDirtyObject(object); 376 } 377 ++createjs.Counter.paintedObjects; 378 } 379 // Draw a masked object. The current code just show render objects 380 // with 'compose' clips. 381 var scissor = object.getClip(); 382 if (!scissor || !scissor.getMethod() || scissor.isShow()) { 383 this.destroyScissor_(); 384 } else if (!scissor.getShape()) { 385 this.updateScissor_(scissor.getBox()); 386 } else if (scissor.isCompose()) { 387 var mask = this.getMask(); 388 object.beginPaintObject(mask); 389 object.paintObject(mask); 390 var shape = scissor.getShape(); 391 shape.beginPaintObject(mask); 392 shape.paintObject(mask); 393 this.drawMask(mask, scissor.getComposition()); 394 return; 395 } else { 396 return; 397 } 398 object.beginPaintObject(this); 399 object.paintObject(this); 400 }; 401 402 /** @override */ 403 createjs.CanvasRenderer.prototype.begin = function() { 404 if (createjs.DEBUG) { 405 this.painted_ = []; 406 } 407 this.clearScreen(); 408 // Some Android 4.1.x browsers (e.g. DoCoMo L-05E) has an issue that calling 409 // the CanvasRenderingContext2D.prototype.clearRect() method in a 410 // setInterval() callback does not clear a <canvas> element: 411 // <https://code.google.com/p/android/issues/detail?id=39247>. 412 // This renderer triggers DOM reflow to work around this issue when a game 413 // needs it, i.e. it calls the createjs.Config.setUseAndroidWorkarounds() 414 // method with its parameters '2d' and 1. (DOM reflow is a very slow operation 415 // and this renderer does not trigger it by default.) 416 this.updateCanvas('2d'); 417 }; 418 419 /** @override */ 420 createjs.CanvasRenderer.prototype.paint = function(time) { 421 /// <param type="number" name="time"/> 422 // Render each layer and copy it to this renderer. Passing null to the 423 // endPaint() method prevents it from copying its result to this renderer. 424 // (This is used only by debug builds to hide layers specified by a user.) 425 this.destroyScissor_(); 426 if (createjs.DEBUG) { 427 this.drawDebug_(this.painted_); 428 } 429 }; 430