test.display.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. const expect = chai.expect;
  2. import Base64 from '../core/base64.js';
  3. import Display from '../core/display.js';
  4. describe('Display/Canvas Helper', function () {
  5. const checkedData = new Uint8ClampedArray([
  6. 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
  7. 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
  8. 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
  9. 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
  10. ]);
  11. const basicData = new Uint8ClampedArray([0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255]);
  12. function makeImageCanvas(inputData, width, height) {
  13. const canvas = document.createElement('canvas');
  14. canvas.width = width;
  15. canvas.height = height;
  16. const ctx = canvas.getContext('2d');
  17. const data = new ImageData(inputData, width, height);
  18. ctx.putImageData(data, 0, 0);
  19. return canvas;
  20. }
  21. function makeImagePng(inputData, width, height) {
  22. const canvas = makeImageCanvas(inputData, width, height);
  23. const url = canvas.toDataURL();
  24. const data = url.split(",")[1];
  25. return Base64.decode(data);
  26. }
  27. describe('viewport handling', function () {
  28. let display;
  29. beforeEach(function () {
  30. display = new Display(document.createElement('canvas'));
  31. display.clipViewport = true;
  32. display.resize(5, 5);
  33. display.viewportChangeSize(3, 3);
  34. display.viewportChangePos(1, 1);
  35. });
  36. it('should take viewport location into consideration when drawing images', function () {
  37. display.resize(4, 4);
  38. display.viewportChangeSize(2, 2);
  39. display.drawImage(makeImageCanvas(basicData, 4, 1), 1, 1);
  40. display.flip();
  41. const expected = new Uint8Array(16);
  42. for (let i = 0; i < 8; i++) { expected[i] = basicData[i]; }
  43. for (let i = 8; i < 16; i++) { expected[i] = 0; }
  44. expect(display).to.have.displayed(expected);
  45. });
  46. it('should resize the target canvas when resizing the viewport', function () {
  47. display.viewportChangeSize(2, 2);
  48. expect(display._target.width).to.equal(2);
  49. expect(display._target.height).to.equal(2);
  50. });
  51. it('should move the viewport if necessary', function () {
  52. display.viewportChangeSize(5, 5);
  53. expect(display.absX(0)).to.equal(0);
  54. expect(display.absY(0)).to.equal(0);
  55. expect(display._target.width).to.equal(5);
  56. expect(display._target.height).to.equal(5);
  57. });
  58. it('should limit the viewport to the framebuffer size', function () {
  59. display.viewportChangeSize(6, 6);
  60. expect(display._target.width).to.equal(5);
  61. expect(display._target.height).to.equal(5);
  62. });
  63. it('should redraw when moving the viewport', function () {
  64. display.flip = sinon.spy();
  65. display.viewportChangePos(-1, 1);
  66. expect(display.flip).to.have.been.calledOnce;
  67. });
  68. it('should redraw when resizing the viewport', function () {
  69. display.flip = sinon.spy();
  70. display.viewportChangeSize(2, 2);
  71. expect(display.flip).to.have.been.calledOnce;
  72. });
  73. it('should show the entire framebuffer when disabling the viewport', function () {
  74. display.clipViewport = false;
  75. expect(display.absX(0)).to.equal(0);
  76. expect(display.absY(0)).to.equal(0);
  77. expect(display._target.width).to.equal(5);
  78. expect(display._target.height).to.equal(5);
  79. });
  80. it('should ignore viewport changes when the viewport is disabled', function () {
  81. display.clipViewport = false;
  82. display.viewportChangeSize(2, 2);
  83. display.viewportChangePos(1, 1);
  84. expect(display.absX(0)).to.equal(0);
  85. expect(display.absY(0)).to.equal(0);
  86. expect(display._target.width).to.equal(5);
  87. expect(display._target.height).to.equal(5);
  88. });
  89. it('should show the entire framebuffer just after enabling the viewport', function () {
  90. display.clipViewport = false;
  91. display.clipViewport = true;
  92. expect(display.absX(0)).to.equal(0);
  93. expect(display.absY(0)).to.equal(0);
  94. expect(display._target.width).to.equal(5);
  95. expect(display._target.height).to.equal(5);
  96. });
  97. });
  98. describe('resizing', function () {
  99. let display;
  100. beforeEach(function () {
  101. display = new Display(document.createElement('canvas'));
  102. display.clipViewport = false;
  103. display.resize(4, 4);
  104. });
  105. it('should change the size of the logical canvas', function () {
  106. display.resize(5, 7);
  107. expect(display._fbWidth).to.equal(5);
  108. expect(display._fbHeight).to.equal(7);
  109. });
  110. it('should keep the framebuffer data', function () {
  111. display.fillRect(0, 0, 4, 4, [0xff, 0, 0]);
  112. display.resize(2, 2);
  113. display.flip();
  114. const expected = [];
  115. for (let i = 0; i < 4 * 2*2; i += 4) {
  116. expected[i] = 0xff;
  117. expected[i+1] = expected[i+2] = 0;
  118. expected[i+3] = 0xff;
  119. }
  120. expect(display).to.have.displayed(new Uint8Array(expected));
  121. });
  122. describe('viewport', function () {
  123. beforeEach(function () {
  124. display.clipViewport = true;
  125. display.viewportChangeSize(3, 3);
  126. display.viewportChangePos(1, 1);
  127. });
  128. it('should keep the viewport position and size if possible', function () {
  129. display.resize(6, 6);
  130. expect(display.absX(0)).to.equal(1);
  131. expect(display.absY(0)).to.equal(1);
  132. expect(display._target.width).to.equal(3);
  133. expect(display._target.height).to.equal(3);
  134. });
  135. it('should move the viewport if necessary', function () {
  136. display.resize(3, 3);
  137. expect(display.absX(0)).to.equal(0);
  138. expect(display.absY(0)).to.equal(0);
  139. expect(display._target.width).to.equal(3);
  140. expect(display._target.height).to.equal(3);
  141. });
  142. it('should shrink the viewport if necessary', function () {
  143. display.resize(2, 2);
  144. expect(display.absX(0)).to.equal(0);
  145. expect(display.absY(0)).to.equal(0);
  146. expect(display._target.width).to.equal(2);
  147. expect(display._target.height).to.equal(2);
  148. });
  149. });
  150. });
  151. describe('rescaling', function () {
  152. let display;
  153. let canvas;
  154. beforeEach(function () {
  155. canvas = document.createElement('canvas');
  156. display = new Display(canvas);
  157. display.clipViewport = true;
  158. display.resize(4, 4);
  159. display.viewportChangeSize(3, 3);
  160. display.viewportChangePos(1, 1);
  161. document.body.appendChild(canvas);
  162. });
  163. afterEach(function () {
  164. document.body.removeChild(canvas);
  165. });
  166. it('should not change the bitmap size of the canvas', function () {
  167. display.scale = 2.0;
  168. expect(canvas.width).to.equal(3);
  169. expect(canvas.height).to.equal(3);
  170. });
  171. it('should change the effective rendered size of the canvas', function () {
  172. display.scale = 2.0;
  173. expect(canvas.clientWidth).to.equal(6);
  174. expect(canvas.clientHeight).to.equal(6);
  175. });
  176. it('should not change when resizing', function () {
  177. display.scale = 2.0;
  178. display.resize(5, 5);
  179. expect(display.scale).to.equal(2.0);
  180. expect(canvas.width).to.equal(3);
  181. expect(canvas.height).to.equal(3);
  182. expect(canvas.clientWidth).to.equal(6);
  183. expect(canvas.clientHeight).to.equal(6);
  184. });
  185. });
  186. describe('autoscaling', function () {
  187. let display;
  188. let canvas;
  189. beforeEach(function () {
  190. canvas = document.createElement('canvas');
  191. display = new Display(canvas);
  192. display.clipViewport = true;
  193. display.resize(4, 3);
  194. display.viewportChangeSize(4, 3);
  195. document.body.appendChild(canvas);
  196. });
  197. afterEach(function () {
  198. document.body.removeChild(canvas);
  199. });
  200. it('should preserve aspect ratio while autoscaling', function () {
  201. display.autoscale(16, 9);
  202. expect(canvas.clientWidth / canvas.clientHeight).to.equal(4 / 3);
  203. });
  204. it('should use width to determine scale when the current aspect ratio is wider than the target', function () {
  205. display.autoscale(9, 16);
  206. expect(display.absX(9)).to.equal(4);
  207. expect(display.absY(18)).to.equal(8);
  208. expect(canvas.clientWidth).to.equal(9);
  209. expect(canvas.clientHeight).to.equal(7); // round 9 / (4 / 3)
  210. });
  211. it('should use height to determine scale when the current aspect ratio is taller than the target', function () {
  212. display.autoscale(16, 9);
  213. expect(display.absX(9)).to.equal(3);
  214. expect(display.absY(18)).to.equal(6);
  215. expect(canvas.clientWidth).to.equal(12); // 16 * (4 / 3)
  216. expect(canvas.clientHeight).to.equal(9);
  217. });
  218. it('should not change the bitmap size of the canvas', function () {
  219. display.autoscale(16, 9);
  220. expect(canvas.width).to.equal(4);
  221. expect(canvas.height).to.equal(3);
  222. });
  223. });
  224. describe('drawing', function () {
  225. // TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the
  226. // basic cases
  227. let display;
  228. beforeEach(function () {
  229. display = new Display(document.createElement('canvas'));
  230. display.resize(4, 4);
  231. });
  232. it('should not draw directly on the target canvas', function () {
  233. display.fillRect(0, 0, 4, 4, [0xff, 0, 0]);
  234. display.flip();
  235. display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
  236. const expected = [];
  237. for (let i = 0; i < 4 * display._fbWidth * display._fbHeight; i += 4) {
  238. expected[i] = 0xff;
  239. expected[i+1] = expected[i+2] = 0;
  240. expected[i+3] = 0xff;
  241. }
  242. expect(display).to.have.displayed(new Uint8Array(expected));
  243. });
  244. it('should support filling a rectangle with particular color via #fillRect', function () {
  245. display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
  246. display.fillRect(0, 0, 2, 2, [0, 0, 0xff]);
  247. display.fillRect(2, 2, 2, 2, [0, 0, 0xff]);
  248. display.flip();
  249. expect(display).to.have.displayed(checkedData);
  250. });
  251. it('should support copying an portion of the canvas via #copyImage', function () {
  252. display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
  253. display.fillRect(0, 0, 2, 2, [0, 0, 0xff]);
  254. display.copyImage(0, 0, 2, 2, 2, 2);
  255. display.flip();
  256. expect(display).to.have.displayed(checkedData);
  257. });
  258. it('should support drawing images via #imageRect', function (done) {
  259. display.imageRect(0, 0, 4, 4, "image/png", makeImagePng(checkedData, 4, 4));
  260. display.flip();
  261. display.onflush = () => {
  262. expect(display).to.have.displayed(checkedData);
  263. done();
  264. };
  265. display.flush();
  266. });
  267. it('should support blit images with true color via #blitImage', function () {
  268. display.blitImage(0, 0, 4, 4, checkedData, 0);
  269. display.flip();
  270. expect(display).to.have.displayed(checkedData);
  271. });
  272. it('should support drawing an image object via #drawImage', function () {
  273. const img = makeImageCanvas(checkedData, 4, 4);
  274. display.drawImage(img, 0, 0);
  275. display.flip();
  276. expect(display).to.have.displayed(checkedData);
  277. });
  278. });
  279. describe('the render queue processor', function () {
  280. let display;
  281. beforeEach(function () {
  282. display = new Display(document.createElement('canvas'));
  283. display.resize(4, 4);
  284. sinon.spy(display, '_scanRenderQ');
  285. });
  286. it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {
  287. display._renderQPush({ type: 'noop' }); // does nothing
  288. expect(display._scanRenderQ).to.have.been.calledOnce;
  289. });
  290. it('should not try to process an item when it is pushed on if we are waiting for other items', function () {
  291. display._renderQ.length = 2;
  292. display._renderQPush({ type: 'noop' });
  293. expect(display._scanRenderQ).to.not.have.been.called;
  294. });
  295. it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () {
  296. const img = { complete: false, width: 4, height: 4, addEventListener: sinon.spy() };
  297. display._renderQ = [{ type: 'img', x: 3, y: 4, width: 4, height: 4, img: img },
  298. { type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5 }];
  299. display.drawImage = sinon.spy();
  300. display.fillRect = sinon.spy();
  301. display._scanRenderQ();
  302. expect(display.drawImage).to.not.have.been.called;
  303. expect(display.fillRect).to.not.have.been.called;
  304. expect(img.addEventListener).to.have.been.calledOnce;
  305. display._renderQ[0].img.complete = true;
  306. display._scanRenderQ();
  307. expect(display.drawImage).to.have.been.calledOnce;
  308. expect(display.fillRect).to.have.been.calledOnce;
  309. expect(img.addEventListener).to.have.been.calledOnce;
  310. });
  311. it('should call callback when queue is flushed', function () {
  312. display.onflush = sinon.spy();
  313. display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
  314. expect(display.onflush).to.not.have.been.called;
  315. display.flush();
  316. expect(display.onflush).to.have.been.calledOnce;
  317. });
  318. it('should draw a blit image on type "blit"', function () {
  319. display.blitImage = sinon.spy();
  320. display._renderQPush({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
  321. expect(display.blitImage).to.have.been.calledOnce;
  322. expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
  323. });
  324. it('should copy a region on type "copy"', function () {
  325. display.copyImage = sinon.spy();
  326. display._renderQPush({ type: 'copy', x: 3, y: 4, width: 5, height: 6, oldX: 7, oldY: 8 });
  327. expect(display.copyImage).to.have.been.calledOnce;
  328. expect(display.copyImage).to.have.been.calledWith(7, 8, 3, 4, 5, 6);
  329. });
  330. it('should fill a rect with a given color on type "fill"', function () {
  331. display.fillRect = sinon.spy();
  332. display._renderQPush({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]});
  333. expect(display.fillRect).to.have.been.calledOnce;
  334. expect(display.fillRect).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9]);
  335. });
  336. it('should draw an image from an image object on type "img" (if complete)', function () {
  337. display.drawImage = sinon.spy();
  338. display._renderQPush({ type: 'img', x: 3, y: 4, img: { complete: true } });
  339. expect(display.drawImage).to.have.been.calledOnce;
  340. expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4);
  341. });
  342. });
  343. });