]> git.proxmox.com Git - mirror_novnc.git/blob - include/canvas.js
New API. Refactor Canvas and RFB objects.
[mirror_novnc.git] / include / canvas.js
1 /*
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2010 Joel Martin
4 * Licensed under LGPL-3 (see LICENSE.LGPL-3)
5 *
6 * See README.md for usage and integration instructions.
7 */
8
9 "use strict";
10 /*jslint browser: true, white: false, bitwise: false */
11 /*global window, Util, Base64 */
12
13 function Canvas(conf) {
14
15 conf = conf || {}; // Configuration
16 var that = {}, // Public API interface
17
18 // Pre-declare functions used before definitions (jslint)jslint
19 setFillColor, fillRect,
20
21 // Private Canvas namespace variables
22 c_forceCanvas = false,
23
24 c_width = 0,
25 c_height = 0,
26
27 c_prevStyle = "",
28
29 c_keyPress = null,
30 c_mouseButton = null,
31 c_mouseMove = null;
32
33
34 // Capability settings, default can be overridden
35 Util.conf_default(conf, that, 'prefer_js', null);
36 Util.conf_default(conf, that, 'cursor_uri', null);
37
38 // Configuration settings
39 Util.conf_default(conf, that, 'target', null);
40 Util.conf_default(conf, that, 'true_color', true);
41 Util.conf_default(conf, that, 'focused', true);
42 Util.conf_default(conf, that, 'colourMap', []);
43 Util.conf_default(conf, that, 'scale', 1);
44
45 // Override some specific getters/setters
46 that.set_prefer_js = function(val) {
47 if (val && c_forceCanvas) {
48 Util.Warn("Preferring Javascript to Canvas ops is not supported");
49 return false;
50 }
51 conf.prefer_js = val;
52 return true;
53 };
54
55 that.get_colourMap = function(idx) {
56 if (typeof idx === 'undefined') {
57 return conf.colourMap;
58 } else {
59 return conf.colourMap[idx];
60 }
61 };
62
63 that.set_colourMap = function(val, idx) {
64 if (typeof idx === 'undefined') {
65 conf.colourMap = val;
66 } else {
67 conf.colourMap[idx] = val;
68 }
69 };
70
71 // Add some other getters/setters
72 that.get_width = function() {
73 return c_width;
74 };
75 that.get_height = function() {
76 return c_height;
77 };
78
79
80
81 //
82 // Private functions
83 //
84
85 // Create the public API interface
86 function constructor() {
87 Util.Debug(">> Canvas.init");
88
89 var c, ctx, imgTest, tval, i, curDat, curSave,
90 has_imageData = false;
91
92 if (! conf.target) { throw("target must be set"); }
93
94 if (typeof conf.target === 'string') {
95 conf.target = window.$(conf.target);
96 }
97
98 c = conf.target;
99
100 if (! c.getContext) { throw("no getContext method"); }
101
102 if (! conf.ctx) { conf.ctx = c.getContext('2d'); }
103 ctx = conf.ctx;
104
105 that.clear();
106
107 /*
108 * Determine browser Canvas feature support
109 * and select fastest rendering methods
110 */
111 tval = 0;
112 try {
113 imgTest = ctx.getImageData(0, 0, 1,1);
114 imgTest.data[0] = 123;
115 imgTest.data[3] = 255;
116 ctx.putImageData(imgTest, 0, 0);
117 tval = ctx.getImageData(0, 0, 1, 1).data[0];
118 if (tval === 123) {
119 has_imageData = true;
120 }
121 } catch (exc1) {}
122
123 if (has_imageData) {
124 Util.Info("Canvas supports imageData");
125 c_forceCanvas = false;
126 if (ctx.createImageData) {
127 // If it's there, it's faster
128 Util.Info("Using Canvas createImageData");
129 that.imageData = that.imageDataCreate;
130 } else if (ctx.getImageData) {
131 Util.Info("Using Canvas getImageData");
132 that.imageData = that.imageDataGet;
133 }
134 Util.Info("Prefering javascript operations");
135 if (conf.prefer_js === null) {
136 conf.prefer_js = true;
137 }
138 that.rgbxImage = that.rgbxImageData;
139 that.cmapImage = that.cmapImageData;
140 } else {
141 Util.Warn("Canvas lacks imageData, using fillRect (slow)");
142 c_forceCanvas = true;
143 conf.prefer_js = false;
144 that.rgbxImage = that.rgbxImageFill;
145 that.cmapImage = that.cmapImageFill;
146 }
147
148 /*
149 * Determine browser support for setting the cursor via data URI
150 * scheme
151 */
152 curDat = [];
153 for (i=0; i < 8 * 8 * 4; i += 1) {
154 curDat.push(255);
155 }
156 try {
157 curSave = c.style.cursor;
158 that.changeCursor(curDat, curDat, 2, 2, 8, 8);
159 if (c.style.cursor) {
160 if (conf.cursor_uri === null) {
161 conf.cursor_uri = true;
162 }
163 Util.Info("Data URI scheme cursor supported");
164 } else {
165 if (conf.cursor_uri === null) {
166 conf.cursor_uri = false;
167 }
168 Util.Warn("Data URI scheme cursor not supported");
169 }
170 c.style.cursor = curSave;
171 } catch (exc2) {
172 Util.Error("Data URI scheme cursor test exception: " + exc2);
173 conf.cursor_uri = false;
174 }
175
176 conf.focused = true;
177
178 Util.Debug("<< Canvas.init");
179 return that ;
180 }
181
182 /* Translate DOM key event to keysym value */
183 function getKeysym(e) {
184 var evt, keysym;
185 evt = (e ? e : window.event);
186
187 /* Remap modifier and special keys */
188 switch ( evt.keyCode ) {
189 case 8 : keysym = 0xFF08; break; // BACKSPACE
190 case 9 : keysym = 0xFF09; break; // TAB
191 case 13 : keysym = 0xFF0D; break; // ENTER
192 case 27 : keysym = 0xFF1B; break; // ESCAPE
193 case 45 : keysym = 0xFF63; break; // INSERT
194 case 46 : keysym = 0xFFFF; break; // DELETE
195 case 36 : keysym = 0xFF50; break; // HOME
196 case 35 : keysym = 0xFF57; break; // END
197 case 33 : keysym = 0xFF55; break; // PAGE_UP
198 case 34 : keysym = 0xFF56; break; // PAGE_DOWN
199 case 37 : keysym = 0xFF51; break; // LEFT
200 case 38 : keysym = 0xFF52; break; // UP
201 case 39 : keysym = 0xFF53; break; // RIGHT
202 case 40 : keysym = 0xFF54; break; // DOWN
203 case 112 : keysym = 0xFFBE; break; // F1
204 case 113 : keysym = 0xFFBF; break; // F2
205 case 114 : keysym = 0xFFC0; break; // F3
206 case 115 : keysym = 0xFFC1; break; // F4
207 case 116 : keysym = 0xFFC2; break; // F5
208 case 117 : keysym = 0xFFC3; break; // F6
209 case 118 : keysym = 0xFFC4; break; // F7
210 case 119 : keysym = 0xFFC5; break; // F8
211 case 120 : keysym = 0xFFC6; break; // F9
212 case 121 : keysym = 0xFFC7; break; // F10
213 case 122 : keysym = 0xFFC8; break; // F11
214 case 123 : keysym = 0xFFC9; break; // F12
215 case 16 : keysym = 0xFFE1; break; // SHIFT
216 case 17 : keysym = 0xFFE3; break; // CONTROL
217 //case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option)
218 case 18 : keysym = 0xFFE9; break; // Left ALT (Mac Command)
219 default : keysym = evt.keyCode; break;
220 }
221
222 /* Remap symbols */
223 switch (keysym) {
224 case 186 : keysym = 59; break; // ; (IE)
225 case 187 : keysym = 61; break; // = (IE)
226 case 188 : keysym = 44; break; // , (Mozilla, IE)
227 case 109 : // - (Mozilla)
228 if (Util.Engine.gecko) {
229 keysym = 45; }
230 break;
231 case 189 : keysym = 45; break; // - (IE)
232 case 190 : keysym = 46; break; // . (Mozilla, IE)
233 case 191 : keysym = 47; break; // / (Mozilla, IE)
234 case 192 : keysym = 96; break; // ` (Mozilla, IE)
235 case 219 : keysym = 91; break; // [ (Mozilla, IE)
236 case 220 : keysym = 92; break; // \ (Mozilla, IE)
237 case 221 : keysym = 93; break; // ] (Mozilla, IE)
238 case 222 : keysym = 39; break; // ' (Mozilla, IE)
239 }
240
241 /* Remap shifted and unshifted keys */
242 if (!!evt.shiftKey) {
243 switch (keysym) {
244 case 48 : keysym = 41 ; break; // ) (shifted 0)
245 case 49 : keysym = 33 ; break; // ! (shifted 1)
246 case 50 : keysym = 64 ; break; // @ (shifted 2)
247 case 51 : keysym = 35 ; break; // # (shifted 3)
248 case 52 : keysym = 36 ; break; // $ (shifted 4)
249 case 53 : keysym = 37 ; break; // % (shifted 5)
250 case 54 : keysym = 94 ; break; // ^ (shifted 6)
251 case 55 : keysym = 38 ; break; // & (shifted 7)
252 case 56 : keysym = 42 ; break; // * (shifted 8)
253 case 57 : keysym = 40 ; break; // ( (shifted 9)
254
255 case 59 : keysym = 58 ; break; // : (shifted `)
256 case 61 : keysym = 43 ; break; // + (shifted ;)
257 case 44 : keysym = 60 ; break; // < (shifted ,)
258 case 45 : keysym = 95 ; break; // _ (shifted -)
259 case 46 : keysym = 62 ; break; // > (shifted .)
260 case 47 : keysym = 63 ; break; // ? (shifted /)
261 case 96 : keysym = 126; break; // ~ (shifted `)
262 case 91 : keysym = 123; break; // { (shifted [)
263 case 92 : keysym = 124; break; // | (shifted \)
264 case 93 : keysym = 125; break; // } (shifted ])
265 case 39 : keysym = 34 ; break; // " (shifted ')
266 }
267 } else if ((keysym >= 65) && (keysym <=90)) {
268 /* Remap unshifted A-Z */
269 keysym += 32;
270 }
271
272 return keysym;
273 }
274
275 function onMouseButton(e, down) {
276 var evt, pos, bmask;
277 if (! conf.focused) {
278 return true;
279 }
280 evt = (e ? e : window.event);
281 pos = Util.getEventPosition(e, conf.target, conf.scale);
282 bmask = 1 << evt.button;
283 //Util.Debug('mouse ' + pos.x + "," + pos.y + " down: " + down + " bmask: " + bmask);
284 if (c_mouseButton) {
285 c_mouseButton(pos.x, pos.y, down, bmask);
286 }
287 Util.stopEvent(e);
288 return false;
289 }
290
291 function onMouseDown(e) {
292 onMouseButton(e, 1);
293 }
294
295 function onMouseUp(e) {
296 onMouseButton(e, 0);
297 }
298
299 function onMouseWheel(e) {
300 var evt, pos, bmask, wheelData;
301 evt = (e ? e : window.event);
302 pos = Util.getEventPosition(e, conf.target, conf.scale);
303 wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40;
304 if (wheelData > 0) {
305 bmask = 1 << 3;
306 } else {
307 bmask = 1 << 4;
308 }
309 //Util.Debug('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y);
310 if (c_mouseButton) {
311 c_mouseButton(pos.x, pos.y, 1, bmask);
312 c_mouseButton(pos.x, pos.y, 0, bmask);
313 }
314 Util.stopEvent(e);
315 return false;
316 }
317
318 function onMouseMove(e) {
319 var evt, pos;
320 evt = (e ? e : window.event);
321 pos = Util.getEventPosition(e, conf.target, conf.scale);
322 //Util.Debug('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + "," + pos.y);
323 if (c_mouseMove) {
324 c_mouseMove(pos.x, pos.y);
325 }
326 }
327
328 function onKeyDown(e) {
329 //Util.Debug("keydown: " + getKeysym(e));
330 if (! conf.focused) {
331 return true;
332 }
333 if (c_keyPress) {
334 c_keyPress(getKeysym(e), 1);
335 }
336 Util.stopEvent(e);
337 return false;
338 }
339
340 function onKeyUp(e) {
341 //Util.Debug("keyup: " + getKeysym(e));
342 if (! conf.focused) {
343 return true;
344 }
345 if (c_keyPress) {
346 c_keyPress(getKeysym(e), 0);
347 }
348 Util.stopEvent(e);
349 return false;
350 }
351
352 function onMouseDisable(e) {
353 var evt, pos;
354 if (! conf.focused) {
355 return true;
356 }
357 evt = (e ? e : window.event);
358 pos = Util.getPosition(conf.target);
359 /* Stop propagation if inside canvas area */
360 if ((evt.clientX >= pos.x) &&
361 (evt.clientY >= pos.y) &&
362 (evt.clientX < (pos.x + c_width)) &&
363 (evt.clientY < (pos.y + c_height))) {
364 //Util.Debug("mouse event disabled");
365 Util.stopEvent(e);
366 return false;
367 }
368 //Util.Debug("mouse event not disabled");
369 return true;
370 }
371
372 //
373 // Public API interface functions
374 //
375
376 that.getContext = function () {
377 return conf.ctx;
378 };
379
380 that.start = function(keyPressFunc, mouseButtonFunc, mouseMoveFunc) {
381 var c;
382 Util.Debug(">> Canvas.start");
383
384 c = conf.target;
385 c_keyPress = keyPressFunc || null;
386 c_mouseButton = mouseButtonFunc || null;
387 c_mouseMove = mouseMoveFunc || null;
388
389 Util.addEvent(document, 'keydown', onKeyDown);
390 Util.addEvent(document, 'keyup', onKeyUp);
391 Util.addEvent(c, 'mousedown', onMouseDown);
392 Util.addEvent(c, 'mouseup', onMouseUp);
393 Util.addEvent(c, 'mousemove', onMouseMove);
394 Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
395 onMouseWheel);
396
397 /* Work around right and middle click browser behaviors */
398 Util.addEvent(document, 'click', onMouseDisable);
399 Util.addEvent(document.body, 'contextmenu', onMouseDisable);
400
401 Util.Debug("<< Canvas.start");
402 };
403
404 that.rescale = function(factor) {
405 var c, tp, x, y,
406 properties = ['transform', 'WebkitTransform', 'MozTransform', null];
407 c = conf.target;
408 tp = properties.shift();
409 while (tp) {
410 if (typeof c.style[tp] !== 'undefined') {
411 break;
412 }
413 tp = properties.shift();
414 }
415
416 if (tp === null) {
417 Util.Debug("No scaling support");
418 return;
419 }
420
421 if (conf.scale === factor) {
422 //Util.Debug("Canvas already scaled to '" + factor + "'");
423 return;
424 }
425
426 conf.scale = factor;
427 x = c.width - c.width * factor;
428 y = c.height - c.height * factor;
429 c.style[tp] = "scale(" + conf.scale + ") translate(-" + x + "px, -" + y + "px)";
430 };
431
432 that.resize = function(width, height, true_color) {
433 var c = conf.target;
434
435 if (typeof true_color !== "undefined") {
436 conf.true_color = true_color;
437 }
438
439 c.width = width;
440 c.height = height;
441
442 c_width = c.offsetWidth;
443 c_height = c.offsetHeight;
444
445 that.rescale(conf.scale);
446 };
447
448 that.clear = function() {
449 that.resize(640, 20);
450 conf.ctx.clearRect(0, 0, c_width, c_height);
451 };
452
453 that.stop = function() {
454 var c = conf.target;
455 Util.removeEvent(document, 'keydown', onKeyDown);
456 Util.removeEvent(document, 'keyup', onKeyUp);
457 Util.removeEvent(c, 'mousedown', onMouseDown);
458 Util.removeEvent(c, 'mouseup', onMouseUp);
459 Util.removeEvent(c, 'mousemove', onMouseMove);
460 Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
461 onMouseWheel);
462
463 /* Work around right and middle click browser behaviors */
464 Util.removeEvent(document, 'click', onMouseDisable);
465 Util.removeEvent(document.body, 'contextmenu', onMouseDisable);
466
467 // Turn off cursor rendering
468 if (conf.cursor_uri) {
469 c.style.cursor = "default";
470 }
471 };
472
473 setFillColor = function(color) {
474 var rgb, newStyle;
475 if (conf.true_color) {
476 rgb = color;
477 } else {
478 rgb = conf.colourMap[color[0]];
479 }
480 if (newStyle !== c_prevStyle) {
481 newStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";
482 conf.ctx.fillStyle = newStyle;
483 c_prevStyle = newStyle;
484 }
485 };
486 that.setFillColor = setFillColor;
487
488 fillRect = function(x, y, width, height, color) {
489 setFillColor(color);
490 conf.ctx.fillRect(x, y, width, height);
491 };
492 that.fillRect = fillRect;
493
494 that.copyImage = function(old_x, old_y, new_x, new_y, width, height) {
495 conf.ctx.drawImage(conf.target, old_x, old_y, width, height,
496 new_x, new_y, width, height);
497 };
498
499 /*
500 * Tile rendering functions optimized for rendering engines.
501 *
502 * - In Chrome/webkit, Javascript image data array manipulations are
503 * faster than direct Canvas fillStyle, fillRect rendering. In
504 * gecko, Javascript array handling is much slower.
505 */
506 that.getTile = function(x, y, width, height, color) {
507 var img, data, p, rgb, red, green, blue, j, i;
508 img = {'x': x, 'y': y, 'width': width, 'height': height,
509 'data': []};
510 if (conf.prefer_js) {
511 data = img.data;
512 if (conf.true_color) {
513 rgb = color;
514 } else {
515 rgb = conf.colourMap[color[0]];
516 }
517 red = rgb[0];
518 green = rgb[1];
519 blue = rgb[2];
520 for (j = 0; j < height; j += 1) {
521 for (i = 0; i < width; i += 1) {
522 p = (i + (j * width) ) * 4;
523 data[p + 0] = red;
524 data[p + 1] = green;
525 data[p + 2] = blue;
526 //data[p + 3] = 255; // Set Alpha
527 }
528 }
529 } else {
530 fillRect(x, y, width, height, color);
531 }
532 return img;
533 };
534
535 that.setSubTile = function(img, x, y, w, h, color) {
536 var data, p, rgb, red, green, blue, width, j, i;
537 if (conf.prefer_js) {
538 data = img.data;
539 width = img.width;
540 if (conf.true_color) {
541 rgb = color;
542 } else {
543 rgb = conf.colourMap[color[0]];
544 }
545 red = rgb[0];
546 green = rgb[1];
547 blue = rgb[2];
548 for (j = 0; j < h; j += 1) {
549 for (i = 0; i < w; i += 1) {
550 p = (x + i + ((y + j) * width) ) * 4;
551 data[p + 0] = red;
552 data[p + 1] = green;
553 data[p + 2] = blue;
554 //img.data[p + 3] = 255; // Set Alpha
555 }
556 }
557 } else {
558 fillRect(img.x + x, img.y + y, w, h, color);
559 }
560 };
561
562 that.putTile = function(img) {
563 if (conf.prefer_js) {
564 that.rgbxImage(img.x, img.y, img.width, img.height, img.data, 0);
565 } else {
566 // No-op, under gecko already done by setSubTile
567 }
568 };
569
570 that.imageDataGet = function(width, height) {
571 return conf.ctx.getImageData(0, 0, width, height);
572 };
573 that.imageDataCreate = function(width, height) {
574 return conf.ctx.createImageData(width, height);
575 };
576
577 that.rgbxImageData = function(x, y, width, height, arr, offset) {
578 var img, i, j, data;
579 img = that.imageData(width, height);
580 data = img.data;
581 for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) {
582 data[i + 0] = arr[j + 0];
583 data[i + 1] = arr[j + 1];
584 data[i + 2] = arr[j + 2];
585 data[i + 3] = 255; // Set Alpha
586 }
587 conf.ctx.putImageData(img, x, y);
588 };
589
590 // really slow fallback if we don't have imageData
591 that.rgbxImageFill = function(x, y, width, height, arr, offset) {
592 var i, j, sx = 0, sy = 0;
593 for (i=0, j=offset; i < (width * height); i+=1, j+=4) {
594 fillRect(x+sx, y+sy, 1, 1, [arr[j+0], arr[j+1], arr[j+2]]);
595 sx += 1;
596 if ((sx % width) === 0) {
597 sx = 0;
598 sy += 1;
599 }
600 }
601 };
602
603 that.cmapImageData = function(x, y, width, height, arr, offset) {
604 var img, i, j, data, rgb, cmap;
605 img = that.imageData(width, height);
606 data = img.data;
607 cmap = conf.colourMap;
608 for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) {
609 rgb = cmap[arr[j]];
610 data[i + 0] = rgb[0];
611 data[i + 1] = rgb[1];
612 data[i + 2] = rgb[2];
613 data[i + 3] = 255; // Set Alpha
614 }
615 conf.ctx.putImageData(img, x, y);
616 };
617
618 that.cmapImageFill = function(x, y, width, height, arr, offset) {
619 var i, j, sx = 0, sy = 0, cmap;
620 cmap = conf.colourMap;
621 for (i=0, j=offset; i < (width * height); i+=1, j+=1) {
622 fillRect(x+sx, y+sy, 1, 1, [arr[j]]);
623 sx += 1;
624 if ((sx % width) === 0) {
625 sx = 0;
626 sy += 1;
627 }
628 }
629 };
630
631
632 that.blitImage = function(x, y, width, height, arr, offset) {
633 if (conf.true_color) {
634 that.rgbxImage(x, y, width, height, arr, offset);
635 } else {
636 that.cmapImage(x, y, width, height, arr, offset);
637 }
638 };
639
640 that.blitStringImage = function(str, x, y) {
641 var img = new Image();
642 img.onload = function () { conf.ctx.drawImage(img, x, y); };
643 img.src = str;
644 };
645
646 that.changeCursor = function(pixels, mask, hotx, hoty, w, h) {
647 var cur = [], cmap, rgb, IHDRsz, ANDsz, XORsz, url, idx, alpha, x, y;
648 //Util.Debug(">> changeCursor, x: " + hotx + ", y: " + hoty + ", w: " + w + ", h: " + h);
649
650 if (conf.cursor_uri === false) {
651 Util.Warn("changeCursor called but no cursor data URI support");
652 return;
653 }
654
655 cmap = conf.colourMap;
656 IHDRsz = 40;
657 ANDsz = w * h * 4;
658 XORsz = Math.ceil( (w * h) / 8.0 );
659
660 // Main header
661 cur.push16le(0); // Reserved
662 cur.push16le(2); // .CUR type
663 cur.push16le(1); // Number of images, 1 for non-animated ico
664
665 // Cursor #1 header
666 cur.push(w); // width
667 cur.push(h); // height
668 cur.push(0); // colors, 0 -> true-color
669 cur.push(0); // reserved
670 cur.push16le(hotx); // hotspot x coordinate
671 cur.push16le(hoty); // hotspot y coordinate
672 cur.push32le(IHDRsz + XORsz + ANDsz); // cursor data byte size
673 cur.push32le(22); // offset of cursor data in the file
674
675 // Cursor #1 InfoHeader
676 cur.push32le(IHDRsz); // Infoheader size
677 cur.push32le(w); // Cursor width
678 cur.push32le(h*2); // XOR+AND height
679 cur.push16le(1); // number of planes
680 cur.push16le(32); // bits per pixel
681 cur.push32le(0); // Type of compression
682 cur.push32le(XORsz + ANDsz); // Size of Image
683 cur.push32le(0);
684 cur.push32le(0);
685 cur.push32le(0);
686 cur.push32le(0);
687
688 // XOR/color data
689 for (y = h-1; y >= 0; y -= 1) {
690 for (x = 0; x < w; x += 1) {
691 idx = y * Math.ceil(w / 8) + Math.floor(x/8);
692 alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0;
693
694 if (conf.true_color) {
695 idx = ((w * y) + x) * 4;
696 cur.push(pixels[idx + 2]); // blue
697 cur.push(pixels[idx + 1]); // green
698 cur.push(pixels[idx + 0]); // red
699 cur.push(alpha); // red
700 } else {
701 idx = (w * y) + x;
702 rgb = cmap[pixels[idx]];
703 cur.push(rgb[2]); // blue
704 cur.push(rgb[1]); // green
705 cur.push(rgb[0]); // red
706 cur.push(alpha); // alpha
707 }
708 }
709 }
710
711 // AND/bitmask data (ignored, just needs to be right size)
712 for (y = 0; y < h; y += 1) {
713 for (x = 0; x < Math.ceil(w / 8); x += 1) {
714 cur.push(0x00);
715 }
716 }
717
718 url = "data:image/x-icon;base64," + Base64.encode(cur);
719 conf.target.style.cursor = "url(" + url + ") " + hotx + " " + hoty + ", default";
720 //Util.Debug("<< changeCursor, cur.length: " + cur.length);
721 };
722
723
724
725 return constructor(); // Return the public API interface
726
727 } // End of Canvas()
728