2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
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/
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
15 * The Original Code is guacamole-common-js.
17 * The Initial Developer of the Original Code is
19 * Portions created by the Initial Developer are Copyright (C) 2010
20 * the Initial Developer. All Rights Reserved.
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.
37 * ***** END LICENSE BLOCK ***** */
39 // Guacamole namespace
40 var Guacamole = Guacamole || {};
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.
49 * @param {Guacamole.Tunnel} tunnel The tunnel to use to send and receive
50 * Guacamole instructions.
52 Guacamole.Client = function(tunnel) {
54 var guac_client = this;
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;
63 var currentState = STATE_IDLE;
65 var currentTimestamp = 0;
66 var pingInterval = null;
69 var displayHeight = 0;
73 * Translation from Guacamole protocol line caps to Layer line caps.
82 * Translation from Guacamole protocol line caps to Layer line caps.
90 // Create bounding div
91 var bounds = document.createElement("div");
92 bounds.style.position = "relative";
93 bounds.style.width = (displayWidth*displayScale) + "px";
94 bounds.style.height = (displayHeight*displayScale) + "px";
97 var display = document.createElement("div");
98 display.style.position = "relative";
99 display.style.width = displayWidth + "px";
100 display.style.height = displayHeight + "px";
102 // Ensure transformations on display originate at 0,0
103 display.style.transformOrigin =
104 display.style.webkitTransformOrigin =
105 display.style.MozTransformOrigin =
106 display.style.OTransformOrigin =
107 display.style.msTransformOrigin =
110 // Create default layer
111 var default_layer_container = new Guacamole.Client.LayerContainer(displayWidth, displayHeight);
113 // Position default layer
114 var default_layer_container_element = default_layer_container.getElement();
115 default_layer_container_element.style.position = "absolute";
116 default_layer_container_element.style.left = "0px";
117 default_layer_container_element.style.top = "0px";
118 default_layer_container_element.style.overflow = "hidden";
120 // Create cursor layer
121 var cursor = new Guacamole.Client.LayerContainer(0, 0);
122 cursor.getLayer().setChannelMask(Guacamole.Layer.SRC);
124 // Position cursor layer
125 var cursor_element = cursor.getElement();
126 cursor_element.style.position = "absolute";
127 cursor_element.style.left = "0px";
128 cursor_element.style.top = "0px";
130 // Add default layer and cursor to display
131 display.appendChild(default_layer_container.getElement());
132 display.appendChild(cursor.getElement());
134 // Add display to bounds
135 bounds.appendChild(display);
137 // Initially, only default layer exists
138 var layers = [default_layer_container];
140 // No initial buffers
143 tunnel.onerror = function(message) {
144 if (guac_client.onerror)
145 guac_client.onerror(message);
148 function setState(state) {
149 if (state != currentState) {
150 currentState = state;
151 if (guac_client.onstatechange)
152 guac_client.onstatechange(currentState);
156 function isConnected() {
157 return currentState == STATE_CONNECTED
158 || currentState == STATE_WAITING;
161 var cursorHotspotX = 0;
162 var cursorHotspotY = 0;
167 function moveCursor(x, y) {
170 cursor.translate(x - cursorHotspotX, y - cursorHotspotY);
172 // Update stored position
178 guac_client.getDisplay = function() {
182 guac_client.sendKeyEvent = function(pressed, keysym) {
183 // Do not send requests if not connected
187 tunnel.sendMessage("key", keysym, pressed);
190 guac_client.sendMouseState = function(mouseState) {
192 // Do not send requests if not connected
196 // Update client-side cursor
204 if (mouseState.left) buttonMask |= 1;
205 if (mouseState.middle) buttonMask |= 2;
206 if (mouseState.right) buttonMask |= 4;
207 if (mouseState.up) buttonMask |= 8;
208 if (mouseState.down) buttonMask |= 16;
211 tunnel.sendMessage("mouse", mouseState.x, mouseState.y, buttonMask);
214 guac_client.setClipboard = function(data) {
216 // Do not send requests if not connected
220 tunnel.sendMessage("clipboard", data);
224 guac_client.onstatechange = null;
225 guac_client.onname = null;
226 guac_client.onerror = null;
227 guac_client.onclipboard = null;
230 function getBufferLayer(index) {
233 var buffer = buffers[index];
235 // Create buffer if necessary
236 if (buffer == null) {
237 buffer = new Guacamole.Layer(0, 0);
239 buffers[index] = buffer;
246 function getLayerContainer(index) {
248 var layer = layers[index];
252 layer = new Guacamole.Client.LayerContainer(displayWidth, displayHeight);
253 layers[index] = layer;
255 // Get and position layer
256 var layer_element = layer.getElement();
257 layer_element.style.position = "absolute";
258 layer_element.style.left = "0px";
259 layer_element.style.top = "0px";
260 layer_element.style.overflow = "hidden";
262 // Add to default layer container
263 default_layer_container.getElement().appendChild(layer_element);
271 function getLayer(index) {
273 // If buffer, just get layer
275 return getBufferLayer(index);
277 // Otherwise, retrieve layer from layer container
278 return getLayerContainer(index).getLayer();
283 * Handlers for all defined layer properties.
285 var layerPropertyHandlers = {
287 "miter-limit": function(layer, value) {
288 layer.setMiterLimit(parseFloat(value));
294 * Handlers for all instruction opcodes receivable by a Guacamole protocol
297 var instructionHandlers = {
299 "arc": function(parameters) {
301 var layer = getLayer(parseInt(parameters[0]));
302 var x = parseInt(parameters[1]);
303 var y = parseInt(parameters[2]);
304 var radius = parseInt(parameters[3]);
305 var startAngle = parseFloat(parameters[4]);
306 var endAngle = parseFloat(parameters[5]);
307 var negative = parseInt(parameters[6]);
309 layer.arc(x, y, radius, startAngle, endAngle, negative != 0);
313 "cfill": function(parameters) {
315 var channelMask = parseInt(parameters[0]);
316 var layer = getLayer(parseInt(parameters[1]));
317 var r = parseInt(parameters[2]);
318 var g = parseInt(parameters[3]);
319 var b = parseInt(parameters[4]);
320 var a = parseInt(parameters[5]);
322 layer.setChannelMask(channelMask);
324 layer.fillColor(r, g, b, a);
328 "clip": function(parameters) {
330 var layer = getLayer(parseInt(parameters[0]));
336 "clipboard": function(parameters) {
337 if (guac_client.onclipboard) guac_client.onclipboard(parameters[0]);
340 "close": function(parameters) {
342 var layer = getLayer(parseInt(parameters[0]));
348 "copy": function(parameters) {
350 var srcL = getLayer(parseInt(parameters[0]));
351 var srcX = parseInt(parameters[1]);
352 var srcY = parseInt(parameters[2]);
353 var srcWidth = parseInt(parameters[3]);
354 var srcHeight = parseInt(parameters[4]);
355 var channelMask = parseInt(parameters[5]);
356 var dstL = getLayer(parseInt(parameters[6]));
357 var dstX = parseInt(parameters[7]);
358 var dstY = parseInt(parameters[8]);
360 dstL.setChannelMask(channelMask);
374 "cstroke": function(parameters) {
376 var channelMask = parseInt(parameters[0]);
377 var layer = getLayer(parseInt(parameters[1]));
378 var cap = lineCap[parseInt(parameters[2])];
379 var join = lineJoin[parseInt(parameters[3])];
380 var thickness = parseInt(parameters[4]);
381 var r = parseInt(parameters[5]);
382 var g = parseInt(parameters[6]);
383 var b = parseInt(parameters[7]);
384 var a = parseInt(parameters[8]);
386 layer.setChannelMask(channelMask);
388 layer.strokeColor(cap, join, thickness, r, g, b, a);
392 "cursor": function(parameters) {
394 cursorHotspotX = parseInt(parameters[0]);
395 cursorHotspotY = parseInt(parameters[1]);
396 var srcL = getLayer(parseInt(parameters[2]));
397 var srcX = parseInt(parameters[3]);
398 var srcY = parseInt(parameters[4]);
399 var srcWidth = parseInt(parameters[5]);
400 var srcHeight = parseInt(parameters[6]);
403 cursor.resize(srcWidth, srcHeight);
405 // Draw cursor to cursor layer
406 cursor.getLayer().copy(
416 // Update cursor position (hotspot may have changed)
417 moveCursor(cursorX, cursorY);
421 "curve": function(parameters) {
423 var layer = getLayer(parseInt(parameters[0]));
424 var cp1x = parseInt(parameters[1]);
425 var cp1y = parseInt(parameters[2]);
426 var cp2x = parseInt(parameters[3]);
427 var cp2y = parseInt(parameters[4]);
428 var x = parseInt(parameters[5]);
429 var y = parseInt(parameters[6]);
431 layer.curveTo(cp1x, cp1y, cp2x, cp2y, x, y);
435 "dispose": function(parameters) {
437 var layer_index = parseInt(parameters[0]);
439 // If visible layer, remove from parent
440 if (layer_index > 0) {
442 // Get container element
443 var layer_container = getLayerContainer(layer_index).getElement();
445 // Remove from parent
446 layer_container.parentNode.removeChild(layer_container);
449 delete layers[layer_index];
453 // If buffer, just delete reference
454 else if (layer_index < 0)
455 delete buffers[-1 - layer_index];
457 // Attempting to dispose the root layer currently has no effect.
461 "distort": function(parameters) {
463 var layer_index = parseInt(parameters[0]);
464 var a = parseFloat(parameters[1]);
465 var b = parseFloat(parameters[2]);
466 var c = parseFloat(parameters[3]);
467 var d = parseFloat(parameters[4]);
468 var e = parseFloat(parameters[5]);
469 var f = parseFloat(parameters[6]);
471 // Only valid for visible layers (not buffers)
472 if (layer_index >= 0) {
474 // Get container element
475 var layer_container = getLayerContainer(layer_index).getElement();
477 // Set layer transform
478 layer_container.transform(a, b, c, d, e, f);
484 "error": function(parameters) {
485 if (guac_client.onerror) guac_client.onerror(parameters[0]);
486 guac_client.disconnect();
489 "identity": function(parameters) {
491 var layer = getLayer(parseInt(parameters[0]));
493 layer.setTransform(1, 0, 0, 1, 0, 0);
497 "lfill": function(parameters) {
499 var channelMask = parseInt(parameters[0]);
500 var layer = getLayer(parseInt(parameters[1]));
501 var srcLayer = getLayer(parseInt(parameters[2]));
503 layer.setChannelMask(channelMask);
505 layer.fillLayer(srcLayer);
509 "line": function(parameters) {
511 var layer = getLayer(parseInt(parameters[0]));
512 var x = parseInt(parameters[1]);
513 var y = parseInt(parameters[2]);
519 "lstroke": function(parameters) {
521 var channelMask = parseInt(parameters[0]);
522 var layer = getLayer(parseInt(parameters[1]));
523 var srcLayer = getLayer(parseInt(parameters[2]));
525 layer.setChannelMask(channelMask);
527 layer.strokeLayer(srcLayer);
531 "move": function(parameters) {
533 var layer_index = parseInt(parameters[0]);
534 var parent_index = parseInt(parameters[1]);
535 var x = parseInt(parameters[2]);
536 var y = parseInt(parameters[3]);
537 var z = parseInt(parameters[4]);
539 // Only valid for non-default layers
540 if (layer_index > 0 && parent_index >= 0) {
542 // Get container element
543 var layer_container = getLayerContainer(layer_index);
544 var layer_container_element = layer_container.getElement();
545 var parent = getLayerContainer(parent_index).getElement();
547 // Set parent if necessary
548 if (!(layer_container_element.parentNode === parent))
549 parent.appendChild(layer_container_element);
552 layer_container.translate(x, y);
553 layer_container_element.style.zIndex = z;
559 "name": function(parameters) {
560 if (guac_client.onname) guac_client.onname(parameters[0]);
563 "png": function(parameters) {
565 var channelMask = parseInt(parameters[0]);
566 var layer = getLayer(parseInt(parameters[1]));
567 var x = parseInt(parameters[2]);
568 var y = parseInt(parameters[3]);
569 var data = parameters[4];
571 layer.setChannelMask(channelMask);
576 "data:image/png;base64," + data
579 // If received first update, no longer waiting.
580 if (currentState == STATE_WAITING)
581 setState(STATE_CONNECTED);
585 "pop": function(parameters) {
587 var layer = getLayer(parseInt(parameters[0]));
593 "push": function(parameters) {
595 var layer = getLayer(parseInt(parameters[0]));
601 "rect": function(parameters) {
603 var layer = getLayer(parseInt(parameters[0]));
604 var x = parseInt(parameters[1]);
605 var y = parseInt(parameters[2]);
606 var w = parseInt(parameters[3]);
607 var h = parseInt(parameters[4]);
609 layer.rect(x, y, w, h);
613 "reset": function(parameters) {
615 var layer = getLayer(parseInt(parameters[0]));
621 "set": function(parameters) {
623 var layer = getLayer(parseInt(parameters[0]));
624 var name = parameters[1];
625 var value = parameters[2];
627 // Call property handler if defined
628 var handler = layerPropertyHandlers[name];
630 handler(layer, value);
634 "shade": function(parameters) {
636 var layer_index = parseInt(parameters[0]);
637 var a = parseInt(parameters[1]);
639 // Only valid for visible layers (not buffers)
640 if (layer_index >= 0) {
642 // Get container element
643 var layer_container = getLayerContainer(layer_index).getElement();
646 layer_container.style.opacity = a/255.0;
652 "size": function(parameters) {
654 var layer_index = parseInt(parameters[0]);
655 var width = parseInt(parameters[1]);
656 var height = parseInt(parameters[2]);
659 var layer_container = getLayerContainer(layer_index);
660 layer_container.resize(width, height);
662 // If layer is default, resize display
663 if (layer_index == 0) {
665 displayWidth = width;
666 displayHeight = height;
668 // Update (set) display size
669 display.style.width = displayWidth + "px";
670 display.style.height = displayHeight + "px";
672 // Update bounds size
673 bounds.style.width = (displayWidth*displayScale) + "px";
674 bounds.style.height = (displayHeight*displayScale) + "px";
680 "start": function(parameters) {
682 var layer = getLayer(parseInt(parameters[0]));
683 var x = parseInt(parameters[1]);
684 var y = parseInt(parameters[2]);
690 "sync": function(parameters) {
692 var timestamp = parameters[0];
694 // When all layers have finished rendering all instructions
695 // UP TO THIS POINT IN TIME, send sync response.
697 var layersToSync = 0;
698 function syncLayer() {
702 // Send sync response when layers are finished
703 if (layersToSync == 0) {
704 if (timestamp != currentTimestamp) {
705 tunnel.sendMessage("sync", timestamp);
706 currentTimestamp = timestamp;
712 // Count active, not-ready layers and install sync tracking hooks
713 for (var i=0; i<layers.length; i++) {
715 var layer = layers[i].getLayer();
716 if (layer && !layer.isReady()) {
718 layer.sync(syncLayer);
723 // If all layers are ready, then we didn't install any hooks.
724 // Send sync message now,
725 if (layersToSync == 0) {
726 if (timestamp != currentTimestamp) {
727 tunnel.sendMessage("sync", timestamp);
728 currentTimestamp = timestamp;
734 "transfer": function(parameters) {
736 var srcL = getLayer(parseInt(parameters[0]));
737 var srcX = parseInt(parameters[1]);
738 var srcY = parseInt(parameters[2]);
739 var srcWidth = parseInt(parameters[3]);
740 var srcHeight = parseInt(parameters[4]);
741 var transferFunction = Guacamole.Client.DefaultTransferFunction[parameters[5]];
742 var dstL = getLayer(parseInt(parameters[6]));
743 var dstX = parseInt(parameters[7]);
744 var dstY = parseInt(parameters[8]);
759 "transform": function(parameters) {
761 var layer = getLayer(parseInt(parameters[0]));
762 var a = parseFloat(parameters[1]);
763 var b = parseFloat(parameters[2]);
764 var c = parseFloat(parameters[3]);
765 var d = parseFloat(parameters[4]);
766 var e = parseFloat(parameters[5]);
767 var f = parseFloat(parameters[6]);
769 layer.transform(a, b, c, d, e, f);
776 tunnel.oninstruction = function(opcode, parameters) {
778 var handler = instructionHandlers[opcode];
785 guac_client.disconnect = function() {
787 // Only attempt disconnection not disconnected.
788 if (currentState != STATE_DISCONNECTED
789 && currentState != STATE_DISCONNECTING) {
791 setState(STATE_DISCONNECTING);
795 window.clearInterval(pingInterval);
797 // Send disconnect message and disconnect
798 tunnel.sendMessage("disconnect");
800 setState(STATE_DISCONNECTED);
806 guac_client.connect = function(data) {
808 setState(STATE_CONNECTING);
811 tunnel.connect(data);
814 setState(STATE_IDLE);
818 // Ping every 5 seconds (ensure connection alive)
819 pingInterval = window.setInterval(function() {
820 tunnel.sendMessage("sync", currentTimestamp);
823 setState(STATE_WAITING);
826 guac_client.scale = function(scale) {
828 display.style.transform =
829 display.style.WebkitTransform =
830 display.style.MozTransform =
831 display.style.OTransform =
832 display.style.msTransform =
834 "scale(" + scale + "," + scale + ")";
836 displayScale = scale;
838 // Update bounds size
839 bounds.style.width = (displayWidth*displayScale) + "px";
840 bounds.style.height = (displayHeight*displayScale) + "px";
848 * Simple container for Guacamole.Layer, allowing layers to be easily
849 * repositioned and nested. This allows certain operations to be accelerated
850 * through DOM manipulation, rather than raster operations.
854 * @param {Number} width The width of the Layer, in pixels. The canvas element
855 * backing this Layer will be given this width.
857 * @param {Number} height The height of the Layer, in pixels. The canvas element
858 * backing this Layer will be given this height.
860 Guacamole.Client.LayerContainer = function(width, height) {
863 * Reference to this LayerContainer.
866 var layer_container = this;
868 // Create layer with given size
869 var layer = new Guacamole.Layer(width, height);
871 // Set layer position
872 var canvas = layer.getCanvas();
873 canvas.style.position = "absolute";
874 canvas.style.left = "0px";
875 canvas.style.top = "0px";
877 // Create div with given size
878 var div = document.createElement("div");
879 div.appendChild(canvas);
880 div.style.width = width + "px";
881 div.style.height = height + "px";
884 * Changes the size of this LayerContainer and the contained Layer to the
885 * given width and height.
887 * @param {Number} width The new width to assign to this Layer.
888 * @param {Number} height The new height to assign to this Layer.
890 layer_container.resize = function(width, height) {
893 layer.resize(width, height);
895 // Resize containing div
896 div.style.width = width + "px";
897 div.style.height = height + "px";
902 * Returns the Layer contained within this LayerContainer.
903 * @returns {Guacamole.Layer} The Layer contained within this LayerContainer.
905 layer_container.getLayer = function() {
910 * Returns the element containing the Layer within this LayerContainer.
911 * @returns {Element} The element containing the Layer within this LayerContainer.
913 layer_container.getElement = function() {
918 * The translation component of this LayerContainer's transform.
920 var translate = "translate(0px, 0px)"; // (0, 0)
923 * The arbitrary matrix component of this LayerContainer's transform.
925 var matrix = "matrix(1, 0, 0, 1, 0, 0)"; // Identity
928 * Moves the upper-left corner of this LayerContainer to the given X and Y
931 * @param {Number} x The X coordinate to move to.
932 * @param {Number} y The Y coordinate to move to.
934 layer_container.translate = function(x, y) {
936 // Generate translation
937 translate = "translate("
941 // Set layer transform
942 div.style.transform =
943 div.style.WebkitTransform =
944 div.style.MozTransform =
945 div.style.OTransform =
946 div.style.msTransform =
948 translate + " " + matrix;
953 * Applies the given affine transform (defined with six values from the
954 * transform's matrix).
956 * @param {Number} a The first value in the affine transform's matrix.
957 * @param {Number} b The second value in the affine transform's matrix.
958 * @param {Number} c The third value in the affine transform's matrix.
959 * @param {Number} d The fourth value in the affine transform's matrix.
960 * @param {Number} e The fifth value in the affine transform's matrix.
961 * @param {Number} f The sixth value in the affine transform's matrix.
963 layer_container.transform = function(a, b, c, d, e, f) {
965 // Generate matrix transformation
973 "matrix(" + a + "," + b + "," + c + "," + d + "," + e + "," + f + ")";
975 // Set layer transform
976 div.style.transform =
977 div.style.WebkitTransform =
978 div.style.MozTransform =
979 div.style.OTransform =
980 div.style.msTransform =
982 translate + " " + matrix;
989 * Map of all Guacamole binary raster operations to transfer functions.
992 Guacamole.Client.DefaultTransferFunction = {
995 0x0: function (src, dst) {
996 dst.red = dst.green = dst.blue = 0x00;
1000 0xF: function (src, dst) {
1001 dst.red = dst.green = dst.blue = 0xFF;
1005 0x3: function (src, dst) {
1007 dst.green = src.green;
1008 dst.blue = src.blue;
1009 dst.alpha = src.alpha;
1013 0x5: function (src, dst) {
1018 0xC: function (src, dst) {
1019 dst.red = 0xFF & ~src.red;
1020 dst.green = 0xFF & ~src.green;
1021 dst.blue = 0xFF & ~src.blue;
1022 dst.alpha = src.alpha;
1026 0xA: function (src, dst) {
1027 dst.red = 0xFF & ~dst.red;
1028 dst.green = 0xFF & ~dst.green;
1029 dst.blue = 0xFF & ~dst.blue;
1033 0x1: function (src, dst) {
1034 dst.red = ( src.red & dst.red);
1035 dst.green = ( src.green & dst.green);
1036 dst.blue = ( src.blue & dst.blue);
1040 0xE: function (src, dst) {
1041 dst.red = 0xFF & ~( src.red & dst.red);
1042 dst.green = 0xFF & ~( src.green & dst.green);
1043 dst.blue = 0xFF & ~( src.blue & dst.blue);
1047 0x7: function (src, dst) {
1048 dst.red = ( src.red | dst.red);
1049 dst.green = ( src.green | dst.green);
1050 dst.blue = ( src.blue | dst.blue);
1054 0x8: function (src, dst) {
1055 dst.red = 0xFF & ~( src.red | dst.red);
1056 dst.green = 0xFF & ~( src.green | dst.green);
1057 dst.blue = 0xFF & ~( src.blue | dst.blue);
1061 0x6: function (src, dst) {
1062 dst.red = ( src.red ^ dst.red);
1063 dst.green = ( src.green ^ dst.green);
1064 dst.blue = ( src.blue ^ dst.blue);
1068 0x9: function (src, dst) {
1069 dst.red = 0xFF & ~( src.red ^ dst.red);
1070 dst.green = 0xFF & ~( src.green ^ dst.green);
1071 dst.blue = 0xFF & ~( src.blue ^ dst.blue);
1074 /* AND inverted source */
1075 0x4: function (src, dst) {
1076 dst.red = 0xFF & (~src.red & dst.red);
1077 dst.green = 0xFF & (~src.green & dst.green);
1078 dst.blue = 0xFF & (~src.blue & dst.blue);
1081 /* OR inverted source */
1082 0xD: function (src, dst) {
1083 dst.red = 0xFF & (~src.red | dst.red);
1084 dst.green = 0xFF & (~src.green | dst.green);
1085 dst.blue = 0xFF & (~src.blue | dst.blue);
1088 /* AND inverted destination */
1089 0x2: function (src, dst) {
1090 dst.red = 0xFF & ( src.red & ~dst.red);
1091 dst.green = 0xFF & ( src.green & ~dst.green);
1092 dst.blue = 0xFF & ( src.blue & ~dst.blue);
1095 /* OR inverted destination */
1096 0xB: function (src, dst) {
1097 dst.red = 0xFF & ( src.red | ~dst.red);
1098 dst.green = 0xFF & ( src.green | ~dst.green);
1099 dst.blue = 0xFF & ( src.blue | ~dst.blue);