canvas.js 20 KB

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