canvas.js 22 KB

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