displayContext.save();
/**
+ * A temporary canvas element whose contents can be relied on only
+ * through the duration of an operation.
+ * @private
+ */
+ var temp = document.createElement("canvas");
+
+ /**
+ * The 2D display context of the temporary canvas element.
+ * @private
+ */
+ var tempContext = temp.getContext("2d");
+
+ /**
* 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).
width = newWidth;
height = newHeight;
+ // Acknowledge reset of stack (happens on resize of canvas)
+ stackSize = 0;
+ displayContext.save();
+
}
/**
};
/**
+ * Returns the display context of the canvas element backing this layer.
+ * @returns {CanvasRenderingContext2D} The display context of the canvas
+ * element backing this layer.
+ */
+ this.getContext = function() {
+ return displayContext;
+ };
+
+ /**
* Returns whether this Layer is ready. A Layer is ready if it has no
* pending operations and no operations in-progress.
*
* @param {Number} y The destination Y coordinate.
*/
this.copy = function(srcLayer, srcx, srcy, srcw, srch, x, y) {
- scheduleTaskSynced(srcLayer, function() {
+
+ // If we are copying from ourselves, perform simple drawImage() copy.
+ // No other synchronization is necessary.
+ if (srcLayer === this) {
+
+ scheduleTask(function() {
+ if (layer.autosize != 0) fitRect(x, y, srcw, srch);
+ displayContext.drawImage(srcLayer.getCanvas(), srcx, srcy, srcw, srch, x, y, srcw, srch);
+ });
+
+ return;
+ }
+
+ // Note that source image data MUST be retrieved with getImageData()
+ // rather than drawing directly using the source canvas as an
+ // argument to drawImage(). This is because Canvas implementations may
+ // implement drawImage() lazily, which can cause rendering issues if
+ // the source canvas is updated before drawImage() is actually
+ // performed. Retrieving the actual underlying pixel data with
+ // getImageData() ensures that the image data is truly available.
+
+ // Will contain image data once source layer is ready
+ var data;
+
+ // Draw image data from the source layer any time after the
+ // source layer is ready (the copied image data will be stored
+ // such that the source layer can continue unimpeded).
+ var task = scheduleTask(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);
+ if (srcCanvas.width != 0 && srcCanvas.height != 0) {
+
+ // Copy image data onto temporary canvas
+ temp.width = srcw;
+ temp.height = srch;
+ tempContext.putImageData(data, 0, 0);
+
+ // Draw from temporary canvas
+ displayContext.drawImage(temp, 0, 0, srcw, srch, x, y, srcw, srch);
+
+ }
+ }, true);
+
+ // When source layer is ready, pull data, and unblock draw task
+ srcLayer.sync(function() {
+ data = srcLayer.getContext().getImageData(srcx, srcy, srcw, srch);
+ task.unblock();
});
+
};
/**
- * Add the specified cubic bezier point to the current path.
+ * 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);
+
+ });
+ };
+
+ /**
+ * Starts a new path at the specified point.
+ *
* @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.path = function(x, y, cp1x, cp1y, cp2x, cp2y) {
+ this.curveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
scheduleTask(function() {
// Start a new path if current path is closed
}
if (layer.autosize != 0) fitRect(x, y, 0, 0);
- displayContext.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, y);
+ 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;
});
};
};
/**
- * Applies the given affine transform (defined with three values from the
+ * 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*/
+ );
+
+ });
+ };
+
+
+ /**
+ * 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.
this.transform = function(a, b, c, d, e, f) {
scheduleTask(function() {
- // Clear transform
+ // Apply transform
displayContext.transform(
a, b, c,
d, e, f