Fix jsdoc, add missing documentation.
[guacamole-common-js.git] / src / main / resources / layer.js
1
2 /* ***** BEGIN LICENSE BLOCK *****
3  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4  *
5  * The contents of this file are subject to the Mozilla Public License Version
6  * 1.1 (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  * http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the
13  * License.
14  *
15  * The Original Code is guacamole-common-js.
16  *
17  * The Initial Developer of the Original Code is
18  * Michael Jumper.
19  * Portions created by the Initial Developer are Copyright (C) 2010
20  * the Initial Developer. All Rights Reserved.
21  *
22  * Contributor(s):
23  *
24  * Alternatively, the contents of this file may be used under the terms of
25  * either the GNU General Public License Version 2 or later (the "GPL"), or
26  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27  * in which case the provisions of the GPL or the LGPL are applicable instead
28  * of those above. If you wish to allow use of your version of this file only
29  * under the terms of either the GPL or the LGPL, and not to allow others to
30  * use your version of this file under the terms of the MPL, indicate your
31  * decision by deleting the provisions above and replace them with the notice
32  * and other provisions required by the GPL or the LGPL. If you do not delete
33  * the provisions above, a recipient may use your version of this file under
34  * the terms of any one of the MPL, the GPL or the LGPL.
35  *
36  * ***** END LICENSE BLOCK ***** */
37
38 /**
39  * Namespace for all Guacamole JavaScript objects.
40  * @namespace
41  */
42 var Guacamole = Guacamole || {};
43
44 /**
45  * Abstract ordered drawing surface. Each Layer contains a canvas element and
46  * provides simple drawing instructions for drawing to that canvas element,
47  * however unlike the canvas element itself, drawing operations on a Layer are
48  * guaranteed to run in order, even if such an operation must wait for an image
49  * to load before completing.
50  * 
51  * @constructor
52  * 
53  * @param {Number} width The width of the Layer, in pixels. The canvas element
54  *                       backing this Layer will be given this width.
55  *                       
56  * @param {Number} height The height of the Layer, in pixels. The canvas element
57  *                        backing this Layer will be given this height.
58  */
59 Guacamole.Layer = function(width, height) {
60
61     /**
62      * Reference to this Layer.
63      * @private
64      */
65     var layer = this;
66
67     /**
68      * The canvas element backing this Layer.
69      * @private
70      */
71     var display = document.createElement("canvas");
72
73     /**
74      * The 2D display context of the canvas element backing this Layer.
75      * @private
76      */
77     var displayContext = display.getContext("2d");
78     displayContext.save();
79
80     /**
81      * The queue of all pending Tasks. Tasks will be run in order, with new
82      * tasks added at the end of the queue and old tasks removed from the
83      * front of the queue (FIFO).
84      * @private
85      */
86     var tasks = new Array();
87
88     /**
89      * Whether a new path should be started with the next path drawing
90      * operations.
91      * @private
92      */
93     var pathClosed = true;
94
95     /**
96      * The number of states on the state stack.
97      * 
98      * Note that there will ALWAYS be one element on the stack, but that
99      * element is not exposed. It is only used to reset the layer to its
100      * initial state.
101      * 
102      * @private
103      */
104     var stackSize = 0;
105
106     /**
107      * Map of all Guacamole channel masks to HTML5 canvas composite operation
108      * names. Not all channel mask combinations are currently implemented.
109      * @private
110      */
111     var compositeOperation = {
112      /* 0x0 NOT IMPLEMENTED */
113         0x1: "destination-in",
114         0x2: "destination-out",
115      /* 0x3 NOT IMPLEMENTED */
116         0x4: "source-in",
117      /* 0x5 NOT IMPLEMENTED */
118         0x6: "source-atop",
119      /* 0x7 NOT IMPLEMENTED */
120         0x8: "source-out",
121         0x9: "destination-atop",
122         0xA: "xor",
123         0xB: "destination-over",
124         0xC: "copy",
125      /* 0xD NOT IMPLEMENTED */
126         0xE: "source-over",
127         0xF: "lighter"
128     };
129
130     /**
131      * Resizes the canvas element backing this Layer without testing the
132      * new size. This function should only be used internally.
133      * 
134      * @private
135      * @param {Number} newWidth The new width to assign to this Layer.
136      * @param {Number} newHeight The new height to assign to this Layer.
137      */
138     function resize(newWidth, newHeight) {
139
140         // Only preserve old data if width/height are both non-zero
141         var oldData = null;
142         if (width != 0 && height != 0) {
143
144             // Create canvas and context for holding old data
145             oldData = document.createElement("canvas");
146             oldData.width = width;
147             oldData.height = height;
148
149             var oldDataContext = oldData.getContext("2d");
150
151             // Copy image data from current
152             oldDataContext.drawImage(display,
153                     0, 0, width, height,
154                     0, 0, width, height);
155
156         }
157
158         // Preserve composite operation
159         var oldCompositeOperation = displayContext.globalCompositeOperation;
160
161         // Resize canvas
162         display.width = newWidth;
163         display.height = newHeight;
164
165         // Redraw old data, if any
166         if (oldData)
167                 displayContext.drawImage(oldData, 
168                     0, 0, width, height,
169                     0, 0, width, height);
170
171         // Restore composite operation
172         displayContext.globalCompositeOperation = oldCompositeOperation;
173
174         width = newWidth;
175         height = newHeight;
176
177         // Acknowledge reset of stack (happens on resize of canvas)
178         stackSize = 0;
179         displayContext.save();
180
181     }
182
183     /**
184      * Given the X and Y coordinates of the upper-left corner of a rectangle
185      * and the rectangle's width and height, resize the backing canvas element
186      * as necessary to ensure that the rectangle fits within the canvas
187      * element's coordinate space. This function will only make the canvas
188      * larger. If the rectangle already fits within the canvas element's
189      * coordinate space, the canvas is left unchanged.
190      * 
191      * @private
192      * @param {Number} x The X coordinate of the upper-left corner of the
193      *                   rectangle to fit.
194      * @param {Number} y The Y coordinate of the upper-left corner of the
195      *                   rectangle to fit.
196      * @param {Number} w The width of the the rectangle to fit.
197      * @param {Number} h The height of the the rectangle to fit.
198      */
199     function fitRect(x, y, w, h) {
200         
201         // Calculate bounds
202         var opBoundX = w + x;
203         var opBoundY = h + y;
204         
205         // Determine max width
206         var resizeWidth;
207         if (opBoundX > width)
208             resizeWidth = opBoundX;
209         else
210             resizeWidth = width;
211
212         // Determine max height
213         var resizeHeight;
214         if (opBoundY > height)
215             resizeHeight = opBoundY;
216         else
217             resizeHeight = height;
218
219         // Resize if necessary
220         if (resizeWidth != width || resizeHeight != height)
221             resize(resizeWidth, resizeHeight);
222
223     }
224
225     /**
226      * A container for an task handler. Each operation which must be ordered
227      * is associated with a Task that goes into a task queue. Tasks in this
228      * queue are executed in order once their handlers are set, while Tasks 
229      * without handlers block themselves and any following Tasks from running.
230      *
231      * @constructor
232      * @private
233      * @param {function} taskHandler The function to call when this task 
234      *                               runs, if any.
235      * @param {boolean} blocked Whether this task should start blocked.
236      */
237     function Task(taskHandler, blocked) {
238        
239         var task = this;
240        
241         /**
242          * Whether this Task is blocked.
243          * 
244          * @type boolean
245          */
246         this.blocked = blocked;
247
248         /**
249          * The handler this Task is associated with, if any.
250          * 
251          * @type function
252          */
253         this.handler = taskHandler;
254        
255         /**
256          * Unblocks this Task, allowing it to run.
257          */
258         this.unblock = function() {
259             task.blocked = false;
260             handlePendingTasks();
261         }
262
263     }
264
265     /**
266      * If no tasks are pending or running, run the provided handler immediately,
267      * if any. Otherwise, schedule a task to run immediately after all currently
268      * running or pending tasks are complete.
269      * 
270      * @private
271      * @param {function} handler The function to call when possible, if any.
272      * @param {boolean} blocked Whether the task should start blocked.
273      * @returns {Task} The Task created and added to the queue for future
274      *                 running, if any, or null if the handler was run
275      *                 immediately and no Task needed to be created.
276      */
277     function scheduleTask(handler, blocked) {
278         
279         // If no pending tasks, just call (if available) and exit
280         if (layer.isReady() && !blocked) {
281             if (handler) handler();
282             return null;
283         }
284
285         // If tasks are pending/executing, schedule a pending task
286         // and return a reference to it.
287         var task = new Task(handler, blocked);
288         tasks.push(task);
289         return task;
290         
291     }
292
293     var tasksInProgress = false;
294
295     /**
296      * Run any Tasks which were pending but are now ready to run and are not
297      * blocked by other Tasks.
298      * @private
299      */
300     function handlePendingTasks() {
301
302         if (tasksInProgress)
303             return;
304
305         tasksInProgress = true;
306
307         // Draw all pending tasks.
308         var task;
309         while ((task = tasks[0]) != null && !task.blocked) {
310             tasks.shift();
311             if (task.handler) task.handler();
312         }
313
314         tasksInProgress = false;
315
316     }
317
318     /**
319      * Schedules a task within the current layer just as scheduleTast() does,
320      * except that another specified layer will be blocked until this task
321      * completes, and this task will not start until the other layer is
322      * ready.
323      * 
324      * Essentially, a task is scheduled in both layers, and the specified task
325      * will only be performed once both layers are ready, and neither layer may
326      * proceed until this task completes.
327      * 
328      * Note that there is no way to specify whether the task starts blocked,
329      * as whether the task is blocked depends completely on whether the
330      * other layer is currently ready.
331      * 
332      * @private
333      * @param {Guacamole.Layer} otherLayer The other layer which must be blocked
334      *                          until this task completes.
335      * @param {function} handler The function to call when possible.
336      */
337     function scheduleTaskSynced(otherLayer, handler) {
338
339         // If we ARE the other layer, no need to sync.
340         // Syncing would result in deadlock.
341         if (layer === otherLayer)
342             scheduleTask(handler);
343
344         // Otherwise synchronize operation with other layer
345         else {
346
347             var drawComplete = false;
348             var layerLock = null;
349
350             function performTask() {
351
352                 // Perform task
353                 handler();
354
355                 // Unblock the other layer now that draw is complete
356                 if (layerLock != null) 
357                     layerLock.unblock();
358
359                 // Flag operation as done
360                 drawComplete = true;
361
362             }
363
364             // Currently blocked draw task
365             var task = scheduleTask(performTask, true);
366
367             // Unblock draw task once source layer is ready
368             otherLayer.sync(task.unblock);
369
370             // Block other layer until draw completes
371             // Note that the draw MAY have already been performed at this point,
372             // in which case creating a lock on the other layer will lead to
373             // deadlock (the draw task has already run and will thus never
374             // clear the lock)
375             if (!drawComplete)
376                 layerLock = otherLayer.sync(null, true);
377
378         }
379     }
380
381     /**
382      * Set to true if this Layer should resize itself to accomodate the
383      * dimensions of any drawing operation, and false (the default) otherwise.
384      * 
385      * Note that setting this property takes effect immediately, and thus may
386      * take effect on operations that were started in the past but have not
387      * yet completed. If you wish the setting of this flag to only modify
388      * future operations, you will need to make the setting of this flag an
389      * operation with sync().
390      * 
391      * @example
392      * // Set autosize to true for all future operations
393      * layer.sync(function() {
394      *     layer.autosize = true;
395      * });
396      * 
397      * @type Boolean
398      * @default false
399      */
400     this.autosize = false;
401
402     /**
403      * Returns the canvas element backing this Layer.
404      * @returns {Element} The canvas element backing this Layer.
405      */
406     this.getCanvas = function() {
407         return display;
408     };
409
410     /**
411      * Returns whether this Layer is ready. A Layer is ready if it has no
412      * pending operations and no operations in-progress.
413      * 
414      * @returns {Boolean} true if this Layer is ready, false otherwise.
415      */
416     this.isReady = function() {
417         return tasks.length == 0;
418     };
419
420     /**
421      * Changes the size of this Layer to the given width and height. Resizing
422      * is only attempted if the new size provided is actually different from
423      * the current size.
424      * 
425      * @param {Number} newWidth The new width to assign to this Layer.
426      * @param {Number} newHeight The new height to assign to this Layer.
427      */
428     this.resize = function(newWidth, newHeight) {
429         scheduleTask(function() {
430             if (newWidth != width || newHeight != height)
431                 resize(newWidth, newHeight);
432         });
433     };
434
435     /**
436      * Draws the specified image at the given coordinates. The image specified
437      * must already be loaded.
438      * 
439      * @param {Number} x The destination X coordinate.
440      * @param {Number} y The destination Y coordinate.
441      * @param {Image} image The image to draw. Note that this is an Image
442      *                      object - not a URL.
443      */
444     this.drawImage = function(x, y, image) {
445         scheduleTask(function() {
446             if (layer.autosize != 0) fitRect(x, y, image.width, image.height);
447             displayContext.drawImage(image, x, y);
448         });
449     };
450
451     /**
452      * Draws the image at the specified URL at the given coordinates. The image
453      * will be loaded automatically, and this and any future operations will
454      * wait for the image to finish loading.
455      * 
456      * @param {Number} x The destination X coordinate.
457      * @param {Number} y The destination Y coordinate.
458      * @param {String} url The URL of the image to draw.
459      */
460     this.draw = function(x, y, url) {
461
462         var task = scheduleTask(function() {
463             if (layer.autosize != 0) fitRect(x, y, image.width, image.height);
464             displayContext.drawImage(image, x, y);
465         }, true);
466
467         var image = new Image();
468         image.onload = task.unblock;
469         image.src = url;
470
471     };
472
473     /**
474      * Run an arbitrary function as soon as currently pending operations
475      * are complete.
476      * 
477      * @param {function} handler The function to call once all currently
478      *                           pending operations are complete.
479      * @param {boolean} blocked Whether the task should start blocked.
480      */
481     this.sync = scheduleTask;
482
483     /**
484      * Transfer a rectangle of image data from one Layer to this Layer using the
485      * specified transfer function.
486      * 
487      * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
488      * @param {Number} srcx The X coordinate of the upper-left corner of the
489      *                      rectangle within the source Layer's coordinate
490      *                      space to copy data from.
491      * @param {Number} srcy The Y coordinate of the upper-left corner of the
492      *                      rectangle within the source Layer's coordinate
493      *                      space to copy data from.
494      * @param {Number} srcw The width of the rectangle within the source Layer's
495      *                      coordinate space to copy data from.
496      * @param {Number} srch The height of the rectangle within the source
497      *                      Layer's coordinate space to copy data from.
498      * @param {Number} x The destination X coordinate.
499      * @param {Number} y The destination Y coordinate.
500      * @param {Function} transferFunction The transfer function to use to
501      *                                    transfer data from source to
502      *                                    destination.
503      */
504     this.transfer = function(srcLayer, srcx, srcy, srcw, srch, x, y, transferFunction) {
505         scheduleTaskSynced(srcLayer, function() {
506
507             if (layer.autosize != 0) fitRect(x, y, srcw, srch);
508
509             var srcCanvas = srcLayer.getCanvas();
510             if (srcCanvas.width != 0 && srcCanvas.height != 0) {
511
512                 // Get image data from src and dst
513                 var src = srcLayer.getCanvas().getContext("2d").getImageData(srcx, srcy, srcw, srch);
514                 var dst = displayContext.getImageData(x , y, srcw, srch);
515
516                 // Apply transfer for each pixel
517                 for (var i=0; i<srcw*srch*4; i+=4) {
518
519                     // Get source pixel environment
520                     var src_pixel = new Guacamole.Layer.Pixel(
521                         src.data[i],
522                         src.data[i+1],
523                         src.data[i+2],
524                         src.data[i+3]
525                     );
526                         
527                     // Get destination pixel environment
528                     var dst_pixel = new Guacamole.Layer.Pixel(
529                         dst.data[i],
530                         dst.data[i+1],
531                         dst.data[i+2],
532                         dst.data[i+3]
533                     );
534
535                     // Apply transfer function
536                     transferFunction(src_pixel, dst_pixel);
537
538                     // Save pixel data
539                     dst.data[i  ] = dst_pixel.red;
540                     dst.data[i+1] = dst_pixel.green;
541                     dst.data[i+2] = dst_pixel.blue;
542                     dst.data[i+3] = dst_pixel.alpha;
543
544                 }
545
546                 // Draw image data
547                 displayContext.putImageData(dst, x, y);
548
549             }
550
551         });
552     };
553
554     /**
555      * Copy a rectangle of image data from one Layer to this Layer. This
556      * operation will copy exactly the image data that will be drawn once all
557      * operations of the source Layer that were pending at the time this
558      * function was called are complete. This operation will not alter the
559      * size of the source Layer even if its autosize property is set to true.
560      * 
561      * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
562      * @param {Number} srcx The X coordinate of the upper-left corner of the
563      *                      rectangle within the source Layer's coordinate
564      *                      space to copy data from.
565      * @param {Number} srcy The Y coordinate of the upper-left corner of the
566      *                      rectangle within the source Layer's coordinate
567      *                      space to copy data from.
568      * @param {Number} srcw The width of the rectangle within the source Layer's
569      *                      coordinate space to copy data from.
570      * @param {Number} srch The height of the rectangle within the source
571      *                      Layer's coordinate space to copy data from.
572      * @param {Number} x The destination X coordinate.
573      * @param {Number} y The destination Y coordinate.
574      */
575     this.copy = function(srcLayer, srcx, srcy, srcw, srch, x, y) {
576         scheduleTaskSynced(srcLayer, function() {
577             if (layer.autosize != 0) fitRect(x, y, srcw, srch);
578
579             var srcCanvas = srcLayer.getCanvas();
580             if (srcCanvas.width != 0 && srcCanvas.height != 0)
581                 displayContext.drawImage(srcCanvas, srcx, srcy, srcw, srch, x, y, srcw, srch);
582
583         });
584     };
585
586     /**
587      * Starts a new path at the specified point.
588      * 
589      * @param {Number} x The X coordinate of the point to draw.
590      * @param {Number} y The Y coordinate of the point to draw.
591      */
592     this.moveTo = function(x, y) {
593         scheduleTask(function() {
594             
595             // Start a new path if current path is closed
596             if (pathClosed) {
597                 displayContext.beginPath();
598                 pathClosed = false;
599             }
600             
601             if (layer.autosize != 0) fitRect(x, y, 0, 0);
602             displayContext.moveTo(x, y);
603             
604         });
605     };
606
607     /**
608      * Add the specified line to the current path.
609      * 
610      * @param {Number} x The X coordinate of the endpoint of the line to draw.
611      * @param {Number} y The Y coordinate of the endpoint of the line to draw.
612      */
613     this.lineTo = function(x, y) {
614         scheduleTask(function() {
615             
616             // Start a new path if current path is closed
617             if (pathClosed) {
618                 displayContext.beginPath();
619                 pathClosed = false;
620             }
621             
622             if (layer.autosize != 0) fitRect(x, y, 0, 0);
623             displayContext.lineTo(x, y);
624             
625         });
626     };
627
628     /**
629      * Add the specified arc to the current path.
630      * 
631      * @param {Number} x The X coordinate of the center of the circle which
632      *                   will contain the arc.
633      * @param {Number} y The Y coordinate of the center of the circle which
634      *                   will contain the arc.
635      * @param {Number} radius The radius of the circle.
636      * @param {Number} startAngle The starting angle of the arc, in radians.
637      * @param {Number} endAngle The ending angle of the arc, in radians.
638      * @param {Boolean} negative Whether the arc should be drawn in order of
639      *                           decreasing angle.
640      */
641     this.arc = function(x, y, radius, startAngle, endAngle, negative) {
642         scheduleTask(function() {
643             
644             // Start a new path if current path is closed
645             if (pathClosed) {
646                 displayContext.beginPath();
647                 pathClosed = false;
648             }
649             
650             if (layer.autosize != 0) fitRect(x, y, 0, 0);
651             displayContext.arc(x, y, radius, startAngle, endAngle, negative);
652             
653         });
654     };
655
656     /**
657      * Starts a new path at the specified point.
658      * 
659      * @param {Number} cp1x The X coordinate of the first control point.
660      * @param {Number} cp1y The Y coordinate of the first control point.
661      * @param {Number} cp2x The X coordinate of the second control point.
662      * @param {Number} cp2y The Y coordinate of the second control point.
663      * @param {Number} x The X coordinate of the endpoint of the curve.
664      * @param {Number} y The Y coordinate of the endpoint of the curve.
665      */
666     this.curveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
667         scheduleTask(function() {
668             
669             // Start a new path if current path is closed
670             if (pathClosed) {
671                 displayContext.beginPath();
672                 pathClosed = false;
673             }
674             
675             if (layer.autosize != 0) fitRect(x, y, 0, 0);
676             displayContext.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
677             
678         });
679     };
680
681     /**
682      * Closes the current path by connecting the end point with the start
683      * point (if any) with a straight line.
684      */
685     this.close = function() {
686         scheduleTask(function() {
687             
688             // Close path
689             displayContext.closePath();
690             pathClosed = true;
691             
692         });
693     };
694
695     /**
696      * Add the specified rectangle to the current path.
697      * 
698      * @param {Number} x The X coordinate of the upper-left corner of the
699      *                   rectangle to draw.
700      * @param {Number} y The Y coordinate of the upper-left corner of the
701      *                   rectangle to draw.
702      * @param {Number} w The width of the rectangle to draw.
703      * @param {Number} h The height of the rectangle to draw.
704      */
705     this.rect = function(x, y, w, h) {
706         scheduleTask(function() {
707             
708             // Start a new path if current path is closed
709             if (pathClosed) {
710                 displayContext.beginPath();
711                 pathClosed = false;
712             }
713             
714             if (layer.autosize != 0) fitRect(x, y, w, h);
715             displayContext.rect(x, y, w, h);
716             
717         });
718     };
719
720     /**
721      * Clip all future drawing operations by the current path. The current path
722      * is implicitly closed. The current path can continue to be reused
723      * for other operations (such as fillColor()) but a new path will be started
724      * once a path drawing operation (path() or rect()) is used.
725      */
726     this.clip = function() {
727         scheduleTask(function() {
728
729             // Set new clipping region
730             displayContext.clip();
731
732             // Path now implicitly closed
733             pathClosed = true;
734
735         });
736     };
737
738     /**
739      * Stroke the current path with the specified color. The current path
740      * is implicitly closed. The current path can continue to be reused
741      * for other operations (such as clip()) but a new path will be started
742      * once a path drawing operation (path() or rect()) is used.
743      * 
744      * @param {String} cap The line cap style. Can be "round", "square",
745      *                     or "butt".
746      * @param {String} join The line join style. Can be "round", "bevel",
747      *                      or "miter".
748      * @param {Number} thickness The line thickness in pixels.
749      * @param {Number} r The red component of the color to fill.
750      * @param {Number} g The green component of the color to fill.
751      * @param {Number} b The blue component of the color to fill.
752      * @param {Number} a The alpha component of the color to fill.
753      */
754     this.strokeColor = function(cap, join, thickness, r, g, b, a) {
755         scheduleTask(function() {
756
757             // Stroke with color
758             displayContext.lineCap = cap;
759             displayContext.lineJoin = join;
760             displayContext.lineWidth = thickness;
761             displayContext.strokeStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")";
762             displayContext.stroke();
763
764             // Path now implicitly closed
765             pathClosed = true;
766
767         });
768     };
769
770     /**
771      * Fills the current path with the specified color. The current path
772      * is implicitly closed. The current path can continue to be reused
773      * for other operations (such as clip()) but a new path will be started
774      * once a path drawing operation (path() or rect()) is used.
775      * 
776      * @param {Number} r The red component of the color to fill.
777      * @param {Number} g The green component of the color to fill.
778      * @param {Number} b The blue component of the color to fill.
779      * @param {Number} a The alpha component of the color to fill.
780      */
781     this.fillColor = function(r, g, b, a) {
782         scheduleTask(function() {
783
784             // Fill with color
785             displayContext.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")";
786             displayContext.fill();
787
788             // Path now implicitly closed
789             pathClosed = true;
790
791         });
792     };
793
794     /**
795      * Stroke the current path with the image within the specified layer. The
796      * image data will be tiled infinitely within the stroke. The current path
797      * is implicitly closed. The current path can continue to be reused
798      * for other operations (such as clip()) but a new path will be started
799      * once a path drawing operation (path() or rect()) is used.
800      * 
801      * @param {String} cap The line cap style. Can be "round", "square",
802      *                     or "butt".
803      * @param {String} join The line join style. Can be "round", "bevel",
804      *                      or "miter".
805      * @param {Number} thickness The line thickness in pixels.
806      * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern
807      *                                   within the stroke.
808      */
809     this.strokeLayer = function(cap, join, thickness, srcLayer) {
810         scheduleTaskSynced(srcLayer, function() {
811
812             // Stroke with image data
813             displayContext.lineCap = cap;
814             displayContext.lineJoin = join;
815             displayContext.lineWidth = thickness;
816             displayContext.strokeStyle = displayContext.createPattern(
817                 srcLayer.getCanvas(),
818                 "repeat"
819             );
820             displayContext.stroke();
821
822             // Path now implicitly closed
823             pathClosed = true;
824
825         });
826     };
827
828     /**
829      * Fills the current path with the image within the specified layer. The
830      * image data will be tiled infinitely within the stroke. The current path
831      * is implicitly closed. The current path can continue to be reused
832      * for other operations (such as clip()) but a new path will be started
833      * once a path drawing operation (path() or rect()) is used.
834      * 
835      * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern
836      *                                   within the fill.
837      */
838     this.fillLayer = function(srcLayer) {
839         scheduleTask(function() {
840
841             // Fill with image data 
842             displayContext.fillStyle = displayContext.createPattern(
843                 srcLayer.getCanvas(),
844                 "repeat"
845             );
846             displayContext.fill();
847
848             // Path now implicitly closed
849             pathClosed = true;
850
851         });
852     };
853
854     /**
855      * Push current layer state onto stack.
856      */
857     this.push = function() {
858         scheduleTask(function() {
859
860             // Save current state onto stack
861             displayContext.save();
862             stackSize++;
863
864         });
865     };
866
867     /**
868      * Pop layer state off stack.
869      */
870     this.pop = function() {
871         scheduleTask(function() {
872
873             // Restore current state from stack
874             if (stackSize > 0) {
875                 displayContext.restore();
876                 stackSize--;
877             }
878
879         });
880     };
881
882     /**
883      * Reset the layer, clearing the stack, the current path, and any transform
884      * matrix.
885      */
886     this.reset = function() {
887         scheduleTask(function() {
888
889             // Clear stack
890             while (stackSize > 0) {
891                 displayContext.restore();
892                 stackSize--;
893             }
894
895             // Restore to initial state
896             displayContext.restore();
897             displayContext.save();
898
899             // Clear path
900             displayContext.beginPath();
901             pathClosed = false;
902
903         });
904     };
905
906     /**
907      * Sets the given affine transform (defined with six values from the
908      * transform's matrix).
909      * 
910      * @param {Number} a The first value in the affine transform's matrix.
911      * @param {Number} b The second value in the affine transform's matrix.
912      * @param {Number} c The third value in the affine transform's matrix.
913      * @param {Number} d The fourth value in the affine transform's matrix.
914      * @param {Number} e The fifth value in the affine transform's matrix.
915      * @param {Number} f The sixth value in the affine transform's matrix.
916      */
917     this.setTransform = function(a, b, c, d, e, f) {
918         scheduleTask(function() {
919
920             // Set transform
921             displayContext.setTransform(
922                 a, b, c,
923                 d, e, f
924               /*0, 0, 1*/
925             );
926
927         });
928     };
929
930
931     /**
932      * Applies the given affine transform (defined with six values from the
933      * transform's matrix).
934      * 
935      * @param {Number} a The first value in the affine transform's matrix.
936      * @param {Number} b The second value in the affine transform's matrix.
937      * @param {Number} c The third value in the affine transform's matrix.
938      * @param {Number} d The fourth value in the affine transform's matrix.
939      * @param {Number} e The fifth value in the affine transform's matrix.
940      * @param {Number} f The sixth value in the affine transform's matrix.
941      */
942     this.transform = function(a, b, c, d, e, f) {
943         scheduleTask(function() {
944
945             // Apply transform
946             displayContext.transform(
947                 a, b, c,
948                 d, e, f
949               /*0, 0, 1*/
950             );
951
952         });
953     };
954
955
956     /**
957      * Sets the channel mask for future operations on this Layer.
958      * 
959      * The channel mask is a Guacamole-specific compositing operation identifier
960      * with a single bit representing each of four channels (in order): source
961      * image where destination transparent, source where destination opaque,
962      * destination where source transparent, and destination where source
963      * opaque.
964      * 
965      * @param {Number} mask The channel mask for future operations on this
966      *                      Layer.
967      */
968     this.setChannelMask = function(mask) {
969         scheduleTask(function() {
970             displayContext.globalCompositeOperation = compositeOperation[mask];
971         });
972     };
973
974     /**
975      * Sets the miter limit for stroke operations using the miter join. This
976      * limit is the maximum ratio of the size of the miter join to the stroke
977      * width. If this ratio is exceeded, the miter will not be drawn for that
978      * joint of the path.
979      * 
980      * @param {Number} limit The miter limit for stroke operations using the
981      *                       miter join.
982      */
983     this.setMiterLimit = function(limit) {
984         scheduleTask(function() {
985             displayContext.miterLimit = limit;
986         });
987     };
988
989     // Initialize canvas dimensions
990     display.width = width;
991     display.height = height;
992
993 };
994
995 /**
996  * Channel mask for the composite operation "rout".
997  */
998 Guacamole.Layer.ROUT  = 0x2;
999
1000 /**
1001  * Channel mask for the composite operation "atop".
1002  */
1003 Guacamole.Layer.ATOP  = 0x6;
1004
1005 /**
1006  * Channel mask for the composite operation "xor".
1007  */
1008 Guacamole.Layer.XOR   = 0xA;
1009
1010 /**
1011  * Channel mask for the composite operation "rover".
1012  */
1013 Guacamole.Layer.ROVER = 0xB;
1014
1015 /**
1016  * Channel mask for the composite operation "over".
1017  */
1018 Guacamole.Layer.OVER  = 0xE;
1019
1020 /**
1021  * Channel mask for the composite operation "plus".
1022  */
1023 Guacamole.Layer.PLUS  = 0xF;
1024
1025 /**
1026  * Channel mask for the composite operation "rin".
1027  * Beware that WebKit-based browsers may leave the contents of the destionation
1028  * layer where the source layer is transparent, despite the definition of this
1029  * operation.
1030  */
1031 Guacamole.Layer.RIN   = 0x1;
1032
1033 /**
1034  * Channel mask for the composite operation "in".
1035  * Beware that WebKit-based browsers may leave the contents of the destionation
1036  * layer where the source layer is transparent, despite the definition of this
1037  * operation.
1038  */
1039 Guacamole.Layer.IN    = 0x4;
1040
1041 /**
1042  * Channel mask for the composite operation "out".
1043  * Beware that WebKit-based browsers may leave the contents of the destionation
1044  * layer where the source layer is transparent, despite the definition of this
1045  * operation.
1046  */
1047 Guacamole.Layer.OUT   = 0x8;
1048
1049 /**
1050  * Channel mask for the composite operation "ratop".
1051  * Beware that WebKit-based browsers may leave the contents of the destionation
1052  * layer where the source layer is transparent, despite the definition of this
1053  * operation.
1054  */
1055 Guacamole.Layer.RATOP = 0x9;
1056
1057 /**
1058  * Channel mask for the composite operation "src".
1059  * Beware that WebKit-based browsers may leave the contents of the destionation
1060  * layer where the source layer is transparent, despite the definition of this
1061  * operation.
1062  */
1063 Guacamole.Layer.SRC   = 0xC;
1064
1065
1066 /**
1067  * Represents a single pixel of image data. All components have a minimum value
1068  * of 0 and a maximum value of 255.
1069  * 
1070  * @constructor
1071  * 
1072  * @param {Number} r The red component of this pixel.
1073  * @param {Number} g The green component of this pixel.
1074  * @param {Number} b The blue component of this pixel.
1075  * @param {Number} a The alpha component of this pixel.
1076  */
1077 Guacamole.Layer.Pixel = function(r, g, b, a) {
1078
1079     /**
1080      * The red component of this pixel, where 0 is the minimum value,
1081      * and 255 is the maximum.
1082      */
1083     this.red   = r;
1084
1085     /**
1086      * The green component of this pixel, where 0 is the minimum value,
1087      * and 255 is the maximum.
1088      */
1089     this.green = g;
1090
1091     /**
1092      * The blue component of this pixel, where 0 is the minimum value,
1093      * and 255 is the maximum.
1094      */
1095     this.blue  = b;
1096
1097     /**
1098      * The alpha component of this pixel, where 0 is the minimum value,
1099      * and 255 is the maximum.
1100      */
1101     this.alpha = a;
1102
1103 };