Transfer functions on abstract pixels.
[guacamole-common-js.git] / src / main / resources / layer.js
index 64ee9a7..709d3ae 100644 (file)
@@ -116,7 +116,6 @@ Guacamole.Layer = function(width, height) {
      */
     function resize(newWidth, newHeight) {
 
-
         // Only preserve old data if width/height are both non-zero
         var oldData = null;
         if (width != 0 && height != 0) {
@@ -135,6 +134,9 @@ Guacamole.Layer = function(width, height) {
 
         }
 
+        // Preserve composite operation
+        var oldCompositeOperation = displayContext.globalCompositeOperation;
+
         // Resize canvas
         display.width = newWidth;
         display.height = newHeight;
@@ -145,8 +147,12 @@ Guacamole.Layer = function(width, height) {
                     0, 0, width, height,
                     0, 0, width, height);
 
+        // Restore composite operation
+        displayContext.globalCompositeOperation = oldCompositeOperation;
+
         width = newWidth;
         height = newHeight;
+
     }
 
     /**
@@ -246,8 +252,8 @@ Guacamole.Layer = function(width, height) {
     function scheduleTask(handler, blocked) {
         
         // If no pending tasks, just call (if available) and exit
-        if (layer.isReady() && !blocked && handler != null) {
-            handler();
+        if (layer.isReady() && !blocked) {
+            if (handler) handler();
             return null;
         }
 
@@ -382,9 +388,113 @@ Guacamole.Layer = function(width, height) {
      * 
      * @param {function} handler The function to call once all currently
      *                           pending operations are complete.
+     * @param {boolean} blocked Whether the task should start blocked.
      */
-    this.sync = function(handler) {
-        return scheduleTask(handler);
+    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) {
+
+                    // 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);
+
+            }
+
+            // 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);
+
+        }
+
     };
 
     /**
@@ -542,10 +652,11 @@ Guacamole.Layer = function(width, height) {
     };
 
     /**
-     * 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.
      * 
@@ -563,3 +674,113 @@ Guacamole.Layer = function(width, height) {
     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;
+
+};