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