d950895ca677aba9a5314e4bb8028fe82d65c5cf
[guacamole-common-js.git] / src / main / resources / guacamole.js
1
2 /* ***** BEGIN LICENSE BLOCK *****
3  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4  *
5  * The contents of this file are subject to the Mozilla Public License Version
6  * 1.1 (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  * http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the
13  * License.
14  *
15  * The Original Code is guacamole-common-js.
16  *
17  * The Initial Developer of the Original Code is
18  * Michael Jumper.
19  * Portions created by the Initial Developer are Copyright (C) 2010
20  * the Initial Developer. All Rights Reserved.
21  *
22  * Contributor(s):
23  * Matt Hortman
24  *
25  * Alternatively, the contents of this file may be used under the terms of
26  * either the GNU General Public License Version 2 or later (the "GPL"), or
27  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28  * in which case the provisions of the GPL or the LGPL are applicable instead
29  * of those above. If you wish to allow use of your version of this file only
30  * under the terms of either the GPL or the LGPL, and not to allow others to
31  * use your version of this file under the terms of the MPL, indicate your
32  * decision by deleting the provisions above and replace them with the notice
33  * and other provisions required by the GPL or the LGPL. If you do not delete
34  * the provisions above, a recipient may use your version of this file under
35  * the terms of any one of the MPL, the GPL or the LGPL.
36  *
37  * ***** END LICENSE BLOCK ***** */
38
39 // Guacamole namespace
40 var Guacamole = Guacamole || {};
41
42
43 /**
44  * Guacamole protocol client. Given a display element and {@link Guacamole.Tunnel},
45  * automatically handles incoming and outgoing Guacamole instructions via the
46  * provided tunnel, updating the display using one or more canvas elements.
47  * 
48  * @constructor
49  * @param {Guacamole.Tunnel} tunnel The tunnel to use to send and receive
50  *                                  Guacamole instructions.
51  */
52 Guacamole.Client = function(tunnel) {
53
54     var guac_client = this;
55
56     var STATE_IDLE          = 0;
57     var STATE_CONNECTING    = 1;
58     var STATE_WAITING       = 2;
59     var STATE_CONNECTED     = 3;
60     var STATE_DISCONNECTING = 4;
61     var STATE_DISCONNECTED  = 5;
62
63     var currentState = STATE_IDLE;
64     
65     var currentTimestamp = 0;
66     var pingInterval = null;
67
68     var displayWidth = 0;
69     var displayHeight = 0;
70
71     /**
72      * Translation from Guacamole protocol line caps to Layer line caps.
73      */
74     var lineCap = {
75         0: "butt",
76         1: "round",
77         2: "square"
78     };
79
80     /**
81      * Translation from Guacamole protocol line caps to Layer line caps.
82      */
83     var lineJoin = {
84         0: "bevel",
85         1: "miter",
86         2: "round"
87     };
88
89     // Create display
90     var display = document.createElement("div");
91     display.style.position = "relative";
92     display.style.width = displayWidth + "px";
93     display.style.height = displayHeight + "px";
94
95     // Create default layer
96     var default_layer_container = new Guacamole.Client.LayerContainer(displayWidth, displayHeight);
97
98     // Position default layer
99     var default_layer_container_element = default_layer_container.getElement();
100     default_layer_container_element.style.position = "absolute";
101     default_layer_container_element.style.left = "0px";
102     default_layer_container_element.style.top  = "0px";
103
104     // Create cursor layer
105     var cursor = new Guacamole.Client.LayerContainer(0, 0);
106     cursor.getLayer().setChannelMask(Guacamole.Layer.SRC);
107
108     // Position cursor layer
109     var cursor_element = cursor.getElement();
110     cursor_element.style.position = "absolute";
111     cursor_element.style.left = "0px";
112     cursor_element.style.top  = "0px";
113
114     // Add default layer and cursor to display
115     display.appendChild(default_layer_container.getElement());
116     display.appendChild(cursor.getElement());
117
118     // Initially, only default layer exists
119     var layers =  [default_layer_container];
120
121     // No initial buffers
122     var buffers = [];
123
124     tunnel.onerror = function(message) {
125         if (guac_client.onerror)
126             guac_client.onerror(message);
127     };
128
129     function setState(state) {
130         if (state != currentState) {
131             currentState = state;
132             if (guac_client.onstatechange)
133                 guac_client.onstatechange(currentState);
134         }
135     }
136
137     function isConnected() {
138         return currentState == STATE_CONNECTED
139             || currentState == STATE_WAITING;
140     }
141
142     var cursorHotspotX = 0;
143     var cursorHotspotY = 0;
144
145     var cursorX = 0;
146     var cursorY = 0;
147
148     function moveCursor(x, y) {
149
150         var element = cursor.getElement();
151
152         // Update rect
153         element.style.left = (x - cursorHotspotX) + "px";
154         element.style.top  = (y - cursorHotspotY) + "px";
155
156         // Update stored position
157         cursorX = x;
158         cursorY = y;
159
160     }
161
162     guac_client.getDisplay = function() {
163         return display;
164     };
165
166     guac_client.sendKeyEvent = function(pressed, keysym) {
167         // Do not send requests if not connected
168         if (!isConnected())
169             return;
170
171         tunnel.sendMessage("key", keysym, pressed);
172     };
173
174     guac_client.sendMouseState = function(mouseState) {
175
176         // Do not send requests if not connected
177         if (!isConnected())
178             return;
179
180         // Update client-side cursor
181         moveCursor(
182             mouseState.x,
183             mouseState.y
184         );
185
186         // Build mask
187         var buttonMask = 0;
188         if (mouseState.left)   buttonMask |= 1;
189         if (mouseState.middle) buttonMask |= 2;
190         if (mouseState.right)  buttonMask |= 4;
191         if (mouseState.up)     buttonMask |= 8;
192         if (mouseState.down)   buttonMask |= 16;
193
194         // Send message
195         tunnel.sendMessage("mouse", mouseState.x, mouseState.y, buttonMask);
196     };
197
198     guac_client.setClipboard = function(data) {
199
200         // Do not send requests if not connected
201         if (!isConnected())
202             return;
203
204         tunnel.sendMessage("clipboard", data);
205     };
206
207     // Handlers
208     guac_client.onstatechange = null;
209     guac_client.onname = null;
210     guac_client.onerror = null;
211     guac_client.onclipboard = null;
212
213     // Layers
214     function getBufferLayer(index) {
215
216         index = -1 - index;
217         var buffer = buffers[index];
218
219         // Create buffer if necessary
220         if (buffer == null) {
221             buffer = new Guacamole.Layer(0, 0);
222             buffer.autosize = 1;
223             buffers[index] = buffer;
224         }
225
226         return buffer;
227
228     }
229
230     function getLayerContainer(index) {
231
232         var layer = layers[index];
233         if (layer == null) {
234
235             // Add new layer
236             layer = new Guacamole.Client.LayerContainer(displayWidth, displayHeight);
237             layers[index] = layer;
238
239             // Get and position layer
240             var layer_element = layer.getElement();
241             layer_element.style.position = "absolute";
242             layer_element.style.left = "0px";
243             layer_element.style.top = "0px";
244
245             // Add to default layer container
246             default_layer_container.getElement().appendChild(layer_element);
247
248         }
249
250         return layer;
251
252     }
253
254     function getLayer(index) {
255        
256         // If buffer, just get layer
257         if (index < 0)
258             return getBufferLayer(index);
259
260         // Otherwise, retrieve layer from layer container
261         return getLayerContainer(index).getLayer();
262
263     }
264
265     var instructionHandlers = {
266
267         "arc": function(parameters) {
268
269             var layer = getLayer(parseInt(parameters[0]));
270             var x = parseInt(parameters[1]);
271             var y = parseInt(parameters[2]);
272             var radius = parseInt(parameters[3]);
273             var startAngle = parseFloat(parameters[4]);
274             var endAngle = parseFloat(parameters[5]);
275
276             layer.arc(x, y, radius, startAngle, endAngle);
277
278         },
279
280         "cfill": function(parameters) {
281
282             var channelMask = parseInt(parameters[0]);
283             var layer = getLayer(parseInt(parameters[1]));
284             var r = parseInt(parameters[2]);
285             var g = parseInt(parameters[3]);
286             var b = parseInt(parameters[4]);
287             var a = parseInt(parameters[5]);
288
289             layer.setChannelMask(channelMask);
290
291             layer.fillColor(r, g, b, a);
292
293         },
294
295         "clip": function(parameters) {
296
297             var layer = getLayer(parseInt(parameters[0]));
298
299             layer.clip();
300
301         },
302
303         "clipboard": function(parameters) {
304             if (guac_client.onclipboard) guac_client.onclipboard(parameters[0]);
305         },
306
307         "close": function(parameters) {
308
309             var layer = getLayer(parseInt(parameters[0]));
310
311             layer.close();
312
313         },
314
315         "copy": function(parameters) {
316
317             var srcL = getLayer(parseInt(parameters[0]));
318             var srcX = parseInt(parameters[1]);
319             var srcY = parseInt(parameters[2]);
320             var srcWidth = parseInt(parameters[3]);
321             var srcHeight = parseInt(parameters[4]);
322             var channelMask = parseInt(parameters[5]);
323             var dstL = getLayer(parseInt(parameters[6]));
324             var dstX = parseInt(parameters[7]);
325             var dstY = parseInt(parameters[8]);
326
327             dstL.setChannelMask(channelMask);
328
329             dstL.copy(
330                 srcL,
331                 srcX,
332                 srcY,
333                 srcWidth, 
334                 srcHeight, 
335                 dstX,
336                 dstY 
337             );
338
339         },
340
341         "cstroke": function(parameters) {
342
343             var channelMask = parseInt(parameters[0]);
344             var layer = getLayer(parseInt(parameters[1]));
345             var cap = lineCap[parseInt(parameters[2])];
346             var join = lineJoin[parseInt(parameters[3])];
347             var thickness = parseInt(parameters[4]);
348             var r = parseInt(parameters[5]);
349             var g = parseInt(parameters[6]);
350             var b = parseInt(parameters[7]);
351             var a = parseInt(parameters[8]);
352
353             layer.setChannelMask(channelMask);
354
355             layer.strokeColor(cap, join, thickness, r, g, b, a);
356
357         },
358
359         "cursor": function(parameters) {
360
361             cursorHotspotX = parseInt(parameters[0]);
362             cursorHotspotY = parseInt(parameters[1]);
363             var srcL = getLayer(parseInt(parameters[2]));
364             var srcX = parseInt(parameters[3]);
365             var srcY = parseInt(parameters[4]);
366             var srcWidth = parseInt(parameters[5]);
367             var srcHeight = parseInt(parameters[6]);
368
369             // Reset cursor size
370             cursor.resize(srcWidth, srcHeight);
371
372             // Draw cursor to cursor layer
373             cursor.getLayer().copy(
374                 srcL,
375                 srcX,
376                 srcY,
377                 srcWidth, 
378                 srcHeight, 
379                 0,
380                 0 
381             );
382
383             // Update cursor position (hotspot may have changed)
384             moveCursor(cursorX, cursorY);
385
386         },
387
388         "curve": function(parameters) {
389
390             var layer = getLayer(parseInt(parameters[0]));
391             var cp1x = parseInt(parameters[1]);
392             var cp1y = parseInt(parameters[2]);
393             var cp2x = parseInt(parameters[3]);
394             var cp2y = parseInt(parameters[4]);
395             var x = parseInt(parameters[5]);
396             var y = parseInt(parameters[6]);
397
398             layer.curveTo(cp1x, cp1y, cp2x, cp2y, x, y);
399
400         },
401
402         "dispose": function(parameters) {
403             
404             var layer_index = parseInt(parameters[0]);
405
406             // If visible layer, remove from parent
407             if (layer_index > 0) {
408
409                 // Get container element
410                 var layer_container = getLayerContainer(layer_index).getElement();
411
412                 // Remove from parent
413                 layer_container.parentNode.removeChild(layer_container);
414
415                 // Delete reference
416                 delete layers[layer_index];
417
418             }
419
420             // If buffer, just delete reference
421             else if (layer_index < 0)
422                 delete buffers[-1 - layer_index];
423
424             // Attempting to dispose the root layer currently has no effect.
425
426         },
427
428         "distort": function(parameters) {
429
430             var layer_index = parseInt(parameters[0]);
431             var a = parseFloat(parameters[1]);
432             var b = parseFloat(parameters[2]);
433             var c = parseFloat(parameters[3]);
434             var d = parseFloat(parameters[4]);
435             var e = parseFloat(parameters[5]);
436             var f = parseFloat(parameters[6]);
437
438             // Only valid for visible layers (not buffers)
439             if (layer_index >= 0) {
440
441                 // Get container element
442                 var layer_container = getLayerContainer(layer_index).getElement();
443
444                 // Set layer transform 
445                 layer_container.style.transform =
446                 layer_container.style.WebkitTransform =
447                 layer_container.style.MozTransform =
448                 layer_container.style.OTransform =
449                 layer_container.style.msTransform =
450
451                     /* a c e
452                      * b d f
453                      * 0 0 1
454                      */
455             
456                     "matrix(" + a + "," + b + "," + c + "," + d + "," + e + "," + f + ")";
457
458              }
459
460         },
461  
462         "error": function(parameters) {
463             if (guac_client.onerror) guac_client.onerror(parameters[0]);
464             guac_client.disconnect();
465         },
466
467         "identity": function(parameters) {
468
469             var layer = getLayer(parseInt(parameters[0]));
470
471             layer.setTransform(1, 0, 0, 1, 0, 0);
472
473         },
474
475         "lfill": function(parameters) {
476
477             var channelMask = parseInt(parameters[0]);
478             var layer = getLayer(parseInt(parameters[1]));
479             var srcLayer = getLayer(parseInt(parameters[2]));
480
481             layer.setChannelMask(channelMask);
482
483             layer.fillLayer(srcLayer);
484
485         },
486
487         "line": function(parameters) {
488
489             var layer = getLayer(parseInt(parameters[0]));
490             var x = parseInt(parameters[1]);
491             var y = parseInt(parameters[2]);
492
493             layer.lineTo(x, y);
494
495         },
496
497         "lstroke": function(parameters) {
498
499             var channelMask = parseInt(parameters[0]);
500             var layer = getLayer(parseInt(parameters[1]));
501             var srcLayer = getLayer(parseInt(parameters[2]));
502
503             layer.setChannelMask(channelMask);
504
505             layer.strokeLayer(srcLayer);
506
507         },
508
509         "move": function(parameters) {
510             
511             var layer_index = parseInt(parameters[0]);
512             var parent_index = parseInt(parameters[1]);
513             var x = parseInt(parameters[2]);
514             var y = parseInt(parameters[3]);
515             var z = parseInt(parameters[4]);
516
517             // Only valid for non-default layers
518             if (layer_index > 0 && parent_index >= 0) {
519
520                 // Get container element
521                 var layer_container = getLayerContainer(layer_index).getElement();
522                 var parent = getLayerContainer(parent_index).getElement();
523
524                 // Set parent if necessary
525                 if (!(layer_container.parentNode === parent))
526                     parent.appendChild(layer_container);
527
528                 // Move layer
529                 layer_container.style.left   = x + "px";
530                 layer_container.style.top    = y + "px";
531                 layer_container.style.zIndex = z;
532
533             }
534
535         },
536
537         "name": function(parameters) {
538             if (guac_client.onname) guac_client.onname(parameters[0]);
539         },
540
541         "png": function(parameters) {
542
543             var channelMask = parseInt(parameters[0]);
544             var layer = getLayer(parseInt(parameters[1]));
545             var x = parseInt(parameters[2]);
546             var y = parseInt(parameters[3]);
547             var data = parameters[4];
548
549             layer.setChannelMask(channelMask);
550
551             layer.draw(
552                 x,
553                 y,
554                 "data:image/png;base64," + data
555             );
556
557             // If received first update, no longer waiting.
558             if (currentState == STATE_WAITING)
559                 setState(STATE_CONNECTED);
560
561         },
562
563         "rect": function(parameters) {
564
565             var layer = getLayer(parseInt(parameters[0]));
566             var x = parseInt(parameters[1]);
567             var y = parseInt(parameters[2]);
568             var w = parseInt(parameters[3]);
569             var h = parseInt(parameters[4]);
570
571             layer.rect(x, y, w, h);
572
573         },
574         
575         "reset": function(parameters) {
576
577             var layer = getLayer(parseInt(parameters[0]));
578
579             layer.reset();
580
581         },
582
583         "shade": function(parameters) {
584             
585             var layer_index = parseInt(parameters[0]);
586             var a = parseInt(parameters[1]);
587
588             // Only valid for visible layers (not buffers)
589             if (layer_index >= 0) {
590
591                 // Get container element
592                 var layer_container = getLayerContainer(layer_index).getElement();
593
594                 // Set layer opacity
595                 layer_container.style.opacity = a/255.0;
596
597             }
598
599         },
600
601         "size": function(parameters) {
602
603             var layer_index = parseInt(parameters[0]);
604             var width = parseInt(parameters[1]);
605             var height = parseInt(parameters[2]);
606
607             // Resize layer
608             var layer_container = getLayerContainer(layer_index);
609             layer_container.resize(width, height);
610
611             // If layer is default, resize display
612             if (layer_index == 0) {
613
614                 displayWidth = width;
615                 displayHeight = height;
616
617                 // Update (set) display size
618                 display.style.width = displayWidth + "px";
619                 display.style.height = displayHeight + "px";
620
621             }
622
623         },
624         
625         "start": function(parameters) {
626
627             var layer = getLayer(parseInt(parameters[0]));
628             var x = parseInt(parameters[1]);
629             var y = parseInt(parameters[2]);
630
631             layer.moveTo(x, y);
632
633         },
634
635         "sync": function(parameters) {
636
637             var timestamp = parameters[0];
638
639             // When all layers have finished rendering all instructions
640             // UP TO THIS POINT IN TIME, send sync response.
641
642             var layersToSync = 0;
643             function syncLayer() {
644
645                 layersToSync--;
646
647                 // Send sync response when layers are finished
648                 if (layersToSync == 0) {
649                     if (timestamp != currentTimestamp) {
650                         tunnel.sendMessage("sync", timestamp);
651                         currentTimestamp = timestamp;
652                     }
653                 }
654
655             }
656
657             // Count active, not-ready layers and install sync tracking hooks
658             for (var i=0; i<layers.length; i++) {
659
660                 var layer = layers[i].getLayer();
661                 if (layer && !layer.isReady()) {
662                     layersToSync++;
663                     layer.sync(syncLayer);
664                 }
665
666             }
667
668             // If all layers are ready, then we didn't install any hooks.
669             // Send sync message now,
670             if (layersToSync == 0) {
671                 if (timestamp != currentTimestamp) {
672                     tunnel.sendMessage("sync", timestamp);
673                     currentTimestamp = timestamp;
674                 }
675             }
676
677         },
678
679         "transfer": function(parameters) {
680
681             var srcL = getLayer(parseInt(parameters[0]));
682             var srcX = parseInt(parameters[1]);
683             var srcY = parseInt(parameters[2]);
684             var srcWidth = parseInt(parameters[3]);
685             var srcHeight = parseInt(parameters[4]);
686             var transferFunction = Guacamole.Client.DefaultTransferFunction[parameters[5]];
687             var dstL = getLayer(parseInt(parameters[6]));
688             var dstX = parseInt(parameters[7]);
689             var dstY = parseInt(parameters[8]);
690
691             dstL.transfer(
692                 srcL,
693                 srcX,
694                 srcY,
695                 srcWidth, 
696                 srcHeight, 
697                 dstX,
698                 dstY,
699                 transferFunction
700             );
701
702         },
703
704         "transform": function(parameters) {
705
706             var layer = getLayer(parseInt(parameters[0]));
707             var a = parseFloat(parameters[1]);
708             var b = parseFloat(parameters[2]);
709             var c = parseFloat(parameters[3]);
710             var d = parseFloat(parameters[4]);
711             var e = parseFloat(parameters[5]);
712             var f = parseFloat(parameters[6]);
713
714             layer.transform(a, b, c, d, e, f);
715
716         }
717       
718     };
719
720
721     tunnel.oninstruction = function(opcode, parameters) {
722
723         var handler = instructionHandlers[opcode];
724         if (handler)
725             handler(parameters);
726
727     };
728
729
730     guac_client.disconnect = function() {
731
732         // Only attempt disconnection not disconnected.
733         if (currentState != STATE_DISCONNECTED
734                 && currentState != STATE_DISCONNECTING) {
735
736             setState(STATE_DISCONNECTING);
737
738             // Stop ping
739             if (pingInterval)
740                 window.clearInterval(pingInterval);
741
742             // Send disconnect message and disconnect
743             tunnel.sendMessage("disconnect");
744             tunnel.disconnect();
745             setState(STATE_DISCONNECTED);
746
747         }
748
749     };
750     
751     guac_client.connect = function(data) {
752
753         setState(STATE_CONNECTING);
754
755         try {
756             tunnel.connect(data);
757         }
758         catch (e) {
759             setState(STATE_IDLE);
760             throw e;
761         }
762
763         // Ping every 5 seconds (ensure connection alive)
764         pingInterval = window.setInterval(function() {
765             tunnel.sendMessage("sync", currentTimestamp);
766         }, 5000);
767
768         setState(STATE_WAITING);
769     };
770
771 };
772
773
774 /**
775  * Simple container for Guacamole.Layer, allowing layers to be easily
776  * repositioned and nested. This allows certain operations to be accelerated
777  * through DOM manipulation, rather than raster operations.
778  * 
779  * @constructor
780  * 
781  * @param {Number} width The width of the Layer, in pixels. The canvas element
782  *                       backing this Layer will be given this width.
783  *                       
784  * @param {Number} height The height of the Layer, in pixels. The canvas element
785  *                        backing this Layer will be given this height.
786  */
787 Guacamole.Client.LayerContainer = function(width, height) {
788
789     /**
790      * Reference to this LayerContainer.
791      * @private
792      */
793     var layer_container = this;
794
795     // Create layer with given size
796     var layer = new Guacamole.Layer(width, height);
797
798     // Set layer position
799     var canvas = layer.getCanvas();
800     canvas.style.position = "absolute";
801     canvas.style.left = "0px";
802     canvas.style.top = "0px";
803
804     // Create div with given size
805     var div = document.createElement("div");
806     div.appendChild(canvas);
807     div.style.width = width + "px";
808     div.style.height = height + "px";
809
810     /**
811      * Changes the size of this LayerContainer and the contained Layer to the
812      * given width and height.
813      * 
814      * @param {Number} width The new width to assign to this Layer.
815      * @param {Number} height The new height to assign to this Layer.
816      */
817     layer_container.resize = function(width, height) {
818
819         // Resize layer
820         layer.resize(width, height);
821
822         // Resize containing div
823         div.style.width = width + "px";
824         div.style.height = height + "px";
825
826     };
827   
828     /**
829      * Returns the Layer contained within this LayerContainer.
830      * @returns {Guacamole.Layer} The Layer contained within this LayerContainer.
831      */
832     layer_container.getLayer = function() {
833         return layer;
834     };
835
836     /**
837      * Returns the element containing the Layer within this LayerContainer.
838      * @returns {Element} The element containing the Layer within this LayerContainer.
839      */
840     layer_container.getElement = function() {
841         return div;
842     };
843
844 };
845
846 /**
847  * Map of all Guacamole binary raster operations to transfer functions.
848  * @private
849  */
850 Guacamole.Client.DefaultTransferFunction = {
851
852     /* BLACK */
853     0x0: function (src, dst) {
854         dst.red = dst.green = dst.blue = 0x00;
855     },
856
857     /* WHITE */
858     0xF: function (src, dst) {
859         dst.red = dst.green = dst.blue = 0xFF;
860     },
861
862     /* SRC */
863     0x3: function (src, dst) {
864         dst.red   = src.red;
865         dst.green = src.green;
866         dst.blue  = src.blue;
867         dst.alpha = src.alpha;
868     },
869
870     /* DEST (no-op) */
871     0x5: function (src, dst) {
872         // Do nothing
873     },
874
875     /* Invert SRC */
876     0xC: function (src, dst) {
877         dst.red   = 0xFF & ~src.red;
878         dst.green = 0xFF & ~src.green;
879         dst.blue  = 0xFF & ~src.blue;
880         dst.alpha =  src.alpha;
881     },
882     
883     /* Invert DEST */
884     0xA: function (src, dst) {
885         dst.red   = 0xFF & ~dst.red;
886         dst.green = 0xFF & ~dst.green;
887         dst.blue  = 0xFF & ~dst.blue;
888     },
889
890     /* AND */
891     0x1: function (src, dst) {
892         dst.red   =  ( src.red   &  dst.red);
893         dst.green =  ( src.green &  dst.green);
894         dst.blue  =  ( src.blue  &  dst.blue);
895     },
896
897     /* NAND */
898     0xE: function (src, dst) {
899         dst.red   = 0xFF & ~( src.red   &  dst.red);
900         dst.green = 0xFF & ~( src.green &  dst.green);
901         dst.blue  = 0xFF & ~( src.blue  &  dst.blue);
902     },
903
904     /* OR */
905     0x7: function (src, dst) {
906         dst.red   =  ( src.red   |  dst.red);
907         dst.green =  ( src.green |  dst.green);
908         dst.blue  =  ( src.blue  |  dst.blue);
909     },
910
911     /* NOR */
912     0x8: function (src, dst) {
913         dst.red   = 0xFF & ~( src.red   |  dst.red);
914         dst.green = 0xFF & ~( src.green |  dst.green);
915         dst.blue  = 0xFF & ~( src.blue  |  dst.blue);
916     },
917
918     /* XOR */
919     0x6: function (src, dst) {
920         dst.red   =  ( src.red   ^  dst.red);
921         dst.green =  ( src.green ^  dst.green);
922         dst.blue  =  ( src.blue  ^  dst.blue);
923     },
924
925     /* XNOR */
926     0x9: function (src, dst) {
927         dst.red   = 0xFF & ~( src.red   ^  dst.red);
928         dst.green = 0xFF & ~( src.green ^  dst.green);
929         dst.blue  = 0xFF & ~( src.blue  ^  dst.blue);
930     },
931
932     /* AND inverted source */
933     0x4: function (src, dst) {
934         dst.red   =  0xFF & (~src.red   &  dst.red);
935         dst.green =  0xFF & (~src.green &  dst.green);
936         dst.blue  =  0xFF & (~src.blue  &  dst.blue);
937     },
938
939     /* OR inverted source */
940     0xD: function (src, dst) {
941         dst.red   =  0xFF & (~src.red   |  dst.red);
942         dst.green =  0xFF & (~src.green |  dst.green);
943         dst.blue  =  0xFF & (~src.blue  |  dst.blue);
944     },
945
946     /* AND inverted destination */
947     0x2: function (src, dst) {
948         dst.red   =  0xFF & ( src.red   & ~dst.red);
949         dst.green =  0xFF & ( src.green & ~dst.green);
950         dst.blue  =  0xFF & ( src.blue  & ~dst.blue);
951     },
952
953     /* OR inverted destination */
954     0xB: function (src, dst) {
955         dst.red   =  0xFF & ( src.red   | ~dst.red);
956         dst.green =  0xFF & ( src.green | ~dst.green);
957         dst.blue  =  0xFF & ( src.blue  | ~dst.blue);
958     }
959
960 };