*
* ***** END LICENSE BLOCK ***** */
-// Guacamole namespace
-var Guacamole = Guacamole || {};
-
-/**
- * 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.
+ * Namespace for all Guacamole JavaScript objects.
+ * @namespace
*/
-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;
+var Guacamole = Guacamole || {};
/**
* Abstract ordered drawing surface. Each Layer contains a canvas element and
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
*/
function resize(newWidth, newHeight) {
-
// Only preserve old data if width/height are both non-zero
var oldData = null;
if (width != 0 && height != 0) {
}
+ // Preserve composite operation
+ var oldCompositeOperation = displayContext.globalCompositeOperation;
+
// Resize canvas
display.width = newWidth;
display.height = newHeight;
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();
+
}
/**
}
/**
+ * 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.
*
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
* @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.
* 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 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,
+ * 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.
*
});
};
+ /**
+ * 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;
+
+};