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