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