1 /** 2 * The Render Engine 3 * CanvasContext 4 * 5 * @fileoverview An extension of the 2D render context which encapsulates 6 * the Canvas element. 7 * 8 * @author: Brett Fattori (brettf@renderengine.com) 9 * @author: $Author: bfattori@gmail.com $ 10 * @version: $Revision: 1557 $ 11 * 12 * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com) 13 * 14 * Permission is hereby granted, free of charge, to any person obtaining a copy 15 * of this software and associated documentation files (the "Software"), to deal 16 * in the Software without restriction, including without limitation the rights 17 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 * copies of the Software, and to permit persons to whom the Software is 19 * furnished to do so, subject to the following conditions: 20 * 21 * The above copyright notice and this permission notice shall be included in 22 * all copies or substantial portions of the Software. 23 * 24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 * THE SOFTWARE. 31 * 32 */ 33 34 // The class this file defines and its required classes 35 R.Engine.define({ 36 "class":"R.rendercontexts.CanvasContext", 37 "requires":[ 38 "R.math.Math2D", 39 "R.rendercontexts.RenderContext2D" 40 ] 41 }); 42 43 /** 44 * @class A <tt>canvas</tt> element represented within the engine. A canvas 45 * is a 2D context which can render lines, images, and polygons. Transformations 46 * can be saved and restored, allowing for complex, stacked transformations. 47 * 48 * @extends R.rendercontexts.RenderContext2D 49 * @constructor 50 * @description Create a new instance of a canvas context. 51 * @param name {String} The name of the context 52 * @param width {Number} The width (in pixels) of the canvas context. 53 * @param height {Number} The height (in pixels) of the canvas context. 54 */ 55 R.rendercontexts.CanvasContext = function () { 56 return R.rendercontexts.RenderContext2D.extend(/** @scope R.rendercontexts.CanvasContext.prototype */{ 57 58 context2D:null, 59 divisions:-1, 60 dirtyBins:null, 61 firstFrame:null, 62 63 /** @private */ 64 constructor:function (name, width, height) { 65 // Make sure the browser supports the canvas and 2D context! 66 Assert((R.engine.Support.sysInfo().support.canvas.defined && 67 R.engine.Support.sysInfo().support.canvas.contexts["2D"]), "Browser does not support Canvas. Cannot construct CanvasContext!"); 68 69 Assert((width != null && height != null), "Width and height must be specified in CanvasContext"); 70 71 this.setWidth(width); 72 this.setHeight(height); 73 var canvas; 74 var worldScale = this.getWorldScale(); 75 // Create the canvas element 76 canvas = document.createElement("canvas"); 77 78 this.base(name || "CanvasContext", canvas); 79 this.setViewport(R.math.Rectangle2D.create(0, 0, this.width, this.height)); 80 81 canvas.id = this.getId(); 82 this.setWorldScale(this.getWorldScale()); 83 84 // Adjust the element accordingly 85 $(this.getElement()) 86 .attr({ 87 "width":this.getWidth(), 88 "height":this.getHeight() 89 }); 90 91 // Set the number of divisions along X and Y 92 this.divisions = 5; 93 this.dirtyBins = {}; 94 this.firstFrame = true; 95 }, 96 97 /** 98 * Releases the context back into the object pool. See {@link PooledObject#release} 99 * for more information. 100 */ 101 release:function () { 102 this.base(); 103 this.context2D = null; 104 }, 105 106 /** 107 * Set the number of divisions along the X and Y axis used to determine the 108 * number of dirty rectangles for the current viewport. 109 * 110 * @param divisions {Number} The number of divisions along X and Y. Defaults to 5. 111 */ 112 setDivisions:function (divisions) { 113 this.divisions = divisions; 114 }, 115 116 /** 117 * Set the scale of the world 118 * @param scaleX {Number} The scale of the world along the X axis 119 * @param scaleY {Number} The scale of the world along the y axis 120 */ 121 setWorldScale:function (scaleX, scaleY) { 122 this.base(scaleX, scaleY); 123 scaleY = scaleY ? scaleY : scaleX; 124 this.setViewport(R.math.Rectangle2D.create(0, 0, this.getWidth() * (1 / scaleX), this.getHeight() * (1 / scaleY))); 125 }, 126 127 /** 128 * Gets the surface context upon which all objects are drawn. 129 * @return {Object} 130 */ 131 get2DContext:function () { 132 if (this.context2D == null) { 133 this.context2D = this.getSurface().getContext('2d'); 134 } 135 return this.context2D; 136 }, 137 138 /** 139 * Push a transform state onto the stack. 140 */ 141 pushTransform:function () { 142 this.base(); 143 this.get2DContext().save(); 144 }, 145 146 /** 147 * Pop a transform state off the stack. 148 */ 149 popTransform:function () { 150 this.base(); 151 this.get2DContext().restore(); 152 }, 153 154 155 //================================================================ 156 // Drawing functions 157 158 /** 159 * Reset the entire context, clearing it and preparing it for drawing. 160 */ 161 reset:function (rect) { 162 if (!R.Engine.options.useDirtyRectangles) { 163 var cRect = (rect != null ? rect : this.getViewport()); 164 var d = cRect.get(); 165 this.get2DContext().clearRect(d.x, d.y, d.w, d.h); 166 } 167 }, 168 169 /** 170 * Set up the world for the given time before any rendering is dont. 171 * @param time {Number} The render time 172 * @param dt {Number} The delta between the world time and the last time the world was updated 173 * in milliseconds. 174 */ 175 setupWorld:function (time, dt) { 176 this.setScale(this.getWorldScale()); 177 178 /* pragma:DEBUG_START */ 179 if (R.Engine.getDebugMode()) { 180 this.setLineStyle("yellow"); 181 this.setLineWidth(1); 182 this.drawRectangle(this.getViewport()); 183 } 184 /* pragma:DEBUG_END */ 185 186 this.base(time, dt); 187 }, 188 189 /** 190 * Capture the dirty rectangles for the bin 191 * @param {Object} bin 192 * @param {Object} itr 193 */ 194 captureBin:function (bin, itr) { 195 var dBin = this.dirtyBins["Bin" + bin]; 196 if (!dBin) { 197 dBin = this.dirtyBins["Bin" + bin] = []; 198 } 199 200 // Brute force method 201 itr.reset(); 202 while (itr.hasNext()) { 203 var obj = itr.next(); 204 if (obj.wasDirty && obj.wasDirty()) { 205 var aabb = obj.getAABB(); 206 dBin.push({ 207 p:aabb.getTopLeft(), 208 d:this.getImage(obj.getAABB()) 209 }); 210 } 211 } 212 itr.reset(); 213 }, 214 215 /** 216 * Reset the bin's dirty rectangles before drawing the dirty objects 217 * in the bin. 218 * @param {Object} bin 219 */ 220 resetBin:function (bin) { 221 var dBin = this.dirtyBins["Bin" + bin]; 222 while (dBin && dBin.length > 0) { 223 var r = dBin.shift(); 224 this.putImage(r.d, r.p); 225 } 226 }, 227 228 /** 229 * Render all of the objects in a single bin, grouped by z-index. 230 * @param bin {Number} The bin number being rendered 231 * @param itr {R.lang.Iterator} The iterator over all the objects in the bin 232 * @param time {Number} The current render time in milliseconds from the engine. 233 * @param dt {Number} The delta between the world time and the last time the world was updated 234 * in milliseconds. 235 */ 236 renderBin:function (bin, itr, time, dt) { 237 if (R.Engine.options.useDirtyRectangles) { 238 if (!this.firstFrame) { 239 this.resetBin(bin); 240 } 241 this.captureBin(bin, itr); 242 this.firstFrame = false; 243 } 244 R.rendercontexts.RenderContext2D.prototype.renderBin.call(this, bin, itr, time, dt); 245 }, 246 247 /** 248 * Set the background color of the context. 249 * 250 * @param color {String} An HTML color 251 */ 252 setBackgroundColor:function (color) { 253 jQuery(this.getSurface()).css("background-color", color); 254 this.base(color); 255 }, 256 257 /** 258 * Set the current transform position (translation). 259 * 260 * @param point {R.math.Point2D} The translation 261 */ 262 setPosition:function (point) { 263 this.get2DContext().translate(point.x, point.y); 264 this.base(point); 265 }, 266 267 /** 268 * Set the rotation angle of the current transform 269 * 270 * @param angle {Number} An angle in degrees 271 */ 272 setRotation:function (angle) { 273 this.get2DContext().rotate(R.math.Math2D.degToRad(angle)); 274 this.base(angle); 275 }, 276 277 /** 278 * Set the scale of the current transform. Specifying 279 * only the first parameter implies a uniform scale. 280 * 281 * @param scaleX {Number} The X scaling factor, with 1 being 100% 282 * @param scaleY {Number} The Y scaling factor 283 */ 284 setScale:function (scaleX, scaleY) { 285 scaleX = scaleX || 1; 286 scaleY = scaleY || scaleX; 287 this.get2DContext().scale(scaleX, scaleY); 288 this.base(scaleX, scaleY); 289 }, 290 291 /** 292 * Set the transformation using a matrix. 293 * 294 * @param matrix {Matrix} The transformation matrix 295 */ 296 setTransform:function (matrix) { 297 }, 298 299 /** 300 * Set the line style for the context. 301 * 302 * @param lineStyle {String} An HTML color or <tt>null</tt> 303 */ 304 setLineStyle:function (lineStyle) { 305 this.get2DContext().strokeStyle = lineStyle; 306 this.base(lineStyle); 307 }, 308 309 /** 310 * Set the line width for drawing paths. 311 * 312 * @param [width=1] {Number} The width of lines in pixels 313 */ 314 setLineWidth:function (width) { 315 this.get2DContext().lineWidth = width * 1.0; 316 this.base(width); 317 }, 318 319 /** 320 * Set the fill style of the context. 321 * 322 * @param fillStyle {String} An HTML color, or <tt>null</tt>. 323 */ 324 setFillStyle:function (fillStyle) { 325 this.get2DContext().fillStyle = fillStyle; 326 this.base(fillStyle); 327 }, 328 329 /** 330 * Draw an un-filled rectangle on the context. 331 * 332 * @param rect {R.math.Rectangle2D} The rectangle to draw 333 */ 334 drawRectangle:function (rect) { 335 var rTL = rect.getTopLeft(); 336 var rDM = rect.getDims(); 337 this.get2DContext().strokeRect(rTL.x, rTL.y, rDM.x, rDM.y); 338 this.base(rect); 339 }, 340 341 /** 342 * Draw a filled rectangle on the context. 343 * 344 * @param rect {R.math.Rectangle2D} The rectangle to draw 345 */ 346 drawFilledRectangle:function (rect) { 347 var rTL = rect.getTopLeft(); 348 var rDM = rect.getDims(); 349 this.get2DContext().fillRect(rTL.x, rTL.y, rDM.x, rDM.y); 350 this.base(rect); 351 }, 352 353 /** 354 * @private 355 */ 356 _arc:function (point, radiusX, startAngle, endAngle) { 357 this.startPath(); 358 this.get2DContext().arc(point.x, point.y, radiusX, startAngle, endAngle, false); 359 //this.endPath(); 360 }, 361 362 /** 363 * Draw an un-filled arc on the context. Arcs are drawn in clockwise 364 * order. 365 * 366 * @param point {R.math.Point2D} The point around which the arc will be drawn 367 * @param radius {Number} The radius of the arc in pixels 368 * @param startAngle {Number} The starting angle of the arc in degrees 369 * @param endAngle {Number} The end angle of the arc in degrees 370 */ 371 drawArc:function (point, radiusX, startAngle, endAngle) { 372 this._arc(point, radiusX, startAngle, endAngle); 373 this.strokePath(); 374 this.base(point, radiusX, startAngle, endAngle); 375 }, 376 377 /** 378 * Draw a filled arc on the context. Arcs are drawn in clockwise 379 * order. 380 * 381 * @param point {R.math.Point2D} The point around which the arc will be drawn 382 * @param radius {Number} The radius of the arc in pixels 383 * @param startAngle {Number} The starting angle of the arc in degrees 384 * @param endAngle {Number} The end angle of the arc in degrees 385 */ 386 drawFilledArc:function (point, radiusX, startAngle, endAngle) { 387 this._arc(point, radiusX, startAngle, endAngle); 388 this.fillPath(); 389 this.base(point, radiusX, startAngle, endAngle); 390 }, 391 392 /** 393 * Draw a line on the context. 394 * 395 * @param point1 {R.math.Point2D} The start of the line 396 * @param point2 {R.math.Point2D} The end of the line 397 */ 398 drawLine:function (point1, point2) { 399 this.startPath(); 400 this.moveTo(point1); 401 this.lineTo(point2); 402 //this.endPath(); 403 this.strokePath(); 404 this.base(point1, point2); 405 }, 406 407 /** 408 * Draw a point on the context. 409 * 410 * @param point {R.math.Point2D} The position to draw the point 411 */ 412 drawPoint:function (point) { 413 if (R.Engine.options.pointAsArc) { 414 this._arc(point, 1, 0, 360); 415 this.get2DContext().fill(); 416 } else { 417 this.get2DContext().fillRect(point.x, point.y, 1.5, 1.5); 418 } 419 this.base(point); 420 }, 421 422 /** 423 * Draw a sprite on the context. 424 * 425 * @param sprite {R.resources.types.Sprite} The sprite to draw 426 * @param time {Number} The current world time 427 * @param dt {Number} The delta between the world time and the last time the world was updated 428 * in milliseconds. 429 */ 430 drawSprite:function (sprite, time, dt) { 431 var f = sprite.getFrame(time, dt); 432 this.get2DContext().drawImage(sprite.getSourceImage(), f.x, f.y, f.w, f.h, 0, 0, f.w, f.h); 433 this.base(sprite, time); 434 f.destroy(); 435 }, 436 437 /** 438 * Draw an image on the context. 439 * 440 * @param rect {R.math.Rectangle2D} The rectangle that specifies the position and 441 * dimensions of the image rectangle. 442 * @param image {Object} The image to draw onto the context 443 * @param [srcRect] {R.math.Rectangle2D} <i>[optional]</i> The source rectangle within the image, if 444 * <tt>null</tt> the entire image is used 445 */ 446 drawImage:function (rect, image, srcRect) { 447 if (srcRect) { 448 this.get2DContext().drawImage(image, 449 srcRect.x, srcRect.y, srcRect.w, srcRect.h, rect.x, rect.y, rect.w, rect.h); 450 } else { 451 this.get2DContext().drawImage(image, rect.x, rect.y, rect.w, rect.h); 452 } 453 this.base(rect, image); 454 }, 455 456 /** 457 * Capture an image from the context. 458 * 459 * @param rect {R.math.Rectangle2D} The area to capture 460 * @returns {Array} Image data capture 461 */ 462 getImage:function (rect) { 463 this.base(); 464 465 // Clamp the rectangle to be within the bounds of the context 466 var p = rect.getTopLeft(); 467 var tl = R.math.Point2D.create((p.x < 0 ? 0 : (p.x > this.getWidth() ? this.getWidth() - 1 : p.x)), 468 (p.y < 0 ? 0 : (p.y > this.getHeight() ? this.getHeight() - 1 : p.y))); 469 var wh = R.math.Point2D.create((rect.r > this.getWidth() ? this.getWidth() - tl.x : (rect.r < 0 ? 1 : rect.w)), 470 (rect.b > this.getHeight() ? this.getHeight() - tl.y : (rect.b < 0 ? 1 : rect.h))); 471 var imgData = this.get2DContext().getImageData(tl.x, tl.y, wh.x, wh.y); 472 tl.destroy(); 473 wh.destroy(); 474 return imgData; 475 }, 476 477 /** 478 * Useful method which returns a data URL which represents the 479 * current state of the canvas context. The URL can be passed to 480 * an image element. <i>Note: Only works in Firefox and Opera!</i> 481 * 482 * @param {String} format The mime-type of the output, or <tt>null</tt> for 483 * the PNG default. (unsupported) 484 * @return {String} The data URL 485 */ 486 getDataURL:function (format) { 487 return this.getSurface().toDataURL(); 488 }, 489 490 /** 491 * Draw an image, captured with {@link #getImage}, to 492 * the context. 493 * 494 * @param imageData {Array} Image data captured 495 * @param point {R.math.Point2D} The poisition at which to draw the image 496 */ 497 putImage:function (imageData, point) { 498 var x = (point.x < 0 ? 0 : (point.x > this.getWidth() ? this.getWidth() - 1 : point.x)); 499 var y = (point.y < 0 ? 0 : (point.y > this.getHeight() ? this.getHeight() - 1 : point.y)); 500 if (imageData != null) { 501 this.get2DContext().putImageData(imageData, x, y); 502 } 503 }, 504 505 /** 506 * Draw filled text on the context. 507 * 508 * @param point {R.math.Point2D} The top-left position to draw the image. 509 * @param text {String} The text to draw 510 */ 511 drawText:function (point, text) { 512 this.base(point, text); 513 if (!this.get2DContext().fillText) { 514 return; // Unsupported by canvas 515 } 516 this.get2DContext().font = this.getNormalizedFont(); 517 this.get2DContext().textBaseline = this.getFontBaseline(); 518 this.get2DContext().fillText(text, point.x, point.y); 519 }, 520 521 /** 522 * Get a rectangle that will approximately enclose the text drawn by the render context. 523 * @param text {String} The text to measure 524 * @return {R.math.Rectangle2D} 525 */ 526 getTextMetrics:function (text) { 527 var rect = this.base(text); 528 this.get2DContext().font = this.getNormalizedFont(); 529 this.get2DContext().textBaseline = this.getFontBaseline(); 530 var metrics = this.get2DContext().measureText(text); 531 // Scale the height a little to account for hanging chars 532 rect.set(0, 0, metrics.width, parseFloat(this.getFontSize()) * 1.25); 533 return rect; 534 }, 535 536 /** 537 * Draw stroked (outline) text on the context. 538 * 539 * @param point {R.math.Point2D} 540 * @param text {String} The text to draw 541 */ 542 strokeText:function (point, text) { 543 if (!R.engine.Support.sysInfo().canvas.text) { 544 return; // Unsupported by canvas 545 } 546 this.get2DContext().font = this.getNormalizedFont(); 547 this.get2DContext().textBaseline = this.getFontBaseline(); 548 this.get2DContext().strokeText(text, point.x, point.y); 549 }, 550 551 /** 552 * Start a path. 553 */ 554 startPath:function () { 555 this.get2DContext().beginPath(); 556 this.base(); 557 }, 558 559 /** 560 * End a path. 561 */ 562 endPath:function () { 563 this.get2DContext().closePath(); 564 this.base(); 565 }, 566 567 /** 568 * Stroke a path using the current line style and width. 569 */ 570 strokePath:function () { 571 this.get2DContext().stroke(); 572 this.base(); 573 }, 574 575 /** 576 * Fill a path using the current fill style. 577 */ 578 fillPath:function () { 579 this.get2DContext().fill(); 580 this.base(); 581 }, 582 583 /** 584 * Move the current path to the point sepcified. 585 * 586 * @param point {R.math.Point2D} The point to move to 587 */ 588 moveTo:function (point) { 589 this.get2DContext().moveTo(point.x, point.y); 590 this.base(); 591 }, 592 593 /** 594 * Draw a line from the current point to the point specified. 595 * 596 * @param point {R.math.Point2D} The point to draw a line to 597 */ 598 lineTo:function (point) { 599 this.get2DContext().lineTo(point.x, point.y); 600 this.base(point); 601 }, 602 603 /** 604 * Draw a quadratic curve from the current point to the specified point. 605 * 606 * @param cPoint {R.math.Point2D} The control point 607 * @param point {R.math.Point2D} The point to draw to 608 */ 609 quadraticCurveTo:function (cPoint, point) { 610 this.get2DContext().quadraticCurveTo(cPoint.x, cPoint.y, point.x, point.y); 611 this.base(cPoint, point); 612 }, 613 614 /** 615 * Draw a bezier curve from the current point to the specified point. 616 * 617 * @param cPoint1 {R.math.Point2D} Control point 1 618 * @param cPoint2 {R.math.Point2D} Control point 2 619 * @param point {R.math.Point2D} The point to draw to 620 */ 621 bezierCurveTo:function (cPoint1, cPoint2, point) { 622 this.get2DContext().bezierCurveTo(cPoint1.x, cPoint1.y, cPoint2.x, cPoint2.y, point.x, point.y); 623 this.base(cPoint1, cPoint2, point); 624 }, 625 626 /** 627 * Draw an arc from the current point to the specified point. 628 * 629 * @param point1 {R.math.Point2D} Arc point 1 630 * @param point2 {R.math.Point2D} Arc point 2 631 * @param radius {Number} The radius of the arc 632 */ 633 arcTo:function (point1, point2, radius) { 634 this.get2DContext().arcTo(point1.x, point1.y, point2.x, point2.y, radius); 635 this.base(point1, point2, radius); 636 } 637 638 }, /** @scope R.rendercontexts.CanvasContext.prototype */{ 639 /** 640 * Get the class name of this object 641 * 642 * @return {String} "R.rendercontexts.CanvasContext" 643 */ 644 getClassName:function () { 645 return "R.rendercontexts.CanvasContext"; 646 } 647 648 }); 649 }; 650