}
/**
+ * 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.
+ *
+ * @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.
*
* destination.
*/
this.transfer = function(srcLayer, srcx, srcy, srcw, srch, x, y, transferFunction) {
+ scheduleTaskSynced(srcLayer, function() {
- var drawComplete = false;
- var srcLock = null;
-
- function doTransfer() {
if (layer.autosize != 0) fitRect(x, y, srcw, srch);
var srcCanvas = srcLayer.getCanvas();
}
- // 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(doTransfer);
-
- // Otherwise synchronize copy operation with source layer
- else {
-
- // Currently blocked draw task
- var task = scheduleTask(doTransfer, 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);
-
- }
-
+ });
};
/**
* @param {Number} y The destination Y coordinate.
*/
this.copy = function(srcLayer, srcx, srcy, srcw, srch, x, y) {
-
- var drawComplete = false;
- var srcLock = null;
-
- function doCopy() {
+ 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(doCopy);
-
- // Otherwise synchronize copy operation with source layer
- else {
-
- // Currently blocked draw task
- var task = scheduleTask(doCopy, 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);
-
- }
-
+ });
};
/**
* 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 {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.strokeColor = function(r, g, b, a) {
+ this.strokeColor = function(cap, join, thickness, r, g, b, a) {
scheduleTask(function() {
// Stroke with color
+ displayContext.lineCap = cap;
+ displayContext.lineJoin = join;
+ displayContext.lineWidth = thickness;
displayContext.strokeStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")";
displayContext.stroke();
});
};
-
/**
* Fills the current path with the specified color. The current path
* is implicitly closed. The current path can continue to be reused
};
/**
+ * 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() {
};
/**
+ * Applies the given affine transform (defined with three 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() {
+
+ // Clear 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
});
};
+ /**
+ * 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;