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