Fix jsdoc, add missing documentation.
[guacamole-common-js.git] / src / main / resources / layer.js
index 790fd01..e6052e7 100644 (file)
  *
  * ***** END LICENSE BLOCK ***** */
 
-// Guacamole namespace
+/**
+ * Namespace for all Guacamole JavaScript objects.
+ * @namespace
+ */
 var Guacamole = Guacamole || {};
 
 /**
@@ -75,12 +78,6 @@ Guacamole.Layer = function(width, height) {
     displayContext.save();
 
     /**
-     * The function to apply when drawing arbitrary source pixels over
-     * destination pixels.
-     */
-    var transferFunction = null;
-
-    /**
      * The queue of all pending Tasks. Tasks will be run in order, with new
      * tasks added at the end of the queue and old tasks removed from the
      * front of the queue (FIFO).
@@ -89,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
@@ -113,36 +128,6 @@ Guacamole.Layer = function(width, height) {
     };
 
     /**
-     * Map of all Guacamole binary raster operations to transfer functions.
-     * @private
-     */
-    var binaryCompositeTransferFunction = {
-
-        0x10: function (src, dst) { return 0x00;         }, /* BLACK */
-        0x1F: function (src, dst) { return 0xFF;         }, /* WHITE */
-
-        0x13: function (src, dst) { return src;          }, /* SRC */
-        0x15: function (src, dst) { return dst;          }, /* DEST */
-        0x1C: function (src, dst) { return ~src;         }, /* NSRC */
-        0x1A: function (src, dst) { return ~dst;         }, /* NDEST */
-
-        0x11: function (src, dst) { return src & dst;    }, /* AND */
-        0x1E: function (src, dst) { return ~(src & dst); }, /* NAND */
-
-        0x17: function (src, dst) { return src | dst;    }, /* OR */
-        0x18: function (src, dst) { return ~(src | dst); }, /* NOR */
-
-        0x16: function (src, dst) { return src ^ dst;    }, /* XOR */
-        0x19: function (src, dst) { return ~(src ^ dst); }, /* XNOR */
-
-        0x14: function (src, dst) { return ~src & dst;   }, /* AND inverted source */
-        0x1D: function (src, dst) { return ~src | dst;   }, /* OR inverted source */
-        0x12: function (src, dst) { return src & ~dst;   }, /* AND inverted destination */
-        0x1B: function (src, dst) { return src | ~dst;   }  /* OR inverted destination */
-
-    };
-
-    /**
      * Resizes the canvas element backing this Layer without testing the
      * new size. This function should only be used internally.
      * 
@@ -189,6 +174,10 @@ Guacamole.Layer = function(width, height) {
         width = newWidth;
         height = newHeight;
 
+        // Acknowledge reset of stack (happens on resize of canvas)
+        stackSize = 0;
+        displayContext.save();
+
     }
 
     /**
@@ -327,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.
      * 
@@ -429,6 +481,77 @@ Guacamole.Layer = function(width, height) {
     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.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);
+
+            }
+
+        });
+    };
+
+    /**
      * Copy a rectangle of image data from one Layer to this Layer. This
      * operation will copy exactly the image data that will be drawn once all
      * operations of the source Layer that were pending at the time this
@@ -449,71 +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) {
-
-        var drawComplete = false;
-        var srcLock = null;
-
-        function doCopyRect() {
+    this.copy = function(srcLayer, srcx, srcy, srcw, srch, x, y) {
+        scheduleTaskSynced(srcLayer, function() {
             if (layer.autosize != 0) fitRect(x, y, srcw, srch);
 
             var srcCanvas = srcLayer.getCanvas();
             if (srcCanvas.width != 0 && srcCanvas.height != 0)
                 displayContext.drawImage(srcCanvas, srcx, srcy, srcw, srch, x, y, srcw, srch);
 
-            // Unblock the source layer now that draw is complete
-            if (srcLock != null) 
-                srcLock.unblock();
-
-            // Flag operation as done
-            drawComplete = true;
-        }
-
-        // If we ARE the source layer, no need to sync.
-        // Syncing would result in deadlock.
-        if (layer === srcLayer)
-            scheduleTask(doCopyRect);
+        });
+    };
 
-        // Otherwise synchronize copy operation with source layer
-        else {
+    /**
+     * 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() {
             
-            // Currently blocked draw task
-            var task = scheduleTask(doCopyRect, true);
-
-            // Unblock draw task once source layer is ready
-            srcLayer.sync(task.unblock);
-
-            // Block source layer until draw completes
-            // Note that the draw MAY have already been performed at this point,
-            // in which case creating a lock on the source layer will lead to
-            // deadlock (the draw task has already run and will thus never
-            // clear the lock)
-            if (!drawComplete)
-                srcLock = srcLayer.sync(null, true);
+            // 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);
+            
         });
     };
 
     /**
-     * Fill the specified rectangle of image data with the specified color.
+     * 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;
+            
+        });
+    };
+
+    /**
+     * Add the specified rectangle to the current path.
      * 
      * @param {Number} x The X coordinate of the upper-left corner of the
      *                   rectangle to draw.
@@ -521,71 +701,260 @@ 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 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;
+
         });
     };
 
     /**
-     * Clip all future drawing operations by the specified rectangle.
+     * 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;
+
+        });
+    };
+
+    /**
+     * Sets 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.setTransform = function(a, b, c, d, e, f) {
+        scheduleTask(function() {
+
+            // Set transform
+            displayContext.setTransform(
+                a, b, c,
+                d, e, f
+              /*0, 0, 1*/
+            );
 
         });
     };
 
+
     /**
-     * Provides the given filtering function with a writable snapshot of
-     * image data and the current width and height of the Layer.
+     * Applies 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.transform = 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);
+
+            // Apply transform
+            displayContext.transform(
+                a, b, c,
+                d, e, f
+              /*0, 0, 1*/
+            );
+
         });
     };
 
+
     /**
-     * Sets the composite operation for future operations on this Layer. This
-     * operation is either a channel mask, or the ID of a binary raster
-     * operation.
+     * 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
@@ -593,25 +962,27 @@ Guacamole.Layer = function(width, height) {
      * destination where source transparent, and destination where source
      * opaque.
      * 
-     * @param {Number} operation The composite operation (channel mask or binary
-     *                           raster operation) for future operations on this
-     *                           Layer.
+     * @param {Number} mask The channel mask for future operations on this
+     *                      Layer.
      */
-    this.setCompositeOperation = function(operation) {
+    this.setChannelMask = function(mask) {
         scheduleTask(function() {
-            
-            // If channel mask, set composite operation only
-            if (operation <= 0xF) {
-                displayContext.globalCompositeOperation = compositeOperation[operation];
-                transferFunction = null;
-            }
-
-            // Otherwise, set binary raster operation
-            else {
-                displayContext.globalCompositeOperation = "source-over";
-                transferFunction = binaryCompositeTransferFunction[operation];
-            }
+            displayContext.globalCompositeOperation = compositeOperation[mask];
+        });
+    };
 
+    /**
+     * 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;
         });
     };
 
@@ -691,3 +1062,42 @@ Guacamole.Layer.RATOP = 0x9;
  */
 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;
+
+};