Fix jsdoc, add missing documentation.
[guacamole-common-js.git] / src / main / resources / layer.js
index bd6af67..e6052e7 100644 (file)
  *
  * ***** END LICENSE BLOCK ***** */
 
-// Guacamole namespace
+/**
+ * Namespace for all Guacamole JavaScript objects.
+ * @namespace
+ */
 var Guacamole = Guacamole || {};
 
 /**
@@ -83,6 +86,24 @@ Guacamole.Layer = function(width, height) {
     var tasks = new Array();
 
     /**
+     * Whether a new path should be started with the next path drawing
+     * operations.
+     * @private
+     */
+    var pathClosed = true;
+
+    /**
+     * The number of states on the state stack.
+     * 
+     * Note that there will ALWAYS be one element on the stack, but that
+     * element is not exposed. It is only used to reset the layer to its
+     * initial state.
+     * 
+     * @private
+     */
+    var stackSize = 0;
+
+    /**
      * Map of all Guacamole channel masks to HTML5 canvas composite operation
      * names. Not all channel mask combinations are currently implemented.
      * @private
@@ -116,7 +137,6 @@ Guacamole.Layer = function(width, height) {
      */
     function resize(newWidth, newHeight) {
 
-
         // Only preserve old data if width/height are both non-zero
         var oldData = null;
         if (width != 0 && height != 0) {
@@ -135,18 +155,29 @@ Guacamole.Layer = function(width, height) {
 
         }
 
+        // Preserve composite operation
+        var oldCompositeOperation = displayContext.globalCompositeOperation;
+
         // Resize canvas
         display.width = newWidth;
         display.height = newHeight;
 
         // Redraw old data, if any
         if (oldData)
-            displayContext.drawImage(oldData, 
+                displayContext.drawImage(oldData, 
                     0, 0, width, height,
                     0, 0, width, height);
 
+        // Restore composite operation
+        displayContext.globalCompositeOperation = oldCompositeOperation;
+
         width = newWidth;
         height = newHeight;
+
+        // Acknowledge reset of stack (happens on resize of canvas)
+        stackSize = 0;
+        displayContext.save();
+
     }
 
     /**
@@ -201,16 +232,34 @@ Guacamole.Layer = function(width, height) {
      * @private
      * @param {function} taskHandler The function to call when this task 
      *                               runs, if any.
+     * @param {boolean} blocked Whether this task should start blocked.
      */
-    function Task(taskHandler) {
-        
+    function Task(taskHandler, blocked) {
+       
+        var task = this;
+       
+        /**
+         * Whether this Task is blocked.
+         * 
+         * @type boolean
+         */
+        this.blocked = blocked;
+
         /**
          * The handler this Task is associated with, if any.
          * 
          * @type function
          */
         this.handler = taskHandler;
-        
+       
+        /**
+         * Unblocks this Task, allowing it to run.
+         */
+        this.unblock = function() {
+            task.blocked = false;
+            handlePendingTasks();
+        }
+
     }
 
     /**
@@ -220,21 +269,22 @@ Guacamole.Layer = function(width, height) {
      * 
      * @private
      * @param {function} handler The function to call when possible, if any.
+     * @param {boolean} blocked Whether the task should start blocked.
      * @returns {Task} The Task created and added to the queue for future
      *                 running, if any, or null if the handler was run
      *                 immediately and no Task needed to be created.
      */
-    function scheduleTask(handler) {
+    function scheduleTask(handler, blocked) {
         
         // If no pending tasks, just call (if available) and exit
-        if (layer.isReady() && handler != null) {
-            handler();
+        if (layer.isReady() && !blocked) {
+            if (handler) handler();
             return null;
         }
 
         // If tasks are pending/executing, schedule a pending task
         // and return a reference to it.
-        var task = new Task(handler);
+        var task = new Task(handler, blocked);
         tasks.push(task);
         return task;
         
@@ -256,9 +306,9 @@ Guacamole.Layer = function(width, height) {
 
         // Draw all pending tasks.
         var task;
-        while ((task = tasks[0]) != null && task.handler) {
+        while ((task = tasks[0]) != null && !task.blocked) {
             tasks.shift();
-            task.handler();
+            if (task.handler) task.handler();
         }
 
         tasksInProgress = false;
@@ -266,6 +316,69 @@ Guacamole.Layer = function(width, height) {
     }
 
     /**
+     * Schedules a task within the current layer just as scheduleTast() does,
+     * except that another specified layer will be blocked until this task
+     * completes, and this task will not start until the other layer is
+     * ready.
+     * 
+     * Essentially, a task is scheduled in both layers, and the specified task
+     * will only be performed once both layers are ready, and neither layer may
+     * proceed until this task completes.
+     * 
+     * Note that there is no way to specify whether the task starts blocked,
+     * as whether the task is blocked depends completely on whether the
+     * other layer is currently ready.
+     * 
+     * @private
+     * @param {Guacamole.Layer} otherLayer The other layer which must be blocked
+     *                          until this task completes.
+     * @param {function} handler The function to call when possible.
+     */
+    function scheduleTaskSynced(otherLayer, handler) {
+
+        // If we ARE the other layer, no need to sync.
+        // Syncing would result in deadlock.
+        if (layer === otherLayer)
+            scheduleTask(handler);
+
+        // Otherwise synchronize operation with other layer
+        else {
+
+            var drawComplete = false;
+            var layerLock = null;
+
+            function performTask() {
+
+                // Perform task
+                handler();
+
+                // Unblock the other layer now that draw is complete
+                if (layerLock != null) 
+                    layerLock.unblock();
+
+                // Flag operation as done
+                drawComplete = true;
+
+            }
+
+            // Currently blocked draw task
+            var task = scheduleTask(performTask, true);
+
+            // Unblock draw task once source layer is ready
+            otherLayer.sync(task.unblock);
+
+            // Block other layer until draw completes
+            // Note that the draw MAY have already been performed at this point,
+            // in which case creating a lock on the other layer will lead to
+            // deadlock (the draw task has already run and will thus never
+            // clear the lock)
+            if (!drawComplete)
+                layerLock = otherLayer.sync(null, true);
+
+        }
+    }
+
+    /**
      * Set to true if this Layer should resize itself to accomodate the
      * dimensions of any drawing operation, and false (the default) otherwise.
      * 
@@ -345,21 +458,14 @@ Guacamole.Layer = function(width, height) {
      * @param {String} url The URL of the image to draw.
      */
     this.draw = function(x, y, url) {
-        var task = scheduleTask(null);
 
-        var image = new Image();
-        image.onload = function() {
-
-            task.handler = function() {
-                if (layer.autosize != 0) fitRect(x, y, image.width, image.height);
-                displayContext.drawImage(image, x, y);
-            };
-
-            // As this task originally had no handler and may have blocked
-            // other tasks, handle any blocked tasks.
-            handlePendingTasks();
+        var task = scheduleTask(function() {
+            if (layer.autosize != 0) fitRect(x, y, image.width, image.height);
+            displayContext.drawImage(image, x, y);
+        }, true);
 
-        };
+        var image = new Image();
+        image.onload = task.unblock;
         image.src = url;
 
     };
@@ -370,9 +476,79 @@ Guacamole.Layer = function(width, height) {
      * 
      * @param {function} handler The function to call once all currently
      *                           pending operations are complete.
+     * @param {boolean} blocked Whether the task should start blocked.
+     */
+    this.sync = scheduleTask;
+
+    /**
+     * Transfer a rectangle of image data from one Layer to this Layer using the
+     * specified transfer function.
+     * 
+     * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
+     * @param {Number} srcx The X coordinate of the upper-left corner of the
+     *                      rectangle within the source Layer's coordinate
+     *                      space to copy data from.
+     * @param {Number} srcy The Y coordinate of the upper-left corner of the
+     *                      rectangle within the source Layer's coordinate
+     *                      space to copy data from.
+     * @param {Number} srcw The width of the rectangle within the source Layer's
+     *                      coordinate space to copy data from.
+     * @param {Number} srch The height of the rectangle within the source
+     *                      Layer's coordinate space to copy data from.
+     * @param {Number} x The destination X coordinate.
+     * @param {Number} y The destination Y coordinate.
+     * @param {Function} transferFunction The transfer function to use to
+     *                                    transfer data from source to
+     *                                    destination.
      */
-    this.sync = function(handler) {
-        scheduleTask(handler);
+    this.transfer = function(srcLayer, srcx, srcy, srcw, srch, x, y, transferFunction) {
+        scheduleTaskSynced(srcLayer, function() {
+
+            if (layer.autosize != 0) fitRect(x, y, srcw, srch);
+
+            var srcCanvas = srcLayer.getCanvas();
+            if (srcCanvas.width != 0 && srcCanvas.height != 0) {
+
+                // Get image data from src and dst
+                var src = srcLayer.getCanvas().getContext("2d").getImageData(srcx, srcy, srcw, srch);
+                var dst = displayContext.getImageData(x , y, srcw, srch);
+
+                // Apply transfer for each pixel
+                for (var i=0; i<srcw*srch*4; i+=4) {
+
+                    // Get source pixel environment
+                    var src_pixel = new Guacamole.Layer.Pixel(
+                        src.data[i],
+                        src.data[i+1],
+                        src.data[i+2],
+                        src.data[i+3]
+                    );
+                        
+                    // Get destination pixel environment
+                    var dst_pixel = new Guacamole.Layer.Pixel(
+                        dst.data[i],
+                        dst.data[i+1],
+                        dst.data[i+2],
+                        dst.data[i+3]
+                    );
+
+                    // Apply transfer function
+                    transferFunction(src_pixel, dst_pixel);
+
+                    // Save pixel data
+                    dst.data[i  ] = dst_pixel.red;
+                    dst.data[i+1] = dst_pixel.green;
+                    dst.data[i+2] = dst_pixel.blue;
+                    dst.data[i+3] = dst_pixel.alpha;
+
+                }
+
+                // Draw image data
+                displayContext.putImageData(dst, x, y);
+
+            }
+
+        });
     };
 
     /**
@@ -396,53 +572,128 @@ Guacamole.Layer = function(width, height) {
      * @param {Number} x The destination X coordinate.
      * @param {Number} y The destination Y coordinate.
      */
-    this.copyRect = function(srcLayer, srcx, srcy, srcw, srch, x, y) {
-
-        function doCopyRect() {
+    this.copy = function(srcLayer, srcx, srcy, srcw, srch, x, y) {
+        scheduleTaskSynced(srcLayer, function() {
             if (layer.autosize != 0) fitRect(x, y, srcw, srch);
-            displayContext.drawImage(srcLayer.getCanvas(), srcx, srcy, srcw, srch, x, y, srcw, srch);
-        }
 
-        // If we ARE the source layer, no need to sync.
-        // Syncing would result in deadlock.
-        if (layer === srcLayer)
-            scheduleTask(doCopyRect);
+            var srcCanvas = srcLayer.getCanvas();
+            if (srcCanvas.width != 0 && srcCanvas.height != 0)
+                displayContext.drawImage(srcCanvas, srcx, srcy, srcw, srch, x, y, srcw, srch);
 
-        // Otherwise synchronize copy operation with source layer
-        else {
-            var task = scheduleTask(null);
-            srcLayer.sync(function() {
-                
-                task.handler = doCopyRect;
+        });
+    };
 
-                // As this task originally had no handler and may have blocked
-                // other tasks, handle any blocked tasks.
-                handlePendingTasks();
+    /**
+     * Starts a new path at the specified point.
+     * 
+     * @param {Number} x The X coordinate of the point to draw.
+     * @param {Number} y The Y coordinate of the point to draw.
+     */
+    this.moveTo = function(x, y) {
+        scheduleTask(function() {
+            
+            // Start a new path if current path is closed
+            if (pathClosed) {
+                displayContext.beginPath();
+                pathClosed = false;
+            }
+            
+            if (layer.autosize != 0) fitRect(x, y, 0, 0);
+            displayContext.moveTo(x, y);
+            
+        });
+    };
 
-            });
-        }
+    /**
+     * Add the specified line to the current path.
+     * 
+     * @param {Number} x The X coordinate of the endpoint of the line to draw.
+     * @param {Number} y The Y coordinate of the endpoint of the line to draw.
+     */
+    this.lineTo = function(x, y) {
+        scheduleTask(function() {
+            
+            // Start a new path if current path is closed
+            if (pathClosed) {
+                displayContext.beginPath();
+                pathClosed = false;
+            }
+            
+            if (layer.autosize != 0) fitRect(x, y, 0, 0);
+            displayContext.lineTo(x, y);
+            
+        });
+    };
 
+    /**
+     * Add the specified arc to the current path.
+     * 
+     * @param {Number} x The X coordinate of the center of the circle which
+     *                   will contain the arc.
+     * @param {Number} y The Y coordinate of the center of the circle which
+     *                   will contain the arc.
+     * @param {Number} radius The radius of the circle.
+     * @param {Number} startAngle The starting angle of the arc, in radians.
+     * @param {Number} endAngle The ending angle of the arc, in radians.
+     * @param {Boolean} negative Whether the arc should be drawn in order of
+     *                           decreasing angle.
+     */
+    this.arc = function(x, y, radius, startAngle, endAngle, negative) {
+        scheduleTask(function() {
+            
+            // Start a new path if current path is closed
+            if (pathClosed) {
+                displayContext.beginPath();
+                pathClosed = false;
+            }
+            
+            if (layer.autosize != 0) fitRect(x, y, 0, 0);
+            displayContext.arc(x, y, radius, startAngle, endAngle, negative);
+            
+        });
     };
 
     /**
-     * Clear the specified rectangle of image data.
+     * Starts a new path at the specified point.
      * 
-     * @param {Number} x The X coordinate of the upper-left corner of the
-     *                   rectangle to clear.
-     * @param {Number} y The Y coordinate of the upper-left corner of the
-     *                   rectangle to clear.
-     * @param {Number} w The width of the rectangle to clear.
-     * @param {Number} h The height of the rectangle to clear.
+     * @param {Number} cp1x The X coordinate of the first control point.
+     * @param {Number} cp1y The Y coordinate of the first control point.
+     * @param {Number} cp2x The X coordinate of the second control point.
+     * @param {Number} cp2y The Y coordinate of the second control point.
+     * @param {Number} x The X coordinate of the endpoint of the curve.
+     * @param {Number} y The Y coordinate of the endpoint of the curve.
      */
-    this.clearRect = function(x, y, w, h) {
+    this.curveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
         scheduleTask(function() {
-            if (layer.autosize != 0) fitRect(x, y, w, h);
-            displayContext.clearRect(x, y, w, h);
+            
+            // Start a new path if current path is closed
+            if (pathClosed) {
+                displayContext.beginPath();
+                pathClosed = false;
+            }
+            
+            if (layer.autosize != 0) fitRect(x, y, 0, 0);
+            displayContext.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
+            
+        });
+    };
+
+    /**
+     * Closes the current path by connecting the end point with the start
+     * point (if any) with a straight line.
+     */
+    this.close = function() {
+        scheduleTask(function() {
+            
+            // Close path
+            displayContext.closePath();
+            pathClosed = true;
+            
         });
     };
 
     /**
-     * Fill the specified rectangle of image data with the specified color.
+     * Add the specified rectangle to the current path.
      * 
      * @param {Number} x The X coordinate of the upper-left corner of the
      *                   rectangle to draw.
@@ -450,72 +701,264 @@ Guacamole.Layer = function(width, height) {
      *                   rectangle to draw.
      * @param {Number} w The width of the rectangle to draw.
      * @param {Number} h The height of the rectangle to draw.
-     * @param {Number} r The red component of the color of the rectangle.
-     * @param {Number} g The green component of the color of the rectangle.
-     * @param {Number} b The blue component of the color of the rectangle.
-     * @param {Number} a The alpha component of the color of the rectangle.
      */
-    this.drawRect = function(x, y, w, h, r, g, b, a) {
+    this.rect = function(x, y, w, h) {
         scheduleTask(function() {
+            
+            // Start a new path if current path is closed
+            if (pathClosed) {
+                displayContext.beginPath();
+                pathClosed = false;
+            }
+            
             if (layer.autosize != 0) fitRect(x, y, w, h);
-            displayContext.fillStyle = "rgba("
-                        + r + "," + g + "," + b + "," + a / 255 + ")";
-            displayContext.fillRect(x, y, w, h);
+            displayContext.rect(x, y, w, h);
+            
         });
     };
 
     /**
-     * Clip all future drawing operations by the specified rectangle.
+     * Clip all future drawing operations by the current path. The current path
+     * is implicitly closed. The current path can continue to be reused
+     * for other operations (such as fillColor()) but a new path will be started
+     * once a path drawing operation (path() or rect()) is used.
+     */
+    this.clip = function() {
+        scheduleTask(function() {
+
+            // Set new clipping region
+            displayContext.clip();
+
+            // Path now implicitly closed
+            pathClosed = true;
+
+        });
+    };
+
+    /**
+     * Stroke the current path with the specified color. The current path
+     * is implicitly closed. The current path can continue to be reused
+     * for other operations (such as clip()) but a new path will be started
+     * once a path drawing operation (path() or rect()) is used.
      * 
-     * @param {Number} x The X coordinate of the upper-left corner of the
-     *                   rectangle to use for the clipping region.
-     * @param {Number} y The Y coordinate of the upper-left corner of the
-     *                   rectangle to use for the clipping region.
-     * @param {Number} w The width of the rectangle to use for the clipping region.
-     * @param {Number} h The height of the rectangle to use for the clipping region.
+     * @param {String} cap The line cap style. Can be "round", "square",
+     *                     or "butt".
+     * @param {String} join The line join style. Can be "round", "bevel",
+     *                      or "miter".
+     * @param {Number} thickness The line thickness in pixels.
+     * @param {Number} r The red component of the color to fill.
+     * @param {Number} g The green component of the color to fill.
+     * @param {Number} b The blue component of the color to fill.
+     * @param {Number} a The alpha component of the color to fill.
      */
-    this.clipRect = function(x, y, w, h) {
+    this.strokeColor = function(cap, join, thickness, r, g, b, a) {
         scheduleTask(function() {
 
-            // Clear any current clipping region
-            displayContext.restore();
+            // Stroke with color
+            displayContext.lineCap = cap;
+            displayContext.lineJoin = join;
+            displayContext.lineWidth = thickness;
+            displayContext.strokeStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")";
+            displayContext.stroke();
+
+            // Path now implicitly closed
+            pathClosed = true;
+
+        });
+    };
+
+    /**
+     * Fills the current path with the specified color. The current path
+     * is implicitly closed. The current path can continue to be reused
+     * for other operations (such as clip()) but a new path will be started
+     * once a path drawing operation (path() or rect()) is used.
+     * 
+     * @param {Number} r The red component of the color to fill.
+     * @param {Number} g The green component of the color to fill.
+     * @param {Number} b The blue component of the color to fill.
+     * @param {Number} a The alpha component of the color to fill.
+     */
+    this.fillColor = function(r, g, b, a) {
+        scheduleTask(function() {
+
+            // Fill with color
+            displayContext.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")";
+            displayContext.fill();
+
+            // Path now implicitly closed
+            pathClosed = true;
+
+        });
+    };
+
+    /**
+     * Stroke the current path with the image within the specified layer. The
+     * image data will be tiled infinitely within the stroke. The current path
+     * is implicitly closed. The current path can continue to be reused
+     * for other operations (such as clip()) but a new path will be started
+     * once a path drawing operation (path() or rect()) is used.
+     * 
+     * @param {String} cap The line cap style. Can be "round", "square",
+     *                     or "butt".
+     * @param {String} join The line join style. Can be "round", "bevel",
+     *                      or "miter".
+     * @param {Number} thickness The line thickness in pixels.
+     * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern
+     *                                   within the stroke.
+     */
+    this.strokeLayer = function(cap, join, thickness, srcLayer) {
+        scheduleTaskSynced(srcLayer, function() {
+
+            // Stroke with image data
+            displayContext.lineCap = cap;
+            displayContext.lineJoin = join;
+            displayContext.lineWidth = thickness;
+            displayContext.strokeStyle = displayContext.createPattern(
+                srcLayer.getCanvas(),
+                "repeat"
+            );
+            displayContext.stroke();
+
+            // Path now implicitly closed
+            pathClosed = true;
+
+        });
+    };
+
+    /**
+     * Fills the current path with the image within the specified layer. The
+     * image data will be tiled infinitely within the stroke. The current path
+     * is implicitly closed. The current path can continue to be reused
+     * for other operations (such as clip()) but a new path will be started
+     * once a path drawing operation (path() or rect()) is used.
+     * 
+     * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern
+     *                                   within the fill.
+     */
+    this.fillLayer = function(srcLayer) {
+        scheduleTask(function() {
+
+            // Fill with image data 
+            displayContext.fillStyle = displayContext.createPattern(
+                srcLayer.getCanvas(),
+                "repeat"
+            );
+            displayContext.fill();
+
+            // Path now implicitly closed
+            pathClosed = true;
+
+        });
+    };
+
+    /**
+     * Push current layer state onto stack.
+     */
+    this.push = function() {
+        scheduleTask(function() {
+
+            // Save current state onto stack
             displayContext.save();
+            stackSize++;
 
-            if (layer.autosize != 0) fitRect(x, y, w, h);
+        });
+    };
 
-            // Set new clipping region
+    /**
+     * Pop layer state off stack.
+     */
+    this.pop = function() {
+        scheduleTask(function() {
+
+            // Restore current state from stack
+            if (stackSize > 0) {
+                displayContext.restore();
+                stackSize--;
+            }
+
+        });
+    };
+
+    /**
+     * Reset the layer, clearing the stack, the current path, and any transform
+     * matrix.
+     */
+    this.reset = function() {
+        scheduleTask(function() {
+
+            // Clear stack
+            while (stackSize > 0) {
+                displayContext.restore();
+                stackSize--;
+            }
+
+            // Restore to initial state
+            displayContext.restore();
+            displayContext.save();
+
+            // Clear path
             displayContext.beginPath();
-            displayContext.rect(x, y, w, h);
-            displayContext.clip();
+            pathClosed = false;
 
         });
     };
 
     /**
-     * Provides the given filtering function with a writable snapshot of
-     * image data and the current width and height of the Layer.
+     * Sets the given affine transform (defined with six values from the
+     * transform's matrix).
      * 
-     * @param {function} filter A function which accepts an array of image
-     *                          data (as returned by the canvas element's
-     *                          display context's getImageData() function),
-     *                          the width of the Layer, and the height of the
-     *                          Layer as parameters, in that order. This
-     *                          function must accomplish its filtering by
-     *                          modifying the given image data array directly.
+     * @param {Number} a The first value in the affine transform's matrix.
+     * @param {Number} b The second value in the affine transform's matrix.
+     * @param {Number} c The third value in the affine transform's matrix.
+     * @param {Number} d The fourth value in the affine transform's matrix.
+     * @param {Number} e The fifth value in the affine transform's matrix.
+     * @param {Number} f The sixth value in the affine transform's matrix.
      */
-    this.filter = function(filter) {
+    this.setTransform = function(a, b, c, d, e, f) {
         scheduleTask(function() {
-            var imageData = displayContext.getImageData(0, 0, width, height);
-            filter(imageData.data, width, height);
-            displayContext.putImageData(imageData, 0, 0);
+
+            // Set transform
+            displayContext.setTransform(
+                a, b, c,
+                d, e, f
+              /*0, 0, 1*/
+            );
+
         });
     };
 
+
     /**
-     * Sets the channel mask for future operations on this Layer. The channel
-     * mask is a Guacamole-specific compositing operation identifier with a
-     * single bit representing each of four channels (in order): source image
-     * where destination transparent, source where destination opaque,
+     * Applies the given affine transform (defined with six values from the
+     * transform's matrix).
+     * 
+     * @param {Number} a The first value in the affine transform's matrix.
+     * @param {Number} b The second value in the affine transform's matrix.
+     * @param {Number} c The third value in the affine transform's matrix.
+     * @param {Number} d The fourth value in the affine transform's matrix.
+     * @param {Number} e The fifth value in the affine transform's matrix.
+     * @param {Number} f The sixth value in the affine transform's matrix.
+     */
+    this.transform = function(a, b, c, d, e, f) {
+        scheduleTask(function() {
+
+            // Apply transform
+            displayContext.transform(
+                a, b, c,
+                d, e, f
+              /*0, 0, 1*/
+            );
+
+        });
+    };
+
+
+    /**
+     * Sets the channel mask for future operations on this Layer.
+     * 
+     * The channel mask is a Guacamole-specific compositing operation identifier
+     * with a single bit representing each of four channels (in order): source
+     * image where destination transparent, source where destination opaque,
      * destination where source transparent, and destination where source
      * opaque.
      * 
@@ -528,8 +971,133 @@ Guacamole.Layer = function(width, height) {
         });
     };
 
+    /**
+     * Sets the miter limit for stroke operations using the miter join. This
+     * limit is the maximum ratio of the size of the miter join to the stroke
+     * width. If this ratio is exceeded, the miter will not be drawn for that
+     * joint of the path.
+     * 
+     * @param {Number} limit The miter limit for stroke operations using the
+     *                       miter join.
+     */
+    this.setMiterLimit = function(limit) {
+        scheduleTask(function() {
+            displayContext.miterLimit = limit;
+        });
+    };
+
     // Initialize canvas dimensions
     display.width = width;
     display.height = height;
 
 };
+
+/**
+ * Channel mask for the composite operation "rout".
+ */
+Guacamole.Layer.ROUT  = 0x2;
+
+/**
+ * Channel mask for the composite operation "atop".
+ */
+Guacamole.Layer.ATOP  = 0x6;
+
+/**
+ * Channel mask for the composite operation "xor".
+ */
+Guacamole.Layer.XOR   = 0xA;
+
+/**
+ * Channel mask for the composite operation "rover".
+ */
+Guacamole.Layer.ROVER = 0xB;
+
+/**
+ * Channel mask for the composite operation "over".
+ */
+Guacamole.Layer.OVER  = 0xE;
+
+/**
+ * Channel mask for the composite operation "plus".
+ */
+Guacamole.Layer.PLUS  = 0xF;
+
+/**
+ * Channel mask for the composite operation "rin".
+ * Beware that WebKit-based browsers may leave the contents of the destionation
+ * layer where the source layer is transparent, despite the definition of this
+ * operation.
+ */
+Guacamole.Layer.RIN   = 0x1;
+
+/**
+ * Channel mask for the composite operation "in".
+ * Beware that WebKit-based browsers may leave the contents of the destionation
+ * layer where the source layer is transparent, despite the definition of this
+ * operation.
+ */
+Guacamole.Layer.IN    = 0x4;
+
+/**
+ * Channel mask for the composite operation "out".
+ * Beware that WebKit-based browsers may leave the contents of the destionation
+ * layer where the source layer is transparent, despite the definition of this
+ * operation.
+ */
+Guacamole.Layer.OUT   = 0x8;
+
+/**
+ * Channel mask for the composite operation "ratop".
+ * Beware that WebKit-based browsers may leave the contents of the destionation
+ * layer where the source layer is transparent, despite the definition of this
+ * operation.
+ */
+Guacamole.Layer.RATOP = 0x9;
+
+/**
+ * Channel mask for the composite operation "src".
+ * Beware that WebKit-based browsers may leave the contents of the destionation
+ * layer where the source layer is transparent, despite the definition of this
+ * operation.
+ */
+Guacamole.Layer.SRC   = 0xC;
+
+
+/**
+ * Represents a single pixel of image data. All components have a minimum value
+ * of 0 and a maximum value of 255.
+ * 
+ * @constructor
+ * 
+ * @param {Number} r The red component of this pixel.
+ * @param {Number} g The green component of this pixel.
+ * @param {Number} b The blue component of this pixel.
+ * @param {Number} a The alpha component of this pixel.
+ */
+Guacamole.Layer.Pixel = function(r, g, b, a) {
+
+    /**
+     * The red component of this pixel, where 0 is the minimum value,
+     * and 255 is the maximum.
+     */
+    this.red   = r;
+
+    /**
+     * The green component of this pixel, where 0 is the minimum value,
+     * and 255 is the maximum.
+     */
+    this.green = g;
+
+    /**
+     * The blue component of this pixel, where 0 is the minimum value,
+     * and 255 is the maximum.
+     */
+    this.blue  = b;
+
+    /**
+     * The alpha component of this pixel, where 0 is the minimum value,
+     * and 255 is the maximum.
+     */
+    this.alpha = a;
+
+};