Use transfer function within copy, if set.
[guacamole-common-js.git] / src / main / resources / layer.js
index fe93e2b..2938f76 100644 (file)
 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.
- */
-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;
-
-/**
  * Abstract ordered drawing surface. Each Layer contains a canvas element and
  * provides simple drawing instructions for drawing to that canvas element,
  * however unlike the canvas element itself, drawing operations on a Layer are
@@ -145,6 +75,12 @@ Guacamole.Layer = function(width, height) {
     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
      * tasks added at the end of the queue and old tasks removed from the
      * front of the queue (FIFO).
@@ -177,6 +113,36 @@ Guacamole.Layer = function(width, height) {
     };
 
     /**
+     * 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
      * new size. This function should only be used internally.
      * 
@@ -186,7 +152,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) {
@@ -205,6 +170,9 @@ Guacamole.Layer = function(width, height) {
 
         }
 
+        // Preserve composite operation
+        var oldCompositeOperation = displayContext.globalCompositeOperation;
+
         // Resize canvas
         display.width = newWidth;
         display.height = newHeight;
@@ -215,8 +183,12 @@ Guacamole.Layer = function(width, height) {
                     0, 0, width, height,
                     0, 0, width, height);
 
+        // Restore composite operation
+        displayContext.globalCompositeOperation = oldCompositeOperation;
+
         width = newWidth;
         height = newHeight;
+
     }
 
     /**
@@ -486,8 +458,29 @@ Guacamole.Layer = function(width, height) {
             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) {
+
+                // 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
             if (srcLock != null) 
@@ -611,19 +604,35 @@ 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 composite operation for future operations on this Layer. This
+     * operation is either a channel mask, or the ID of a binary raster
+     * operation.
+     * 
+     * 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.
      * 
-     * @param {Number} mask The channel mask for future operations on this
-     *                      Layer.
+     * @param {Number} operation The composite operation (channel mask or binary
+     *                           raster operation) for future operations on this
+     *                           Layer.
      */
-    this.setChannelMask = function(mask) {
+    this.setCompositeOperation = function(operation) {
         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];
+            }
+
         });
     };
 
@@ -632,3 +641,74 @@ 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;
+