test.display.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. // requires local modules: util, base64, display
  2. // requires test modules: assertions
  3. /* jshint expr: true */
  4. var expect = chai.expect;
  5. describe('Display/Canvas Helper', function () {
  6. var checked_data = [
  7. 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
  8. 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
  9. 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
  10. 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
  11. ];
  12. checked_data = new Uint8Array(checked_data);
  13. var basic_data = [0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255];
  14. basic_data = new Uint8Array(basic_data);
  15. function make_image_canvas (input_data) {
  16. var canvas = document.createElement('canvas');
  17. canvas.width = 4;
  18. canvas.height = 4;
  19. var ctx = canvas.getContext('2d');
  20. var data = ctx.createImageData(4, 4);
  21. for (var i = 0; i < checked_data.length; i++) { data.data[i] = input_data[i]; }
  22. ctx.putImageData(data, 0, 0);
  23. return canvas;
  24. }
  25. describe('checking for cursor uri support', function () {
  26. beforeEach(function () {
  27. this._old_browser_supports_cursor_uris = Util.browserSupportsCursorURIs;
  28. });
  29. it('should disable cursor URIs if there is no support', function () {
  30. Util.browserSupportsCursorURIs = function () { return false; };
  31. var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false });
  32. expect(display._cursor_uri).to.be.false;
  33. });
  34. it('should enable cursor URIs if there is support', function () {
  35. Util.browserSupportsCursorURIs = function () { return true; };
  36. var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false });
  37. expect(display._cursor_uri).to.be.true;
  38. });
  39. it('respect the cursor_uri option if there is support', function () {
  40. Util.browserSupportsCursorURIs = function () { return false; };
  41. var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false, cursor_uri: false });
  42. expect(display._cursor_uri).to.be.false;
  43. });
  44. afterEach(function () {
  45. Util.browserSupportsCursorURIs = this._old_browser_supports_cursor_uris;
  46. });
  47. });
  48. describe('viewport handling', function () {
  49. var display;
  50. beforeEach(function () {
  51. display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
  52. display.resize(5, 5);
  53. display.viewportChangeSize(3, 3);
  54. display.viewportChangePos(1, 1);
  55. display.getCleanDirtyReset();
  56. });
  57. it('should take viewport location into consideration when drawing images', function () {
  58. display.set_width(4);
  59. display.set_height(4);
  60. display.viewportChangeSize(2, 2);
  61. display.drawImage(make_image_canvas(basic_data), 1, 1);
  62. var expected = new Uint8Array(16);
  63. var i;
  64. for (i = 0; i < 8; i++) { expected[i] = basic_data[i]; }
  65. for (i = 8; i < 16; i++) { expected[i] = 0; }
  66. expect(display).to.have.displayed(expected);
  67. });
  68. it('should redraw the left side when shifted left', function () {
  69. display.viewportChangePos(-1, 0);
  70. var cdr = display.getCleanDirtyReset();
  71. expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 1, w: 2, h: 3 });
  72. expect(cdr.dirtyBoxes).to.have.length(1);
  73. expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 0, y: 1, w: 2, h: 3 });
  74. });
  75. it('should redraw the right side when shifted right', function () {
  76. display.viewportChangePos(1, 0);
  77. var cdr = display.getCleanDirtyReset();
  78. expect(cdr.cleanBox).to.deep.equal({ x: 2, y: 1, w: 2, h: 3 });
  79. expect(cdr.dirtyBoxes).to.have.length(1);
  80. expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 4, y: 1, w: 1, h: 3 });
  81. });
  82. it('should redraw the top part when shifted up', function () {
  83. display.viewportChangePos(0, -1);
  84. var cdr = display.getCleanDirtyReset();
  85. expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 1, w: 3, h: 2 });
  86. expect(cdr.dirtyBoxes).to.have.length(1);
  87. expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 1, y: 0, w: 3, h: 1 });
  88. });
  89. it('should redraw the bottom part when shifted down', function () {
  90. display.viewportChangePos(0, 1);
  91. var cdr = display.getCleanDirtyReset();
  92. expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 2, w: 3, h: 2 });
  93. expect(cdr.dirtyBoxes).to.have.length(1);
  94. expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 1, y: 4, w: 3, h: 1 });
  95. });
  96. it('should reset the entire viewport to being clean after calculating the clean/dirty boxes', function () {
  97. display.viewportChangePos(0, 1);
  98. var cdr1 = display.getCleanDirtyReset();
  99. var cdr2 = display.getCleanDirtyReset();
  100. expect(cdr1).to.not.deep.equal(cdr2);
  101. expect(cdr2.cleanBox).to.deep.equal({ x: 1, y: 2, w: 3, h: 3 });
  102. expect(cdr2.dirtyBoxes).to.be.empty;
  103. });
  104. it('should simply mark the whole display area as dirty if not using viewports', function () {
  105. display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: false });
  106. display.resize(5, 5);
  107. var cdr = display.getCleanDirtyReset();
  108. expect(cdr.cleanBox).to.deep.equal({ x: 0, y: 0, w: 0, h: 0 });
  109. expect(cdr.dirtyBoxes).to.have.length(1);
  110. expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 0, y: 0, w: 5, h: 5 });
  111. });
  112. });
  113. describe('clipping', function () {
  114. var display;
  115. beforeEach(function () {
  116. display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
  117. display.resize(4, 3);
  118. });
  119. it('should report true when no max-size and framebuffer > viewport', function () {
  120. display.viewportChangeSize(2,2);
  121. var clipping = display.clippingDisplay();
  122. expect(clipping).to.be.true;
  123. });
  124. it('should report false when no max-size and framebuffer = viewport', function () {
  125. var clipping = display.clippingDisplay();
  126. expect(clipping).to.be.false;
  127. });
  128. it('should report true when viewport > max-size and framebuffer > viewport', function () {
  129. display.viewportChangeSize(2,2);
  130. display.set_maxWidth(1);
  131. display.set_maxHeight(2);
  132. var clipping = display.clippingDisplay();
  133. expect(clipping).to.be.true;
  134. });
  135. it('should report true when viewport > max-size and framebuffer = viewport', function () {
  136. display.set_maxWidth(1);
  137. display.set_maxHeight(2);
  138. var clipping = display.clippingDisplay();
  139. expect(clipping).to.be.true;
  140. });
  141. });
  142. describe('resizing', function () {
  143. var display;
  144. beforeEach(function () {
  145. display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
  146. display.resize(4, 3);
  147. });
  148. it('should change the size of the logical canvas', function () {
  149. display.resize(5, 7);
  150. expect(display._fb_width).to.equal(5);
  151. expect(display._fb_height).to.equal(7);
  152. });
  153. it('should update the viewport dimensions', function () {
  154. sinon.spy(display, 'viewportChangeSize');
  155. display.resize(2, 2);
  156. expect(display.viewportChangeSize).to.have.been.calledOnce;
  157. });
  158. });
  159. describe('rescaling', function () {
  160. var display;
  161. var canvas;
  162. beforeEach(function () {
  163. display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
  164. display.resize(4, 3);
  165. canvas = display.get_target();
  166. document.body.appendChild(canvas);
  167. });
  168. afterEach(function () {
  169. document.body.removeChild(canvas);
  170. });
  171. it('should not change the bitmap size of the canvas', function () {
  172. display.set_scale(0.5);
  173. expect(canvas.width).to.equal(4);
  174. expect(canvas.height).to.equal(3);
  175. });
  176. it('should change the effective rendered size of the canvas', function () {
  177. display.set_scale(0.5);
  178. expect(canvas.clientWidth).to.equal(2);
  179. expect(canvas.clientHeight).to.equal(2);
  180. });
  181. });
  182. describe('autoscaling', function () {
  183. var display;
  184. var canvas;
  185. beforeEach(function () {
  186. display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
  187. display.resize(4, 3);
  188. canvas = display.get_target();
  189. document.body.appendChild(canvas);
  190. });
  191. afterEach(function () {
  192. document.body.removeChild(canvas);
  193. });
  194. it('should preserve aspect ratio while autoscaling', function () {
  195. display.autoscale(16, 9);
  196. expect(canvas.clientWidth / canvas.clientHeight).to.equal(4 / 3);
  197. });
  198. it('should use width to determine scale when the current aspect ratio is wider than the target', function () {
  199. expect(display.autoscale(9, 16)).to.equal(9 / 4);
  200. expect(canvas.clientWidth).to.equal(9);
  201. expect(canvas.clientHeight).to.equal(7); // round 9 / (4 / 3)
  202. });
  203. it('should use height to determine scale when the current aspect ratio is taller than the target', function () {
  204. expect(display.autoscale(16, 9)).to.equal(3); // 9 / 3
  205. expect(canvas.clientWidth).to.equal(12); // 16 * (4 / 3)
  206. expect(canvas.clientHeight).to.equal(9);
  207. });
  208. it('should not change the bitmap size of the canvas', function () {
  209. display.autoscale(16, 9);
  210. expect(canvas.width).to.equal(4);
  211. expect(canvas.height).to.equal(3);
  212. });
  213. it('should not upscale when downscaleOnly is true', function () {
  214. expect(display.autoscale(2, 2, true)).to.equal(0.5);
  215. expect(canvas.clientWidth).to.equal(2);
  216. expect(canvas.clientHeight).to.equal(2);
  217. expect(display.autoscale(16, 9, true)).to.equal(1.0);
  218. expect(canvas.clientWidth).to.equal(4);
  219. expect(canvas.clientHeight).to.equal(3);
  220. });
  221. });
  222. describe('drawing', function () {
  223. // TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the
  224. // basic cases
  225. function drawing_tests (pref_js) {
  226. var display;
  227. beforeEach(function () {
  228. display = new Display({ target: document.createElement('canvas'), prefer_js: pref_js });
  229. display.resize(4, 4);
  230. });
  231. it('should clear the screen on #clear without a logo set', function () {
  232. display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]);
  233. display._logo = null;
  234. display.clear();
  235. display.resize(4, 4);
  236. var empty = [];
  237. for (var i = 0; i < 4 * display._fb_width * display._fb_height; i++) { empty[i] = 0; }
  238. expect(display).to.have.displayed(new Uint8Array(empty));
  239. });
  240. it('should draw the logo on #clear with a logo set', function (done) {
  241. display._logo = { width: 4, height: 4, data: make_image_canvas(checked_data).toDataURL() };
  242. display._drawCtx._act_drawImg = display._drawCtx.drawImage;
  243. display._drawCtx.drawImage = function (img, x, y) {
  244. this._act_drawImg(img, x, y);
  245. expect(display).to.have.displayed(checked_data);
  246. done();
  247. };
  248. display.clear();
  249. expect(display._fb_width).to.equal(4);
  250. expect(display._fb_height).to.equal(4);
  251. });
  252. it('should support filling a rectangle with particular color via #fillRect', function () {
  253. display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
  254. display.fillRect(0, 0, 2, 2, [0xff, 0, 0]);
  255. display.fillRect(2, 2, 2, 2, [0xff, 0, 0]);
  256. expect(display).to.have.displayed(checked_data);
  257. });
  258. it('should support copying an portion of the canvas via #copyImage', function () {
  259. display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
  260. display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]);
  261. display.copyImage(0, 0, 2, 2, 2, 2);
  262. expect(display).to.have.displayed(checked_data);
  263. });
  264. it('should support drawing tile data with a background color and sub tiles', function () {
  265. display.startTile(0, 0, 4, 4, [0, 0xff, 0]);
  266. display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
  267. display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
  268. display.finishTile();
  269. expect(display).to.have.displayed(checked_data);
  270. });
  271. it('should support drawing BGRX blit images with true color via #blitImage', function () {
  272. var data = [];
  273. for (var i = 0; i < 16; i++) {
  274. data[i * 4] = checked_data[i * 4 + 2];
  275. data[i * 4 + 1] = checked_data[i * 4 + 1];
  276. data[i * 4 + 2] = checked_data[i * 4];
  277. data[i * 4 + 3] = checked_data[i * 4 + 3];
  278. }
  279. display.blitImage(0, 0, 4, 4, data, 0);
  280. expect(display).to.have.displayed(checked_data);
  281. });
  282. it('should support drawing RGB blit images with true color via #blitRgbImage', function () {
  283. var data = [];
  284. for (var i = 0; i < 16; i++) {
  285. data[i * 3] = checked_data[i * 4];
  286. data[i * 3 + 1] = checked_data[i * 4 + 1];
  287. data[i * 3 + 2] = checked_data[i * 4 + 2];
  288. }
  289. display.blitRgbImage(0, 0, 4, 4, data, 0);
  290. expect(display).to.have.displayed(checked_data);
  291. });
  292. it('should support drawing blit images from a data URL via #blitStringImage', function (done) {
  293. var img_url = make_image_canvas(checked_data).toDataURL();
  294. display._drawCtx._act_drawImg = display._drawCtx.drawImage;
  295. display._drawCtx.drawImage = function (img, x, y) {
  296. this._act_drawImg(img, x, y);
  297. expect(display).to.have.displayed(checked_data);
  298. done();
  299. };
  300. display.blitStringImage(img_url, 0, 0);
  301. });
  302. it('should support drawing solid colors with color maps', function () {
  303. display._true_color = false;
  304. display.set_colourMap({ 0: [0xff, 0, 0], 1: [0, 0xff, 0] });
  305. display.fillRect(0, 0, 4, 4, [1]);
  306. display.fillRect(0, 0, 2, 2, [0]);
  307. display.fillRect(2, 2, 2, 2, [0]);
  308. expect(display).to.have.displayed(checked_data);
  309. });
  310. it('should support drawing blit images with color maps', function () {
  311. display._true_color = false;
  312. display.set_colourMap({ 1: [0xff, 0, 0], 0: [0, 0xff, 0] });
  313. var data = [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1].map(function (elem) { return [elem]; });
  314. display.blitImage(0, 0, 4, 4, data, 0);
  315. expect(display).to.have.displayed(checked_data);
  316. });
  317. it('should support drawing an image object via #drawImage', function () {
  318. var img = make_image_canvas(checked_data);
  319. display.drawImage(img, 0, 0);
  320. expect(display).to.have.displayed(checked_data);
  321. });
  322. }
  323. describe('(prefering native methods)', function () { drawing_tests.call(this, false); });
  324. describe('(prefering JavaScript)', function () { drawing_tests.call(this, true); });
  325. });
  326. describe('the render queue processor', function () {
  327. var display;
  328. beforeEach(function () {
  329. display = new Display({ target: document.createElement('canvas'), prefer_js: false });
  330. display.resize(4, 4);
  331. sinon.spy(display, '_scan_renderQ');
  332. this.old_requestAnimFrame = window.requestAnimFrame;
  333. window.requestAnimFrame = function (cb) {
  334. this.next_frame_cb = cb;
  335. }.bind(this);
  336. this.next_frame = function () { this.next_frame_cb(); };
  337. });
  338. afterEach(function () {
  339. window.requestAnimFrame = this.old_requestAnimFrame;
  340. });
  341. it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {
  342. display.renderQ_push({ type: 'noop' }); // does nothing
  343. expect(display._scan_renderQ).to.have.been.calledOnce;
  344. });
  345. it('should not try to process an item when it is pushed on if we are waiting for other items', function () {
  346. display._renderQ.length = 2;
  347. display.renderQ_push({ type: 'noop' });
  348. expect(display._scan_renderQ).to.not.have.been.called;
  349. });
  350. it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () {
  351. var img = { complete: false };
  352. display._renderQ = [{ type: 'img', x: 3, y: 4, img: img },
  353. { type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5 }];
  354. display.drawImage = sinon.spy();
  355. display.fillRect = sinon.spy();
  356. display._scan_renderQ();
  357. expect(display.drawImage).to.not.have.been.called;
  358. expect(display.fillRect).to.not.have.been.called;
  359. display._renderQ[0].img.complete = true;
  360. this.next_frame();
  361. expect(display.drawImage).to.have.been.calledOnce;
  362. expect(display.fillRect).to.have.been.calledOnce;
  363. });
  364. it('should draw a blit image on type "blit"', function () {
  365. display.blitImage = sinon.spy();
  366. display.renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
  367. expect(display.blitImage).to.have.been.calledOnce;
  368. expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
  369. });
  370. it('should draw a blit RGB image on type "blitRgb"', function () {
  371. display.blitRgbImage = sinon.spy();
  372. display.renderQ_push({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
  373. expect(display.blitRgbImage).to.have.been.calledOnce;
  374. expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
  375. });
  376. it('should copy a region on type "copy"', function () {
  377. display.copyImage = sinon.spy();
  378. display.renderQ_push({ type: 'copy', x: 3, y: 4, width: 5, height: 6, old_x: 7, old_y: 8 });
  379. expect(display.copyImage).to.have.been.calledOnce;
  380. expect(display.copyImage).to.have.been.calledWith(7, 8, 3, 4, 5, 6);
  381. });
  382. it('should fill a rect with a given color on type "fill"', function () {
  383. display.fillRect = sinon.spy();
  384. display.renderQ_push({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]});
  385. expect(display.fillRect).to.have.been.calledOnce;
  386. expect(display.fillRect).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9]);
  387. });
  388. it('should draw an image from an image object on type "img" (if complete)', function () {
  389. display.drawImage = sinon.spy();
  390. display.renderQ_push({ type: 'img', x: 3, y: 4, img: { complete: true } });
  391. expect(display.drawImage).to.have.been.calledOnce;
  392. expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4);
  393. });
  394. });
  395. });