mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07:41 +00:00
Initial refactor of raster operations to real transfer functions.
This commit is contained in:
@@ -67,6 +67,36 @@ Guacamole.Client = function(tunnel) {
|
|||||||
var displayWidth = 0;
|
var displayWidth = 0;
|
||||||
var displayHeight = 0;
|
var displayHeight = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of all Guacamole binary raster operations to transfer functions.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
var transferFunctions = {
|
||||||
|
|
||||||
|
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 */
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
// Create display
|
// Create display
|
||||||
var display = document.createElement("div");
|
var display = document.createElement("div");
|
||||||
display.style.position = "relative";
|
display.style.position = "relative";
|
||||||
@@ -84,7 +114,7 @@ Guacamole.Client = function(tunnel) {
|
|||||||
|
|
||||||
// Create cursor layer
|
// Create cursor layer
|
||||||
var cursor = new Guacamole.Client.LayerContainer(0, 0);
|
var cursor = new Guacamole.Client.LayerContainer(0, 0);
|
||||||
cursor.getLayer().setCompositeOperation(Guacamole.Layer.SRC);
|
cursor.getLayer().setChannelMask(Guacamole.Layer.SRC);
|
||||||
|
|
||||||
// Position cursor layer
|
// Position cursor layer
|
||||||
var cursor_element = cursor.getElement();
|
var cursor_element = cursor.getElement();
|
||||||
@@ -349,7 +379,7 @@ Guacamole.Client = function(tunnel) {
|
|||||||
var y = parseInt(parameters[3]);
|
var y = parseInt(parameters[3]);
|
||||||
var data = parameters[4];
|
var data = parameters[4];
|
||||||
|
|
||||||
layer.setCompositeOperation(channelMask);
|
layer.setChannelMask(channelMask);
|
||||||
|
|
||||||
layer.draw(
|
layer.draw(
|
||||||
x,
|
x,
|
||||||
@@ -375,7 +405,7 @@ Guacamole.Client = function(tunnel) {
|
|||||||
var dstX = parseInt(parameters[7]);
|
var dstX = parseInt(parameters[7]);
|
||||||
var dstY = parseInt(parameters[8]);
|
var dstY = parseInt(parameters[8]);
|
||||||
|
|
||||||
dstL.setCompositeOperation(channelMask);
|
dstL.setChannelMask(channelMask);
|
||||||
|
|
||||||
dstL.copyRect(
|
dstL.copyRect(
|
||||||
srcL,
|
srcL,
|
||||||
@@ -389,6 +419,31 @@ Guacamole.Client = function(tunnel) {
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"transfer": function(parameters) {
|
||||||
|
|
||||||
|
var srcL = getLayer(parseInt(parameters[0]));
|
||||||
|
var srcX = parseInt(parameters[1]);
|
||||||
|
var srcY = parseInt(parameters[2]);
|
||||||
|
var srcWidth = parseInt(parameters[3]);
|
||||||
|
var srcHeight = parseInt(parameters[4]);
|
||||||
|
var transferFunction = transferFunctions[parameters[5]];
|
||||||
|
var dstL = getLayer(parseInt(parameters[6]));
|
||||||
|
var dstX = parseInt(parameters[7]);
|
||||||
|
var dstY = parseInt(parameters[8]);
|
||||||
|
|
||||||
|
dstL.transfer(
|
||||||
|
srcL,
|
||||||
|
srcX,
|
||||||
|
srcY,
|
||||||
|
srcWidth,
|
||||||
|
srcHeight,
|
||||||
|
dstX,
|
||||||
|
dstY,
|
||||||
|
transferFunction
|
||||||
|
);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
"rect": function(parameters) {
|
"rect": function(parameters) {
|
||||||
|
|
||||||
var channelMask = parseInt(parameters[0]);
|
var channelMask = parseInt(parameters[0]);
|
||||||
@@ -402,7 +457,7 @@ Guacamole.Client = function(tunnel) {
|
|||||||
var b = parseInt(parameters[8]);
|
var b = parseInt(parameters[8]);
|
||||||
var a = parseInt(parameters[9]);
|
var a = parseInt(parameters[9]);
|
||||||
|
|
||||||
layer.setCompositeOperation(channelMask);
|
layer.setChannelMask(channelMask);
|
||||||
|
|
||||||
layer.drawRect(
|
layer.drawRect(
|
||||||
x, y, w, h,
|
x, y, w, h,
|
||||||
|
@@ -74,12 +74,6 @@ Guacamole.Layer = function(width, height) {
|
|||||||
var displayContext = display.getContext("2d");
|
var displayContext = display.getContext("2d");
|
||||||
displayContext.save();
|
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
|
* 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
|
* tasks added at the end of the queue and old tasks removed from the
|
||||||
@@ -112,36 +106,6 @@ Guacamole.Layer = function(width, height) {
|
|||||||
0xF: "lighter"
|
0xF: "lighter"
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* Resizes the canvas element backing this Layer without testing the
|
||||||
* new size. This function should only be used internally.
|
* new size. This function should only be used internally.
|
||||||
@@ -428,6 +392,86 @@ Guacamole.Layer = function(width, height) {
|
|||||||
*/
|
*/
|
||||||
this.sync = scheduleTask;
|
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) {
|
||||||
|
|
||||||
|
var drawComplete = false;
|
||||||
|
var srcLock = null;
|
||||||
|
|
||||||
|
function doTransfer() {
|
||||||
|
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) {
|
||||||
|
dst.data[i ] = transferFunction(src.data[i ], dst.data[i ]);
|
||||||
|
dst.data[i+1] = transferFunction(src.data[i+1], dst.data[i+1]);
|
||||||
|
dst.data[i+2] = transferFunction(src.data[i+2], dst.data[i+2]);
|
||||||
|
dst.data[i+3] = 0xFF; // Assume output opaque
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy a rectangle of image data from one Layer to this Layer. This
|
* 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
|
* operation will copy exactly the image data that will be drawn once all
|
||||||
@@ -458,29 +502,8 @@ Guacamole.Layer = function(width, height) {
|
|||||||
if (layer.autosize != 0) fitRect(x, y, srcw, srch);
|
if (layer.autosize != 0) fitRect(x, y, srcw, srch);
|
||||||
|
|
||||||
var srcCanvas = srcLayer.getCanvas();
|
var srcCanvas = srcLayer.getCanvas();
|
||||||
if (srcCanvas.width != 0 && srcCanvas.height != 0) {
|
if (srcCanvas.width != 0 && srcCanvas.height != 0)
|
||||||
|
displayContext.drawImage(srcCanvas, srcx, srcy, srcw, srch, x, y, srcw, srch);
|
||||||
// Just copy if no transfer function
|
|
||||||
if (!transferFunction)
|
|
||||||
displayContext.drawImage(srcCanvas, srcx, srcy, srcw, srch, x, y, srcw, srch);
|
|
||||||
|
|
||||||
// Otherwise, copy via transfer function
|
|
||||||
else {
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
dst.data[i ] = transferFunction(src.data[i ], dst.data[i ]);
|
|
||||||
dst.data[i+1] = transferFunction(src.data[i+1], dst.data[i+1]);
|
|
||||||
dst.data[i+2] = transferFunction(src.data[i+2], dst.data[i+2]);
|
|
||||||
dst.data[i+3] = 0xFF; // Assume output opaque
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unblock the source layer now that draw is complete
|
// Unblock the source layer now that draw is complete
|
||||||
if (srcLock != null)
|
if (srcLock != null)
|
||||||
@@ -604,9 +627,7 @@ Guacamole.Layer = function(width, height) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the composite operation for future operations on this Layer. This
|
* Sets the channel mask for future operations on this Layer.
|
||||||
* operation is either a channel mask, or the ID of a binary raster
|
|
||||||
* operation.
|
|
||||||
*
|
*
|
||||||
* The channel mask is a Guacamole-specific compositing operation identifier
|
* The channel mask is a Guacamole-specific compositing operation identifier
|
||||||
* with a single bit representing each of four channels (in order): source
|
* with a single bit representing each of four channels (in order): source
|
||||||
@@ -614,25 +635,12 @@ Guacamole.Layer = function(width, height) {
|
|||||||
* destination where source transparent, and destination where source
|
* destination where source transparent, and destination where source
|
||||||
* opaque.
|
* opaque.
|
||||||
*
|
*
|
||||||
* @param {Number} operation The composite operation (channel mask or binary
|
* @param {Number} mask The channel mask for future operations on this
|
||||||
* raster operation) for future operations on this
|
* Layer.
|
||||||
* Layer.
|
|
||||||
*/
|
*/
|
||||||
this.setCompositeOperation = function(operation) {
|
this.setChannelMask = function(mask) {
|
||||||
scheduleTask(function() {
|
scheduleTask(function() {
|
||||||
|
displayContext.globalCompositeOperation = compositeOperation[mask];
|
||||||
// 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];
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user