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