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 ***** */
40 * Namespace for all Guacamole JavaScript objects.
43 var Guacamole = Guacamole || {};
47 * Guacamole protocol client. Given a display element and {@link Guacamole.Tunnel},
48 * automatically handles incoming and outgoing Guacamole instructions via the
49 * provided tunnel, updating the display using one or more canvas elements.
52 * @param {Guacamole.Tunnel} tunnel The tunnel to use to send and receive
53 * Guacamole instructions.
55 Guacamole.Client = function(tunnel) {
57 var guac_client = this;
60 var STATE_CONNECTING = 1;
61 var STATE_WAITING = 2;
62 var STATE_CONNECTED = 3;
63 var STATE_DISCONNECTING = 4;
64 var STATE_DISCONNECTED = 5;
66 var currentState = STATE_IDLE;
68 var currentTimestamp = 0;
69 var pingInterval = null;
72 var displayHeight = 0;
76 * Translation from Guacamole protocol line caps to Layer line caps.
86 * Translation from Guacamole protocol line caps to Layer line caps.
95 // Create bounding div
96 var bounds = document.createElement("div");
97 bounds.style.position = "relative";
98 bounds.style.width = (displayWidth*displayScale) + "px";
99 bounds.style.height = (displayHeight*displayScale) + "px";
102 var display = document.createElement("div");
103 display.style.position = "relative";
104 display.style.width = displayWidth + "px";
105 display.style.height = displayHeight + "px";
107 // Ensure transformations on display originate at 0,0
108 display.style.transformOrigin =
109 display.style.webkitTransformOrigin =
110 display.style.MozTransformOrigin =
111 display.style.OTransformOrigin =
112 display.style.msTransformOrigin =
115 // Create default layer
116 var default_layer_container = new Guacamole.Client.LayerContainer(displayWidth, displayHeight);
118 // Position default layer
119 var default_layer_container_element = default_layer_container.getElement();
120 default_layer_container_element.style.position = "absolute";
121 default_layer_container_element.style.left = "0px";
122 default_layer_container_element.style.top = "0px";
123 default_layer_container_element.style.overflow = "hidden";
125 // Create cursor layer
126 var cursor = new Guacamole.Client.LayerContainer(0, 0);
127 cursor.getLayer().setChannelMask(Guacamole.Layer.SRC);
129 // Position cursor layer
130 var cursor_element = cursor.getElement();
131 cursor_element.style.position = "absolute";
132 cursor_element.style.left = "0px";
133 cursor_element.style.top = "0px";
135 // Add default layer and cursor to display
136 display.appendChild(default_layer_container.getElement());
137 display.appendChild(cursor.getElement());
139 // Add display to bounds
140 bounds.appendChild(display);
142 // Initially, only default layer exists
143 var layers = [default_layer_container];
145 // No initial buffers
148 tunnel.onerror = function(message) {
149 if (guac_client.onerror)
150 guac_client.onerror(message);
153 function setState(state) {
154 if (state != currentState) {
155 currentState = state;
156 if (guac_client.onstatechange)
157 guac_client.onstatechange(currentState);
161 function isConnected() {
162 return currentState == STATE_CONNECTED
163 || currentState == STATE_WAITING;
166 var cursorHotspotX = 0;
167 var cursorHotspotY = 0;
172 function moveCursor(x, y) {
175 cursor.translate(x - cursorHotspotX, y - cursorHotspotY);
177 // Update stored position
184 * Returns an element containing the display of this Guacamole.Client.
185 * Adding the element returned by this function to an element in the body
186 * of a document will cause the client's display to be visible.
188 * @return {Element} An element containing ths display of this
191 this.getDisplay = function() {
196 * Sends a key event having the given properties as if the user
197 * pressed or released a key.
199 * @param {Boolean} pressed Whether the key is pressed (true) or released
201 * @param {Number} keysym The keysym of the key being pressed or released.
203 this.sendKeyEvent = function(pressed, keysym) {
204 // Do not send requests if not connected
208 tunnel.sendMessage("key", keysym, pressed);
212 * Sends a mouse event having the properties provided by the given mouse
215 * @param {Guacamole.Mouse.State} mouseState The state of the mouse to send
216 * in the mouse event.
218 this.sendMouseState = function(mouseState) {
220 // Do not send requests if not connected
224 // Update client-side cursor
226 Math.floor(mouseState.x),
227 Math.floor(mouseState.y)
232 if (mouseState.left) buttonMask |= 1;
233 if (mouseState.middle) buttonMask |= 2;
234 if (mouseState.right) buttonMask |= 4;
235 if (mouseState.up) buttonMask |= 8;
236 if (mouseState.down) buttonMask |= 16;
239 tunnel.sendMessage("mouse", Math.floor(mouseState.x), Math.floor(mouseState.y), buttonMask);
243 * Sets the clipboard of the remote client to the given text data.
245 * @param {String} data The data to send as the clipboard contents.
247 this.setClipboard = function(data) {
249 // Do not send requests if not connected
253 tunnel.sendMessage("clipboard", data);
257 * Fired whenever the state of this Guacamole.Client changes.
260 * @param {Number} state The new state of the client.
262 this.onstatechange = null;
265 * Fired when the remote client sends a name update.
268 * @param {String} name The new name of this client.
273 * Fired when an error is reported by the remote client, and the connection
277 * @param {String} error A human-readable description of the error.
282 * Fired when the clipboard of the remote client is changing.
285 * @param {String} data The new text data of the remote clipboard.
287 this.onclipboard = null;
290 function getBufferLayer(index) {
293 var buffer = buffers[index];
295 // Create buffer if necessary
296 if (buffer == null) {
297 buffer = new Guacamole.Layer(0, 0);
299 buffers[index] = buffer;
306 function getLayerContainer(index) {
308 var layer = layers[index];
312 layer = new Guacamole.Client.LayerContainer(displayWidth, displayHeight);
313 layers[index] = layer;
315 // Get and position layer
316 var layer_element = layer.getElement();
317 layer_element.style.position = "absolute";
318 layer_element.style.left = "0px";
319 layer_element.style.top = "0px";
320 layer_element.style.overflow = "hidden";
322 // Add to default layer container
323 default_layer_container.getElement().appendChild(layer_element);
331 function getLayer(index) {
333 // If buffer, just get layer
335 return getBufferLayer(index);
337 // Otherwise, retrieve layer from layer container
338 return getLayerContainer(index).getLayer();
343 * Handlers for all defined layer properties.
346 var layerPropertyHandlers = {
348 "miter-limit": function(layer, value) {
349 layer.setMiterLimit(parseFloat(value));
355 * Handlers for all instruction opcodes receivable by a Guacamole protocol
359 var instructionHandlers = {
361 "arc": function(parameters) {
363 var layer = getLayer(parseInt(parameters[0]));
364 var x = parseInt(parameters[1]);
365 var y = parseInt(parameters[2]);
366 var radius = parseInt(parameters[3]);
367 var startAngle = parseFloat(parameters[4]);
368 var endAngle = parseFloat(parameters[5]);
369 var negative = parseInt(parameters[6]);
371 layer.arc(x, y, radius, startAngle, endAngle, negative != 0);
375 "cfill": function(parameters) {
377 var channelMask = parseInt(parameters[0]);
378 var layer = getLayer(parseInt(parameters[1]));
379 var r = parseInt(parameters[2]);
380 var g = parseInt(parameters[3]);
381 var b = parseInt(parameters[4]);
382 var a = parseInt(parameters[5]);
384 layer.setChannelMask(channelMask);
386 layer.fillColor(r, g, b, a);
390 "clip": function(parameters) {
392 var layer = getLayer(parseInt(parameters[0]));
398 "clipboard": function(parameters) {
399 if (guac_client.onclipboard) guac_client.onclipboard(parameters[0]);
402 "close": function(parameters) {
404 var layer = getLayer(parseInt(parameters[0]));
410 "copy": function(parameters) {
412 var srcL = getLayer(parseInt(parameters[0]));
413 var srcX = parseInt(parameters[1]);
414 var srcY = parseInt(parameters[2]);
415 var srcWidth = parseInt(parameters[3]);
416 var srcHeight = parseInt(parameters[4]);
417 var channelMask = parseInt(parameters[5]);
418 var dstL = getLayer(parseInt(parameters[6]));
419 var dstX = parseInt(parameters[7]);
420 var dstY = parseInt(parameters[8]);
422 dstL.setChannelMask(channelMask);
436 "cstroke": function(parameters) {
438 var channelMask = parseInt(parameters[0]);
439 var layer = getLayer(parseInt(parameters[1]));
440 var cap = lineCap[parseInt(parameters[2])];
441 var join = lineJoin[parseInt(parameters[3])];
442 var thickness = parseInt(parameters[4]);
443 var r = parseInt(parameters[5]);
444 var g = parseInt(parameters[6]);
445 var b = parseInt(parameters[7]);
446 var a = parseInt(parameters[8]);
448 layer.setChannelMask(channelMask);
450 layer.strokeColor(cap, join, thickness, r, g, b, a);
454 "cursor": function(parameters) {
456 cursorHotspotX = parseInt(parameters[0]);
457 cursorHotspotY = parseInt(parameters[1]);
458 var srcL = getLayer(parseInt(parameters[2]));
459 var srcX = parseInt(parameters[3]);
460 var srcY = parseInt(parameters[4]);
461 var srcWidth = parseInt(parameters[5]);
462 var srcHeight = parseInt(parameters[6]);
465 cursor.resize(srcWidth, srcHeight);
467 // Draw cursor to cursor layer
468 cursor.getLayer().copy(
478 // Update cursor position (hotspot may have changed)
479 moveCursor(cursorX, cursorY);
483 "curve": function(parameters) {
485 var layer = getLayer(parseInt(parameters[0]));
486 var cp1x = parseInt(parameters[1]);
487 var cp1y = parseInt(parameters[2]);
488 var cp2x = parseInt(parameters[3]);
489 var cp2y = parseInt(parameters[4]);
490 var x = parseInt(parameters[5]);
491 var y = parseInt(parameters[6]);
493 layer.curveTo(cp1x, cp1y, cp2x, cp2y, x, y);
497 "dispose": function(parameters) {
499 var layer_index = parseInt(parameters[0]);
501 // If visible layer, remove from parent
502 if (layer_index > 0) {
504 // Get container element
505 var layer_container = getLayerContainer(layer_index).getElement();
507 // Remove from parent
508 layer_container.parentNode.removeChild(layer_container);
511 delete layers[layer_index];
515 // If buffer, just delete reference
516 else if (layer_index < 0)
517 delete buffers[-1 - layer_index];
519 // Attempting to dispose the root layer currently has no effect.
523 "distort": function(parameters) {
525 var layer_index = parseInt(parameters[0]);
526 var a = parseFloat(parameters[1]);
527 var b = parseFloat(parameters[2]);
528 var c = parseFloat(parameters[3]);
529 var d = parseFloat(parameters[4]);
530 var e = parseFloat(parameters[5]);
531 var f = parseFloat(parameters[6]);
533 // Only valid for visible layers (not buffers)
534 if (layer_index >= 0) {
536 // Get container element
537 var layer_container = getLayerContainer(layer_index).getElement();
539 // Set layer transform
540 layer_container.transform(a, b, c, d, e, f);
546 "error": function(parameters) {
547 if (guac_client.onerror) guac_client.onerror(parameters[0]);
548 guac_client.disconnect();
551 "identity": function(parameters) {
553 var layer = getLayer(parseInt(parameters[0]));
555 layer.setTransform(1, 0, 0, 1, 0, 0);
559 "lfill": function(parameters) {
561 var channelMask = parseInt(parameters[0]);
562 var layer = getLayer(parseInt(parameters[1]));
563 var srcLayer = getLayer(parseInt(parameters[2]));
565 layer.setChannelMask(channelMask);
567 layer.fillLayer(srcLayer);
571 "line": function(parameters) {
573 var layer = getLayer(parseInt(parameters[0]));
574 var x = parseInt(parameters[1]);
575 var y = parseInt(parameters[2]);
581 "lstroke": function(parameters) {
583 var channelMask = parseInt(parameters[0]);
584 var layer = getLayer(parseInt(parameters[1]));
585 var srcLayer = getLayer(parseInt(parameters[2]));
587 layer.setChannelMask(channelMask);
589 layer.strokeLayer(srcLayer);
593 "move": function(parameters) {
595 var layer_index = parseInt(parameters[0]);
596 var parent_index = parseInt(parameters[1]);
597 var x = parseInt(parameters[2]);
598 var y = parseInt(parameters[3]);
599 var z = parseInt(parameters[4]);
601 // Only valid for non-default layers
602 if (layer_index > 0 && parent_index >= 0) {
604 // Get container element
605 var layer_container = getLayerContainer(layer_index);
606 var layer_container_element = layer_container.getElement();
607 var parent = getLayerContainer(parent_index).getElement();
609 // Set parent if necessary
610 if (!(layer_container_element.parentNode === parent))
611 parent.appendChild(layer_container_element);
614 layer_container.translate(x, y);
615 layer_container_element.style.zIndex = z;
621 "name": function(parameters) {
622 if (guac_client.onname) guac_client.onname(parameters[0]);
625 "png": function(parameters) {
627 var channelMask = parseInt(parameters[0]);
628 var layer = getLayer(parseInt(parameters[1]));
629 var x = parseInt(parameters[2]);
630 var y = parseInt(parameters[3]);
631 var data = parameters[4];
633 layer.setChannelMask(channelMask);
638 "data:image/png;base64," + data
641 // If received first update, no longer waiting.
642 if (currentState == STATE_WAITING)
643 setState(STATE_CONNECTED);
647 "pop": function(parameters) {
649 var layer = getLayer(parseInt(parameters[0]));
655 "push": function(parameters) {
657 var layer = getLayer(parseInt(parameters[0]));
663 "rect": function(parameters) {
665 var layer = getLayer(parseInt(parameters[0]));
666 var x = parseInt(parameters[1]);
667 var y = parseInt(parameters[2]);
668 var w = parseInt(parameters[3]);
669 var h = parseInt(parameters[4]);
671 layer.rect(x, y, w, h);
675 "reset": function(parameters) {
677 var layer = getLayer(parseInt(parameters[0]));
683 "set": function(parameters) {
685 var layer = getLayer(parseInt(parameters[0]));
686 var name = parameters[1];
687 var value = parameters[2];
689 // Call property handler if defined
690 var handler = layerPropertyHandlers[name];
692 handler(layer, value);
696 "shade": function(parameters) {
698 var layer_index = parseInt(parameters[0]);
699 var a = parseInt(parameters[1]);
701 // Only valid for visible layers (not buffers)
702 if (layer_index >= 0) {
704 // Get container element
705 var layer_container = getLayerContainer(layer_index).getElement();
708 layer_container.style.opacity = a/255.0;
714 "size": function(parameters) {
716 var layer_index = parseInt(parameters[0]);
717 var width = parseInt(parameters[1]);
718 var height = parseInt(parameters[2]);
720 // If not buffer, resize layer and container
721 if (layer_index >= 0) {
724 var layer_container = getLayerContainer(layer_index);
725 layer_container.resize(width, height);
727 // If layer is default, resize display
728 if (layer_index == 0) {
730 displayWidth = width;
731 displayHeight = height;
733 // Update (set) display size
734 display.style.width = displayWidth + "px";
735 display.style.height = displayHeight + "px";
737 // Update bounds size
738 bounds.style.width = (displayWidth*displayScale) + "px";
739 bounds.style.height = (displayHeight*displayScale) + "px";
745 // If buffer, resize layer only
747 var layer = getBufferLayer(parseInt(parameters[0]));
748 layer.resize(width, height);
753 "start": function(parameters) {
755 var layer = getLayer(parseInt(parameters[0]));
756 var x = parseInt(parameters[1]);
757 var y = parseInt(parameters[2]);
763 "sync": function(parameters) {
765 var timestamp = parameters[0];
767 // When all layers have finished rendering all instructions
768 // UP TO THIS POINT IN TIME, send sync response.
770 var layersToSync = 0;
771 function syncLayer() {
775 // Send sync response when layers are finished
776 if (layersToSync == 0) {
777 if (timestamp != currentTimestamp) {
778 tunnel.sendMessage("sync", timestamp);
779 currentTimestamp = timestamp;
785 // Count active, not-ready layers and install sync tracking hooks
786 for (var i=0; i<layers.length; i++) {
788 var layer = layers[i].getLayer();
789 if (layer && !layer.isReady()) {
791 layer.sync(syncLayer);
796 // If all layers are ready, then we didn't install any hooks.
797 // Send sync message now,
798 if (layersToSync == 0) {
799 if (timestamp != currentTimestamp) {
800 tunnel.sendMessage("sync", timestamp);
801 currentTimestamp = timestamp;
807 "transfer": function(parameters) {
809 var srcL = getLayer(parseInt(parameters[0]));
810 var srcX = parseInt(parameters[1]);
811 var srcY = parseInt(parameters[2]);
812 var srcWidth = parseInt(parameters[3]);
813 var srcHeight = parseInt(parameters[4]);
814 var transferFunction = Guacamole.Client.DefaultTransferFunction[parameters[5]];
815 var dstL = getLayer(parseInt(parameters[6]));
816 var dstX = parseInt(parameters[7]);
817 var dstY = parseInt(parameters[8]);
832 "transform": function(parameters) {
834 var layer = getLayer(parseInt(parameters[0]));
835 var a = parseFloat(parameters[1]);
836 var b = parseFloat(parameters[2]);
837 var c = parseFloat(parameters[3]);
838 var d = parseFloat(parameters[4]);
839 var e = parseFloat(parameters[5]);
840 var f = parseFloat(parameters[6]);
842 layer.transform(a, b, c, d, e, f);
849 tunnel.oninstruction = function(opcode, parameters) {
851 var handler = instructionHandlers[opcode];
858 this.disconnect = function() {
860 // Only attempt disconnection not disconnected.
861 if (currentState != STATE_DISCONNECTED
862 && currentState != STATE_DISCONNECTING) {
864 setState(STATE_DISCONNECTING);
868 window.clearInterval(pingInterval);
870 // Send disconnect message and disconnect
871 tunnel.sendMessage("disconnect");
873 setState(STATE_DISCONNECTED);
879 this.connect = function(data) {
881 setState(STATE_CONNECTING);
884 tunnel.connect(data);
887 setState(STATE_IDLE);
891 // Ping every 5 seconds (ensure connection alive)
892 pingInterval = window.setInterval(function() {
893 tunnel.sendMessage("sync", currentTimestamp);
896 setState(STATE_WAITING);
899 this.scale = function(scale) {
901 display.style.transform =
902 display.style.WebkitTransform =
903 display.style.MozTransform =
904 display.style.OTransform =
905 display.style.msTransform =
907 "scale(" + scale + "," + scale + ")";
909 displayScale = scale;
911 // Update bounds size
912 bounds.style.width = (displayWidth*displayScale) + "px";
913 bounds.style.height = (displayHeight*displayScale) + "px";
917 this.getScale = function() {
924 * Simple container for Guacamole.Layer, allowing layers to be easily
925 * repositioned and nested. This allows certain operations to be accelerated
926 * through DOM manipulation, rather than raster operations.
930 * @param {Number} width The width of the Layer, in pixels. The canvas element
931 * backing this Layer will be given this width.
933 * @param {Number} height The height of the Layer, in pixels. The canvas element
934 * backing this Layer will be given this height.
936 Guacamole.Client.LayerContainer = function(width, height) {
939 * Reference to this LayerContainer.
942 var layer_container = this;
944 // Create layer with given size
945 var layer = new Guacamole.Layer(width, height);
947 // Set layer position
948 var canvas = layer.getCanvas();
949 canvas.style.position = "absolute";
950 canvas.style.left = "0px";
951 canvas.style.top = "0px";
953 // Create div with given size
954 var div = document.createElement("div");
955 div.appendChild(canvas);
956 div.style.width = width + "px";
957 div.style.height = height + "px";
960 * Changes the size of this LayerContainer and the contained Layer to the
961 * given width and height.
963 * @param {Number} width The new width to assign to this Layer.
964 * @param {Number} height The new height to assign to this Layer.
966 this.resize = function(width, height) {
969 layer.resize(width, height);
971 // Resize containing div
972 div.style.width = width + "px";
973 div.style.height = height + "px";
978 * Returns the Layer contained within this LayerContainer.
979 * @returns {Guacamole.Layer} The Layer contained within this LayerContainer.
981 this.getLayer = function() {
986 * Returns the element containing the Layer within this LayerContainer.
987 * @returns {Element} The element containing the Layer within this LayerContainer.
989 this.getElement = function() {
994 * The translation component of this LayerContainer's transform.
997 var translate = "translate(0px, 0px)"; // (0, 0)
1000 * The arbitrary matrix component of this LayerContainer's transform.
1003 var matrix = "matrix(1, 0, 0, 1, 0, 0)"; // Identity
1006 * Moves the upper-left corner of this LayerContainer to the given X and Y
1009 * @param {Number} x The X coordinate to move to.
1010 * @param {Number} y The Y coordinate to move to.
1012 this.translate = function(x, y) {
1014 // Generate translation
1015 translate = "translate("
1019 // Set layer transform
1020 div.style.transform =
1021 div.style.WebkitTransform =
1022 div.style.MozTransform =
1023 div.style.OTransform =
1024 div.style.msTransform =
1026 translate + " " + matrix;
1031 * Applies the given affine transform (defined with six values from the
1032 * transform's matrix).
1034 * @param {Number} a The first value in the affine transform's matrix.
1035 * @param {Number} b The second value in the affine transform's matrix.
1036 * @param {Number} c The third value in the affine transform's matrix.
1037 * @param {Number} d The fourth value in the affine transform's matrix.
1038 * @param {Number} e The fifth value in the affine transform's matrix.
1039 * @param {Number} f The sixth value in the affine transform's matrix.
1041 this.transform = function(a, b, c, d, e, f) {
1043 // Generate matrix transformation
1051 "matrix(" + a + "," + b + "," + c + "," + d + "," + e + "," + f + ")";
1053 // Set layer transform
1054 div.style.transform =
1055 div.style.WebkitTransform =
1056 div.style.MozTransform =
1057 div.style.OTransform =
1058 div.style.msTransform =
1060 translate + " " + matrix;
1067 * Map of all Guacamole binary raster operations to transfer functions.
1070 Guacamole.Client.DefaultTransferFunction = {
1073 0x0: function (src, dst) {
1074 dst.red = dst.green = dst.blue = 0x00;
1078 0xF: function (src, dst) {
1079 dst.red = dst.green = dst.blue = 0xFF;
1083 0x3: function (src, dst) {
1085 dst.green = src.green;
1086 dst.blue = src.blue;
1087 dst.alpha = src.alpha;
1091 0x5: function (src, dst) {
1096 0xC: function (src, dst) {
1097 dst.red = 0xFF & ~src.red;
1098 dst.green = 0xFF & ~src.green;
1099 dst.blue = 0xFF & ~src.blue;
1100 dst.alpha = src.alpha;
1104 0xA: function (src, dst) {
1105 dst.red = 0xFF & ~dst.red;
1106 dst.green = 0xFF & ~dst.green;
1107 dst.blue = 0xFF & ~dst.blue;
1111 0x1: function (src, dst) {
1112 dst.red = ( src.red & dst.red);
1113 dst.green = ( src.green & dst.green);
1114 dst.blue = ( src.blue & dst.blue);
1118 0xE: function (src, dst) {
1119 dst.red = 0xFF & ~( src.red & dst.red);
1120 dst.green = 0xFF & ~( src.green & dst.green);
1121 dst.blue = 0xFF & ~( src.blue & dst.blue);
1125 0x7: function (src, dst) {
1126 dst.red = ( src.red | dst.red);
1127 dst.green = ( src.green | dst.green);
1128 dst.blue = ( src.blue | dst.blue);
1132 0x8: function (src, dst) {
1133 dst.red = 0xFF & ~( src.red | dst.red);
1134 dst.green = 0xFF & ~( src.green | dst.green);
1135 dst.blue = 0xFF & ~( src.blue | dst.blue);
1139 0x6: function (src, dst) {
1140 dst.red = ( src.red ^ dst.red);
1141 dst.green = ( src.green ^ dst.green);
1142 dst.blue = ( src.blue ^ dst.blue);
1146 0x9: function (src, dst) {
1147 dst.red = 0xFF & ~( src.red ^ dst.red);
1148 dst.green = 0xFF & ~( src.green ^ dst.green);
1149 dst.blue = 0xFF & ~( src.blue ^ dst.blue);
1152 /* AND inverted source */
1153 0x4: function (src, dst) {
1154 dst.red = 0xFF & (~src.red & dst.red);
1155 dst.green = 0xFF & (~src.green & dst.green);
1156 dst.blue = 0xFF & (~src.blue & dst.blue);
1159 /* OR inverted source */
1160 0xD: function (src, dst) {
1161 dst.red = 0xFF & (~src.red | dst.red);
1162 dst.green = 0xFF & (~src.green | dst.green);
1163 dst.blue = 0xFF & (~src.blue | dst.blue);
1166 /* AND inverted destination */
1167 0x2: function (src, dst) {
1168 dst.red = 0xFF & ( src.red & ~dst.red);
1169 dst.green = 0xFF & ( src.green & ~dst.green);
1170 dst.blue = 0xFF & ( src.blue & ~dst.blue);
1173 /* OR inverted destination */
1174 0xB: function (src, dst) {
1175 dst.red = 0xFF & ( src.red | ~dst.red);
1176 dst.green = 0xFF & ( src.green | ~dst.green);
1177 dst.blue = 0xFF & ( src.blue | ~dst.blue);