1 /* 2 * Copyright (C) 2013 Glyptodon LLC 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to deal 6 * in the Software without restriction, including without limitation the rights 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 * copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in 12 * all copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 * THE SOFTWARE. 21 */ 22 23 var Guacamole = Guacamole || {}; 24 25 /** 26 * Abstract ordered drawing surface. Each Layer contains a canvas element and 27 * provides simple drawing instructions for drawing to that canvas element, 28 * however unlike the canvas element itself, drawing operations on a Layer are 29 * guaranteed to run in order, even if such an operation must wait for an image 30 * to load before completing. 31 * 32 * @constructor 33 * 34 * @param {Number} width The width of the Layer, in pixels. The canvas element 35 * backing this Layer will be given this width. 36 * 37 * @param {Number} height The height of the Layer, in pixels. The canvas element 38 * backing this Layer will be given this height. 39 */ 40 Guacamole.Layer = function(width, height) { 41 42 /** 43 * Reference to this Layer. 44 * @private 45 */ 46 var layer = this; 47 48 /** 49 * The canvas element backing this Layer. 50 * @private 51 */ 52 var canvas = document.createElement("canvas"); 53 54 /** 55 * The 2D display context of the canvas element backing this Layer. 56 * @private 57 */ 58 var context = canvas.getContext("2d"); 59 context.save(); 60 61 /** 62 * Whether a new path should be started with the next path drawing 63 * operations. 64 * @private 65 */ 66 var pathClosed = true; 67 68 /** 69 * The number of states on the state stack. 70 * 71 * Note that there will ALWAYS be one element on the stack, but that 72 * element is not exposed. It is only used to reset the layer to its 73 * initial state. 74 * 75 * @private 76 */ 77 var stackSize = 0; 78 79 /** 80 * Map of all Guacamole channel masks to HTML5 canvas composite operation 81 * names. Not all channel mask combinations are currently implemented. 82 * @private 83 */ 84 var compositeOperation = { 85 /* 0x0 NOT IMPLEMENTED */ 86 0x1: "destination-in", 87 0x2: "destination-out", 88 /* 0x3 NOT IMPLEMENTED */ 89 0x4: "source-in", 90 /* 0x5 NOT IMPLEMENTED */ 91 0x6: "source-atop", 92 /* 0x7 NOT IMPLEMENTED */ 93 0x8: "source-out", 94 0x9: "destination-atop", 95 0xA: "xor", 96 0xB: "destination-over", 97 0xC: "copy", 98 /* 0xD NOT IMPLEMENTED */ 99 0xE: "source-over", 100 0xF: "lighter" 101 }; 102 103 /** 104 * Resizes the canvas element backing this Layer without testing the 105 * new size. This function should only be used internally. 106 * 107 * @private 108 * @param {Number} newWidth The new width to assign to this Layer. 109 * @param {Number} newHeight The new height to assign to this Layer. 110 */ 111 function resize(newWidth, newHeight) { 112 113 // Only preserve old data if width/height are both non-zero 114 var oldData = null; 115 if (layer.width !== 0 && layer.height !== 0) { 116 117 // Create canvas and context for holding old data 118 oldData = document.createElement("canvas"); 119 oldData.width = layer.width; 120 oldData.height = layer.height; 121 122 var oldDataContext = oldData.getContext("2d"); 123 124 // Copy image data from current 125 oldDataContext.drawImage(canvas, 126 0, 0, layer.width, layer.height, 127 0, 0, layer.width, layer.height); 128 129 } 130 131 // Preserve composite operation 132 var oldCompositeOperation = context.globalCompositeOperation; 133 134 // Resize canvas 135 canvas.width = newWidth; 136 canvas.height = newHeight; 137 138 // Redraw old data, if any 139 if (oldData) 140 context.drawImage(oldData, 141 0, 0, layer.width, layer.height, 142 0, 0, layer.width, layer.height); 143 144 // Restore composite operation 145 context.globalCompositeOperation = oldCompositeOperation; 146 147 layer.width = newWidth; 148 layer.height = newHeight; 149 150 // Acknowledge reset of stack (happens on resize of canvas) 151 stackSize = 0; 152 context.save(); 153 154 } 155 156 /** 157 * Given the X and Y coordinates of the upper-left corner of a rectangle 158 * and the rectangle's width and height, resize the backing canvas element 159 * as necessary to ensure that the rectangle fits within the canvas 160 * element's coordinate space. This function will only make the canvas 161 * larger. If the rectangle already fits within the canvas element's 162 * coordinate space, the canvas is left unchanged. 163 * 164 * @private 165 * @param {Number} x The X coordinate of the upper-left corner of the 166 * rectangle to fit. 167 * @param {Number} y The Y coordinate of the upper-left corner of the 168 * rectangle to fit. 169 * @param {Number} w The width of the the rectangle to fit. 170 * @param {Number} h The height of the the rectangle to fit. 171 */ 172 function fitRect(x, y, w, h) { 173 174 // Calculate bounds 175 var opBoundX = w + x; 176 var opBoundY = h + y; 177 178 // Determine max width 179 var resizeWidth; 180 if (opBoundX > layer.width) 181 resizeWidth = opBoundX; 182 else 183 resizeWidth = layer.width; 184 185 // Determine max height 186 var resizeHeight; 187 if (opBoundY > layer.height) 188 resizeHeight = opBoundY; 189 else 190 resizeHeight = layer.height; 191 192 // Resize if necessary 193 layer.resize(resizeWidth, resizeHeight); 194 195 } 196 197 /** 198 * Set to true if this Layer should resize itself to accomodate the 199 * dimensions of any drawing operation, and false (the default) otherwise. 200 * 201 * Note that setting this property takes effect immediately, and thus may 202 * take effect on operations that were started in the past but have not 203 * yet completed. If you wish the setting of this flag to only modify 204 * future operations, you will need to make the setting of this flag an 205 * operation with sync(). 206 * 207 * @example 208 * // Set autosize to true for all future operations 209 * layer.sync(function() { 210 * layer.autosize = true; 211 * }); 212 * 213 * @type Boolean 214 * @default false 215 */ 216 this.autosize = false; 217 218 /** 219 * The current width of this layer. 220 * @type Number 221 */ 222 this.width = width; 223 224 /** 225 * The current height of this layer. 226 * @type Number 227 */ 228 this.height = height; 229 230 /** 231 * Returns the canvas element backing this Layer. 232 * @returns {Element} The canvas element backing this Layer. 233 */ 234 this.getCanvas = function() { 235 return canvas; 236 }; 237 238 /** 239 * Changes the size of this Layer to the given width and height. Resizing 240 * is only attempted if the new size provided is actually different from 241 * the current size. 242 * 243 * @param {Number} newWidth The new width to assign to this Layer. 244 * @param {Number} newHeight The new height to assign to this Layer. 245 */ 246 this.resize = function(newWidth, newHeight) { 247 if (newWidth !== layer.width || newHeight !== layer.height) 248 resize(newWidth, newHeight); 249 }; 250 251 /** 252 * Draws the specified image at the given coordinates. The image specified 253 * must already be loaded. 254 * 255 * @param {Number} x The destination X coordinate. 256 * @param {Number} y The destination Y coordinate. 257 * @param {Image} image The image to draw. Note that this is an Image 258 * object - not a URL. 259 */ 260 this.drawImage = function(x, y, image) { 261 if (layer.autosize) fitRect(x, y, image.width, image.height); 262 context.drawImage(image, x, y); 263 }; 264 265 /** 266 * Transfer a rectangle of image data from one Layer to this Layer using the 267 * specified transfer function. 268 * 269 * @param {Guacamole.Layer} srcLayer The Layer to copy image data from. 270 * @param {Number} srcx The X coordinate of the upper-left corner of the 271 * rectangle within the source Layer's coordinate 272 * space to copy data from. 273 * @param {Number} srcy The Y coordinate of the upper-left corner of the 274 * rectangle within the source Layer's coordinate 275 * space to copy data from. 276 * @param {Number} srcw The width of the rectangle within the source Layer's 277 * coordinate space to copy data from. 278 * @param {Number} srch The height of the rectangle within the source 279 * Layer's coordinate space to copy data from. 280 * @param {Number} x The destination X coordinate. 281 * @param {Number} y The destination Y coordinate. 282 * @param {Function} transferFunction The transfer function to use to 283 * transfer data from source to 284 * destination. 285 */ 286 this.transfer = function(srcLayer, srcx, srcy, srcw, srch, x, y, transferFunction) { 287 288 var srcCanvas = srcLayer.getCanvas(); 289 290 // If entire rectangle outside source canvas, stop 291 if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return; 292 293 // Otherwise, clip rectangle to area 294 if (srcx + srcw > srcCanvas.width) 295 srcw = srcCanvas.width - srcx; 296 297 if (srcy + srch > srcCanvas.height) 298 srch = srcCanvas.height - srcy; 299 300 // Stop if nothing to draw. 301 if (srcw === 0 || srch === 0) return; 302 303 if (layer.autosize) fitRect(x, y, srcw, srch); 304 305 // Get image data from src and dst 306 var src = srcLayer.getCanvas().getContext("2d").getImageData(srcx, srcy, srcw, srch); 307 var dst = context.getImageData(x , y, srcw, srch); 308 309 // Apply transfer for each pixel 310 for (var i=0; i<srcw*srch*4; i+=4) { 311 312 // Get source pixel environment 313 var src_pixel = new Guacamole.Layer.Pixel( 314 src.data[i], 315 src.data[i+1], 316 src.data[i+2], 317 src.data[i+3] 318 ); 319 320 // Get destination pixel environment 321 var dst_pixel = new Guacamole.Layer.Pixel( 322 dst.data[i], 323 dst.data[i+1], 324 dst.data[i+2], 325 dst.data[i+3] 326 ); 327 328 // Apply transfer function 329 transferFunction(src_pixel, dst_pixel); 330 331 // Save pixel data 332 dst.data[i ] = dst_pixel.red; 333 dst.data[i+1] = dst_pixel.green; 334 dst.data[i+2] = dst_pixel.blue; 335 dst.data[i+3] = dst_pixel.alpha; 336 337 } 338 339 // Draw image data 340 context.putImageData(dst, x, y); 341 342 }; 343 344 /** 345 * Put a rectangle of image data from one Layer to this Layer directly 346 * without performing any alpha blending. Simply copy the data. 347 * 348 * @param {Guacamole.Layer} srcLayer The Layer to copy image data from. 349 * @param {Number} srcx The X coordinate of the upper-left corner of the 350 * rectangle within the source Layer's coordinate 351 * space to copy data from. 352 * @param {Number} srcy The Y coordinate of the upper-left corner of the 353 * rectangle within the source Layer's coordinate 354 * space to copy data from. 355 * @param {Number} srcw The width of the rectangle within the source Layer's 356 * coordinate space to copy data from. 357 * @param {Number} srch The height of the rectangle within the source 358 * Layer's coordinate space to copy data from. 359 * @param {Number} x The destination X coordinate. 360 * @param {Number} y The destination Y coordinate. 361 */ 362 this.put = function(srcLayer, srcx, srcy, srcw, srch, x, y) { 363 364 var srcCanvas = srcLayer.getCanvas(); 365 366 // If entire rectangle outside source canvas, stop 367 if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return; 368 369 // Otherwise, clip rectangle to area 370 if (srcx + srcw > srcCanvas.width) 371 srcw = srcCanvas.width - srcx; 372 373 if (srcy + srch > srcCanvas.height) 374 srch = srcCanvas.height - srcy; 375 376 // Stop if nothing to draw. 377 if (srcw === 0 || srch === 0) return; 378 379 if (layer.autosize) fitRect(x, y, srcw, srch); 380 381 // Get image data from src and dst 382 var src = srcLayer.getCanvas().getContext("2d").getImageData(srcx, srcy, srcw, srch); 383 context.putImageData(src, x, y); 384 385 }; 386 387 /** 388 * Copy a rectangle of image data from one Layer to this Layer. This 389 * operation will copy exactly the image data that will be drawn once all 390 * operations of the source Layer that were pending at the time this 391 * function was called are complete. This operation will not alter the 392 * size of the source Layer even if its autosize property is set to true. 393 * 394 * @param {Guacamole.Layer} srcLayer The Layer to copy image data from. 395 * @param {Number} srcx The X coordinate of the upper-left corner of the 396 * rectangle within the source Layer's coordinate 397 * space to copy data from. 398 * @param {Number} srcy The Y coordinate of the upper-left corner of the 399 * rectangle within the source Layer's coordinate 400 * space to copy data from. 401 * @param {Number} srcw The width of the rectangle within the source Layer's 402 * coordinate space to copy data from. 403 * @param {Number} srch The height of the rectangle within the source 404 * Layer's coordinate space to copy data from. 405 * @param {Number} x The destination X coordinate. 406 * @param {Number} y The destination Y coordinate. 407 */ 408 this.copy = function(srcLayer, srcx, srcy, srcw, srch, x, y) { 409 410 var srcCanvas = srcLayer.getCanvas(); 411 412 // If entire rectangle outside source canvas, stop 413 if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return; 414 415 // Otherwise, clip rectangle to area 416 if (srcx + srcw > srcCanvas.width) 417 srcw = srcCanvas.width - srcx; 418 419 if (srcy + srch > srcCanvas.height) 420 srch = srcCanvas.height - srcy; 421 422 // Stop if nothing to draw. 423 if (srcw === 0 || srch === 0) return; 424 425 if (layer.autosize) fitRect(x, y, srcw, srch); 426 context.drawImage(srcCanvas, srcx, srcy, srcw, srch, x, y, srcw, srch); 427 428 }; 429 430 /** 431 * Starts a new path at the specified point. 432 * 433 * @param {Number} x The X coordinate of the point to draw. 434 * @param {Number} y The Y coordinate of the point to draw. 435 */ 436 this.moveTo = function(x, y) { 437 438 // Start a new path if current path is closed 439 if (pathClosed) { 440 context.beginPath(); 441 pathClosed = false; 442 } 443 444 if (layer.autosize) fitRect(x, y, 0, 0); 445 context.moveTo(x, y); 446 447 }; 448 449 /** 450 * Add the specified line to the current path. 451 * 452 * @param {Number} x The X coordinate of the endpoint of the line to draw. 453 * @param {Number} y The Y coordinate of the endpoint of the line to draw. 454 */ 455 this.lineTo = function(x, y) { 456 457 // Start a new path if current path is closed 458 if (pathClosed) { 459 context.beginPath(); 460 pathClosed = false; 461 } 462 463 if (layer.autosize) fitRect(x, y, 0, 0); 464 context.lineTo(x, y); 465 466 }; 467 468 /** 469 * Add the specified arc to the current path. 470 * 471 * @param {Number} x The X coordinate of the center of the circle which 472 * will contain the arc. 473 * @param {Number} y The Y coordinate of the center of the circle which 474 * will contain the arc. 475 * @param {Number} radius The radius of the circle. 476 * @param {Number} startAngle The starting angle of the arc, in radians. 477 * @param {Number} endAngle The ending angle of the arc, in radians. 478 * @param {Boolean} negative Whether the arc should be drawn in order of 479 * decreasing angle. 480 */ 481 this.arc = function(x, y, radius, startAngle, endAngle, negative) { 482 483 // Start a new path if current path is closed 484 if (pathClosed) { 485 context.beginPath(); 486 pathClosed = false; 487 } 488 489 if (layer.autosize) fitRect(x, y, 0, 0); 490 context.arc(x, y, radius, startAngle, endAngle, negative); 491 492 }; 493 494 /** 495 * Starts a new path at the specified point. 496 * 497 * @param {Number} cp1x The X coordinate of the first control point. 498 * @param {Number} cp1y The Y coordinate of the first control point. 499 * @param {Number} cp2x The X coordinate of the second control point. 500 * @param {Number} cp2y The Y coordinate of the second control point. 501 * @param {Number} x The X coordinate of the endpoint of the curve. 502 * @param {Number} y The Y coordinate of the endpoint of the curve. 503 */ 504 this.curveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) { 505 506 // Start a new path if current path is closed 507 if (pathClosed) { 508 context.beginPath(); 509 pathClosed = false; 510 } 511 512 if (layer.autosize) fitRect(x, y, 0, 0); 513 context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); 514 515 }; 516 517 /** 518 * Closes the current path by connecting the end point with the start 519 * point (if any) with a straight line. 520 */ 521 this.close = function() { 522 context.closePath(); 523 pathClosed = true; 524 }; 525 526 /** 527 * Add the specified rectangle to the current path. 528 * 529 * @param {Number} x The X coordinate of the upper-left corner of the 530 * rectangle to draw. 531 * @param {Number} y The Y coordinate of the upper-left corner of the 532 * rectangle to draw. 533 * @param {Number} w The width of the rectangle to draw. 534 * @param {Number} h The height of the rectangle to draw. 535 */ 536 this.rect = function(x, y, w, h) { 537 538 // Start a new path if current path is closed 539 if (pathClosed) { 540 context.beginPath(); 541 pathClosed = false; 542 } 543 544 if (layer.autosize) fitRect(x, y, w, h); 545 context.rect(x, y, w, h); 546 547 }; 548 549 /** 550 * Clip all future drawing operations by the current path. The current path 551 * is implicitly closed. The current path can continue to be reused 552 * for other operations (such as fillColor()) but a new path will be started 553 * once a path drawing operation (path() or rect()) is used. 554 */ 555 this.clip = function() { 556 557 // Set new clipping region 558 context.clip(); 559 560 // Path now implicitly closed 561 pathClosed = true; 562 563 }; 564 565 /** 566 * Stroke the current path with the specified color. The current path 567 * is implicitly closed. The current path can continue to be reused 568 * for other operations (such as clip()) but a new path will be started 569 * once a path drawing operation (path() or rect()) is used. 570 * 571 * @param {String} cap The line cap style. Can be "round", "square", 572 * or "butt". 573 * @param {String} join The line join style. Can be "round", "bevel", 574 * or "miter". 575 * @param {Number} thickness The line thickness in pixels. 576 * @param {Number} r The red component of the color to fill. 577 * @param {Number} g The green component of the color to fill. 578 * @param {Number} b The blue component of the color to fill. 579 * @param {Number} a The alpha component of the color to fill. 580 */ 581 this.strokeColor = function(cap, join, thickness, r, g, b, a) { 582 583 // Stroke with color 584 context.lineCap = cap; 585 context.lineJoin = join; 586 context.lineWidth = thickness; 587 context.strokeStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")"; 588 context.stroke(); 589 590 // Path now implicitly closed 591 pathClosed = true; 592 593 }; 594 595 /** 596 * Fills the current path with the specified color. The current path 597 * is implicitly closed. The current path can continue to be reused 598 * for other operations (such as clip()) but a new path will be started 599 * once a path drawing operation (path() or rect()) is used. 600 * 601 * @param {Number} r The red component of the color to fill. 602 * @param {Number} g The green component of the color to fill. 603 * @param {Number} b The blue component of the color to fill. 604 * @param {Number} a The alpha component of the color to fill. 605 */ 606 this.fillColor = function(r, g, b, a) { 607 608 // Fill with color 609 context.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")"; 610 context.fill(); 611 612 // Path now implicitly closed 613 pathClosed = true; 614 615 }; 616 617 /** 618 * Stroke the current path with the image within the specified layer. The 619 * image data will be tiled infinitely within the stroke. The current path 620 * is implicitly closed. The current path can continue to be reused 621 * for other operations (such as clip()) but a new path will be started 622 * once a path drawing operation (path() or rect()) is used. 623 * 624 * @param {String} cap The line cap style. Can be "round", "square", 625 * or "butt". 626 * @param {String} join The line join style. Can be "round", "bevel", 627 * or "miter". 628 * @param {Number} thickness The line thickness in pixels. 629 * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern 630 * within the stroke. 631 */ 632 this.strokeLayer = function(cap, join, thickness, srcLayer) { 633 634 // Stroke with image data 635 context.lineCap = cap; 636 context.lineJoin = join; 637 context.lineWidth = thickness; 638 context.strokeStyle = context.createPattern( 639 srcLayer.getCanvas(), 640 "repeat" 641 ); 642 context.stroke(); 643 644 // Path now implicitly closed 645 pathClosed = true; 646 647 }; 648 649 /** 650 * Fills the current path with the image within the specified layer. The 651 * image data will be tiled infinitely within the stroke. The current path 652 * is implicitly closed. The current path can continue to be reused 653 * for other operations (such as clip()) but a new path will be started 654 * once a path drawing operation (path() or rect()) is used. 655 * 656 * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern 657 * within the fill. 658 */ 659 this.fillLayer = function(srcLayer) { 660 661 // Fill with image data 662 context.fillStyle = context.createPattern( 663 srcLayer.getCanvas(), 664 "repeat" 665 ); 666 context.fill(); 667 668 // Path now implicitly closed 669 pathClosed = true; 670 671 }; 672 673 /** 674 * Push current layer state onto stack. 675 */ 676 this.push = function() { 677 678 // Save current state onto stack 679 context.save(); 680 stackSize++; 681 682 }; 683 684 /** 685 * Pop layer state off stack. 686 */ 687 this.pop = function() { 688 689 // Restore current state from stack 690 if (stackSize > 0) { 691 context.restore(); 692 stackSize--; 693 } 694 695 }; 696 697 /** 698 * Reset the layer, clearing the stack, the current path, and any transform 699 * matrix. 700 */ 701 this.reset = function() { 702 703 // Clear stack 704 while (stackSize > 0) { 705 context.restore(); 706 stackSize--; 707 } 708 709 // Restore to initial state 710 context.restore(); 711 context.save(); 712 713 // Clear path 714 context.beginPath(); 715 pathClosed = false; 716 717 }; 718 719 /** 720 * Sets the given affine transform (defined with six values from the 721 * transform's matrix). 722 * 723 * @param {Number} a The first value in the affine transform's matrix. 724 * @param {Number} b The second value in the affine transform's matrix. 725 * @param {Number} c The third value in the affine transform's matrix. 726 * @param {Number} d The fourth value in the affine transform's matrix. 727 * @param {Number} e The fifth value in the affine transform's matrix. 728 * @param {Number} f The sixth value in the affine transform's matrix. 729 */ 730 this.setTransform = function(a, b, c, d, e, f) { 731 context.setTransform( 732 a, b, c, 733 d, e, f 734 /*0, 0, 1*/ 735 ); 736 }; 737 738 /** 739 * Applies the given affine transform (defined with six values from the 740 * transform's matrix). 741 * 742 * @param {Number} a The first value in the affine transform's matrix. 743 * @param {Number} b The second value in the affine transform's matrix. 744 * @param {Number} c The third value in the affine transform's matrix. 745 * @param {Number} d The fourth value in the affine transform's matrix. 746 * @param {Number} e The fifth value in the affine transform's matrix. 747 * @param {Number} f The sixth value in the affine transform's matrix. 748 */ 749 this.transform = function(a, b, c, d, e, f) { 750 context.transform( 751 a, b, c, 752 d, e, f 753 /*0, 0, 1*/ 754 ); 755 }; 756 757 /** 758 * Sets the channel mask for future operations on this Layer. 759 * 760 * The channel mask is a Guacamole-specific compositing operation identifier 761 * with a single bit representing each of four channels (in order): source 762 * image where destination transparent, source where destination opaque, 763 * destination where source transparent, and destination where source 764 * opaque. 765 * 766 * @param {Number} mask The channel mask for future operations on this 767 * Layer. 768 */ 769 this.setChannelMask = function(mask) { 770 context.globalCompositeOperation = compositeOperation[mask]; 771 }; 772 773 /** 774 * Sets the miter limit for stroke operations using the miter join. This 775 * limit is the maximum ratio of the size of the miter join to the stroke 776 * width. If this ratio is exceeded, the miter will not be drawn for that 777 * joint of the path. 778 * 779 * @param {Number} limit The miter limit for stroke operations using the 780 * miter join. 781 */ 782 this.setMiterLimit = function(limit) { 783 context.miterLimit = limit; 784 }; 785 786 // Initialize canvas dimensions 787 canvas.width = width; 788 canvas.height = height; 789 790 // Explicitly render canvas below other elements in the layer (such as 791 // child layers). Chrome and others may fail to render layers properly 792 // without this. 793 canvas.style.zIndex = -1; 794 795 }; 796 797 /** 798 * Channel mask for the composite operation "rout". 799 */ 800 Guacamole.Layer.ROUT = 0x2; 801 802 /** 803 * Channel mask for the composite operation "atop". 804 */ 805 Guacamole.Layer.ATOP = 0x6; 806 807 /** 808 * Channel mask for the composite operation "xor". 809 */ 810 Guacamole.Layer.XOR = 0xA; 811 812 /** 813 * Channel mask for the composite operation "rover". 814 */ 815 Guacamole.Layer.ROVER = 0xB; 816 817 /** 818 * Channel mask for the composite operation "over". 819 */ 820 Guacamole.Layer.OVER = 0xE; 821 822 /** 823 * Channel mask for the composite operation "plus". 824 */ 825 Guacamole.Layer.PLUS = 0xF; 826 827 /** 828 * Channel mask for the composite operation "rin". 829 * Beware that WebKit-based browsers may leave the contents of the destionation 830 * layer where the source layer is transparent, despite the definition of this 831 * operation. 832 */ 833 Guacamole.Layer.RIN = 0x1; 834 835 /** 836 * Channel mask for the composite operation "in". 837 * Beware that WebKit-based browsers may leave the contents of the destionation 838 * layer where the source layer is transparent, despite the definition of this 839 * operation. 840 */ 841 Guacamole.Layer.IN = 0x4; 842 843 /** 844 * Channel mask for the composite operation "out". 845 * Beware that WebKit-based browsers may leave the contents of the destionation 846 * layer where the source layer is transparent, despite the definition of this 847 * operation. 848 */ 849 Guacamole.Layer.OUT = 0x8; 850 851 /** 852 * Channel mask for the composite operation "ratop". 853 * Beware that WebKit-based browsers may leave the contents of the destionation 854 * layer where the source layer is transparent, despite the definition of this 855 * operation. 856 */ 857 Guacamole.Layer.RATOP = 0x9; 858 859 /** 860 * Channel mask for the composite operation "src". 861 * Beware that WebKit-based browsers may leave the contents of the destionation 862 * layer where the source layer is transparent, despite the definition of this 863 * operation. 864 */ 865 Guacamole.Layer.SRC = 0xC; 866 867 /** 868 * Represents a single pixel of image data. All components have a minimum value 869 * of 0 and a maximum value of 255. 870 * 871 * @constructor 872 * 873 * @param {Number} r The red component of this pixel. 874 * @param {Number} g The green component of this pixel. 875 * @param {Number} b The blue component of this pixel. 876 * @param {Number} a The alpha component of this pixel. 877 */ 878 Guacamole.Layer.Pixel = function(r, g, b, a) { 879 880 /** 881 * The red component of this pixel, where 0 is the minimum value, 882 * and 255 is the maximum. 883 */ 884 this.red = r; 885 886 /** 887 * The green component of this pixel, where 0 is the minimum value, 888 * and 255 is the maximum. 889 */ 890 this.green = g; 891 892 /** 893 * The blue component of this pixel, where 0 is the minimum value, 894 * and 255 is the maximum. 895 */ 896 this.blue = b; 897 898 /** 899 * The alpha component of this pixel, where 0 is the minimum value, 900 * and 255 is the maximum. 901 */ 902 this.alpha = a; 903 904 }; 905