1 const expect
= chai
.expect
;
3 import Base64
from '../core/base64.js';
4 import Display
from '../core/display.js';
6 import sinon
from '../vendor/sinon.js';
8 describe('Display/Canvas Helper', function () {
9 const checked_data
= new Uint8Array([
10 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
11 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
12 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
13 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
16 const basic_data
= new Uint8Array([0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255]);
18 function make_image_canvas (input_data
) {
19 const canvas
= document
.createElement('canvas');
22 const ctx
= canvas
.getContext('2d');
23 const data
= ctx
.createImageData(4, 4);
24 for (let i
= 0; i
< checked_data
.length
; i
++) { data
.data
[i
] = input_data
[i
]; }
25 ctx
.putImageData(data
, 0, 0);
29 function make_image_png (input_data
) {
30 const canvas
= make_image_canvas(input_data
);
31 const url
= canvas
.toDataURL();
32 const data
= url
.split(",")[1];
33 return Base64
.decode(data
);
36 describe('viewport handling', function () {
38 beforeEach(function () {
39 display
= new Display(document
.createElement('canvas'));
40 display
.clipViewport
= true;
42 display
.viewportChangeSize(3, 3);
43 display
.viewportChangePos(1, 1);
46 it('should take viewport location into consideration when drawing images', function () {
48 display
.viewportChangeSize(2, 2);
49 display
.drawImage(make_image_canvas(basic_data
), 1, 1);
52 const expected
= new Uint8Array(16);
53 for (let i
= 0; i
< 8; i
++) { expected
[i
] = basic_data
[i
]; }
54 for (let i
= 8; i
< 16; i
++) { expected
[i
] = 0; }
55 expect(display
).to
.have
.displayed(expected
);
58 it('should resize the target canvas when resizing the viewport', function() {
59 display
.viewportChangeSize(2, 2);
60 expect(display
._target
.width
).to
.equal(2);
61 expect(display
._target
.height
).to
.equal(2);
64 it('should move the viewport if necessary', function() {
65 display
.viewportChangeSize(5, 5);
66 expect(display
.absX(0)).to
.equal(0);
67 expect(display
.absY(0)).to
.equal(0);
68 expect(display
._target
.width
).to
.equal(5);
69 expect(display
._target
.height
).to
.equal(5);
72 it('should limit the viewport to the framebuffer size', function() {
73 display
.viewportChangeSize(6, 6);
74 expect(display
._target
.width
).to
.equal(5);
75 expect(display
._target
.height
).to
.equal(5);
78 it('should redraw when moving the viewport', function () {
79 display
.flip
= sinon
.spy();
80 display
.viewportChangePos(-1, 1);
81 expect(display
.flip
).to
.have
.been
.calledOnce
;
84 it('should redraw when resizing the viewport', function () {
85 display
.flip
= sinon
.spy();
86 display
.viewportChangeSize(2, 2);
87 expect(display
.flip
).to
.have
.been
.calledOnce
;
90 it('should show the entire framebuffer when disabling the viewport', function() {
91 display
.clipViewport
= false;
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);
98 it('should ignore viewport changes when the viewport is disabled', function() {
99 display
.clipViewport
= false;
100 display
.viewportChangeSize(2, 2);
101 display
.viewportChangePos(1, 1);
102 expect(display
.absX(0)).to
.equal(0);
103 expect(display
.absY(0)).to
.equal(0);
104 expect(display
._target
.width
).to
.equal(5);
105 expect(display
._target
.height
).to
.equal(5);
108 it('should show the entire framebuffer just after enabling the viewport', function() {
109 display
.clipViewport
= false;
110 display
.clipViewport
= true;
111 expect(display
.absX(0)).to
.equal(0);
112 expect(display
.absY(0)).to
.equal(0);
113 expect(display
._target
.width
).to
.equal(5);
114 expect(display
._target
.height
).to
.equal(5);
118 describe('resizing', function () {
120 beforeEach(function () {
121 display
= new Display(document
.createElement('canvas'));
122 display
.clipViewport
= false;
123 display
.resize(4, 4);
126 it('should change the size of the logical canvas', function () {
127 display
.resize(5, 7);
128 expect(display
._fb_width
).to
.equal(5);
129 expect(display
._fb_height
).to
.equal(7);
132 it('should keep the framebuffer data', function () {
133 display
.fillRect(0, 0, 4, 4, [0, 0, 0xff]);
134 display
.resize(2, 2);
137 for (let i
= 0; i
< 4 * 2*2; i
+= 4) {
139 expected
[i
+1] = expected
[i
+2] = 0;
140 expected
[i
+3] = 0xff;
142 expect(display
).to
.have
.displayed(new Uint8Array(expected
));
145 describe('viewport', function () {
146 beforeEach(function () {
147 display
.clipViewport
= true;
148 display
.viewportChangeSize(3, 3);
149 display
.viewportChangePos(1, 1);
152 it('should keep the viewport position and size if possible', function () {
153 display
.resize(6, 6);
154 expect(display
.absX(0)).to
.equal(1);
155 expect(display
.absY(0)).to
.equal(1);
156 expect(display
._target
.width
).to
.equal(3);
157 expect(display
._target
.height
).to
.equal(3);
160 it('should move the viewport if necessary', function () {
161 display
.resize(3, 3);
162 expect(display
.absX(0)).to
.equal(0);
163 expect(display
.absY(0)).to
.equal(0);
164 expect(display
._target
.width
).to
.equal(3);
165 expect(display
._target
.height
).to
.equal(3);
168 it('should shrink the viewport if necessary', function () {
169 display
.resize(2, 2);
170 expect(display
.absX(0)).to
.equal(0);
171 expect(display
.absY(0)).to
.equal(0);
172 expect(display
._target
.width
).to
.equal(2);
173 expect(display
._target
.height
).to
.equal(2);
178 describe('rescaling', function () {
182 beforeEach(function () {
183 canvas
= document
.createElement('canvas');
184 display
= new Display(canvas
);
185 display
.clipViewport
= true;
186 display
.resize(4, 4);
187 display
.viewportChangeSize(3, 3);
188 display
.viewportChangePos(1, 1);
189 document
.body
.appendChild(canvas
);
192 afterEach(function () {
193 document
.body
.removeChild(canvas
);
196 it('should not change the bitmap size of the canvas', function () {
198 expect(canvas
.width
).to
.equal(3);
199 expect(canvas
.height
).to
.equal(3);
202 it('should change the effective rendered size of the canvas', function () {
204 expect(canvas
.clientWidth
).to
.equal(6);
205 expect(canvas
.clientHeight
).to
.equal(6);
208 it('should not change when resizing', function () {
210 display
.resize(5, 5);
211 expect(display
.scale
).to
.equal(2.0);
212 expect(canvas
.width
).to
.equal(3);
213 expect(canvas
.height
).to
.equal(3);
214 expect(canvas
.clientWidth
).to
.equal(6);
215 expect(canvas
.clientHeight
).to
.equal(6);
219 describe('autoscaling', function () {
223 beforeEach(function () {
224 canvas
= document
.createElement('canvas');
225 display
= new Display(canvas
);
226 display
.clipViewport
= true;
227 display
.resize(4, 3);
228 document
.body
.appendChild(canvas
);
231 afterEach(function () {
232 document
.body
.removeChild(canvas
);
235 it('should preserve aspect ratio while autoscaling', function () {
236 display
.autoscale(16, 9);
237 expect(canvas
.clientWidth
/ canvas
.clientHeight
).to
.equal(4 / 3);
240 it('should use width to determine scale when the current aspect ratio is wider than the target', function () {
241 display
.autoscale(9, 16);
242 expect(display
.absX(9)).to
.equal(4);
243 expect(display
.absY(18)).to
.equal(8);
244 expect(canvas
.clientWidth
).to
.equal(9);
245 expect(canvas
.clientHeight
).to
.equal(7); // round 9 / (4 / 3)
248 it('should use height to determine scale when the current aspect ratio is taller than the target', function () {
249 display
.autoscale(16, 9);
250 expect(display
.absX(9)).to
.equal(3);
251 expect(display
.absY(18)).to
.equal(6);
252 expect(canvas
.clientWidth
).to
.equal(12); // 16 * (4 / 3)
253 expect(canvas
.clientHeight
).to
.equal(9);
257 it('should not change the bitmap size of the canvas', function () {
258 display
.autoscale(16, 9);
259 expect(canvas
.width
).to
.equal(4);
260 expect(canvas
.height
).to
.equal(3);
264 describe('drawing', function () {
266 // TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the
269 beforeEach(function () {
270 display
= new Display(document
.createElement('canvas'));
271 display
.resize(4, 4);
274 it('should clear the screen on #clear without a logo set', function () {
275 display
.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]);
276 display
._logo
= null;
278 display
.resize(4, 4);
280 for (let i
= 0; i
< 4 * display
._fb_width
* display
._fb_height
; i
++) { empty
[i
] = 0; }
281 expect(display
).to
.have
.displayed(new Uint8Array(empty
));
284 it('should draw the logo on #clear with a logo set', function (done
) {
285 display
._logo
= { width
: 4, height
: 4, type
: "image/png", data
: make_image_png(checked_data
) };
287 display
.onflush
= () => {
288 expect(display
).to
.have
.displayed(checked_data
);
289 expect(display
._fb_width
).to
.equal(4);
290 expect(display
._fb_height
).to
.equal(4);
296 it('should not draw directly on the target canvas', function () {
297 display
.fillRect(0, 0, 4, 4, [0, 0, 0xff]);
299 display
.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
301 for (let i
= 0; i
< 4 * display
._fb_width
* display
._fb_height
; i
+= 4) {
303 expected
[i
+1] = expected
[i
+2] = 0;
304 expected
[i
+3] = 0xff;
306 expect(display
).to
.have
.displayed(new Uint8Array(expected
));
309 it('should support filling a rectangle with particular color via #fillRect', function () {
310 display
.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
311 display
.fillRect(0, 0, 2, 2, [0xff, 0, 0]);
312 display
.fillRect(2, 2, 2, 2, [0xff, 0, 0]);
314 expect(display
).to
.have
.displayed(checked_data
);
317 it('should support copying an portion of the canvas via #copyImage', function () {
318 display
.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
319 display
.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]);
320 display
.copyImage(0, 0, 2, 2, 2, 2);
322 expect(display
).to
.have
.displayed(checked_data
);
325 it('should support drawing images via #imageRect', function (done
) {
326 display
.imageRect(0, 0, "image/png", make_image_png(checked_data
));
328 display
.onflush
= () => {
329 expect(display
).to
.have
.displayed(checked_data
);
335 it('should support drawing tile data with a background color and sub tiles', function () {
336 display
.startTile(0, 0, 4, 4, [0, 0xff, 0]);
337 display
.subTile(0, 0, 2, 2, [0xff, 0, 0]);
338 display
.subTile(2, 2, 2, 2, [0xff, 0, 0]);
339 display
.finishTile();
341 expect(display
).to
.have
.displayed(checked_data
);
344 // We have a special cache for 16x16 tiles that we need to test
345 it('should support drawing a 16x16 tile', function () {
346 const large_checked_data
= new Uint8Array(16*16*4);
347 display
.resize(16, 16);
349 for (let y
= 0;y
< 16;y
++) {
350 for (let x
= 0;x
< 16;x
++) {
352 if ((x
< 4) && (y
< 4)) {
353 // NB: of course IE11 doesn't support #slice on ArrayBufferViews...
354 pixel
= Array
.prototype.slice
.call(checked_data
, (y
*4+x
)*4, (y
*4+x
+1)*4);
356 pixel
= [0, 0xff, 0, 255];
358 large_checked_data
.set(pixel
, (y
*16+x
)*4);
362 display
.startTile(0, 0, 16, 16, [0, 0xff, 0]);
363 display
.subTile(0, 0, 2, 2, [0xff, 0, 0]);
364 display
.subTile(2, 2, 2, 2, [0xff, 0, 0]);
365 display
.finishTile();
367 expect(display
).to
.have
.displayed(large_checked_data
);
370 it('should support drawing BGRX blit images with true color via #blitImage', function () {
372 for (let i
= 0; i
< 16; i
++) {
373 data
[i
* 4] = checked_data
[i
* 4 + 2];
374 data
[i
* 4 + 1] = checked_data
[i
* 4 + 1];
375 data
[i
* 4 + 2] = checked_data
[i
* 4];
376 data
[i
* 4 + 3] = checked_data
[i
* 4 + 3];
378 display
.blitImage(0, 0, 4, 4, data
, 0);
380 expect(display
).to
.have
.displayed(checked_data
);
383 it('should support drawing RGB blit images with true color via #blitRgbImage', function () {
385 for (let i
= 0; i
< 16; i
++) {
386 data
[i
* 3] = checked_data
[i
* 4];
387 data
[i
* 3 + 1] = checked_data
[i
* 4 + 1];
388 data
[i
* 3 + 2] = checked_data
[i
* 4 + 2];
390 display
.blitRgbImage(0, 0, 4, 4, data
, 0);
392 expect(display
).to
.have
.displayed(checked_data
);
395 it('should support drawing an image object via #drawImage', function () {
396 const img
= make_image_canvas(checked_data
);
397 display
.drawImage(img
, 0, 0);
399 expect(display
).to
.have
.displayed(checked_data
);
403 describe('the render queue processor', function () {
405 beforeEach(function () {
406 display
= new Display(document
.createElement('canvas'));
407 display
.resize(4, 4);
408 sinon
.spy(display
, '_scan_renderQ');
411 afterEach(function () {
412 window
.requestAnimationFrame
= this.old_requestAnimationFrame
;
415 it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {
416 display
._renderQ_push({ type
: 'noop' }); // does nothing
417 expect(display
._scan_renderQ
).to
.have
.been
.calledOnce
;
420 it('should not try to process an item when it is pushed on if we are waiting for other items', function () {
421 display
._renderQ
.length
= 2;
422 display
._renderQ_push({ type
: 'noop' });
423 expect(display
._scan_renderQ
).to
.not
.have
.been
.called
;
426 it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () {
427 const img
= { complete
: false, addEventListener
: sinon
.spy() }
428 display
._renderQ
= [{ type
: 'img', x
: 3, y
: 4, img
: img
},
429 { type
: 'fill', x
: 1, y
: 2, width
: 3, height
: 4, color
: 5 }];
430 display
.drawImage
= sinon
.spy();
431 display
.fillRect
= sinon
.spy();
433 display
._scan_renderQ();
434 expect(display
.drawImage
).to
.not
.have
.been
.called
;
435 expect(display
.fillRect
).to
.not
.have
.been
.called
;
436 expect(img
.addEventListener
).to
.have
.been
.calledOnce
;
438 display
._renderQ
[0].img
.complete
= true;
439 display
._scan_renderQ();
440 expect(display
.drawImage
).to
.have
.been
.calledOnce
;
441 expect(display
.fillRect
).to
.have
.been
.calledOnce
;
442 expect(img
.addEventListener
).to
.have
.been
.calledOnce
;
445 it('should call callback when queue is flushed', function () {
446 display
.onflush
= sinon
.spy();
447 display
.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
448 expect(display
.onflush
).to
.not
.have
.been
.called
;
450 expect(display
.onflush
).to
.have
.been
.calledOnce
;
453 it('should draw a blit image on type "blit"', function () {
454 display
.blitImage
= sinon
.spy();
455 display
._renderQ_push({ type
: 'blit', x
: 3, y
: 4, width
: 5, height
: 6, data
: [7, 8, 9] });
456 expect(display
.blitImage
).to
.have
.been
.calledOnce
;
457 expect(display
.blitImage
).to
.have
.been
.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
460 it('should draw a blit RGB image on type "blitRgb"', function () {
461 display
.blitRgbImage
= sinon
.spy();
462 display
._renderQ_push({ type
: 'blitRgb', x
: 3, y
: 4, width
: 5, height
: 6, data
: [7, 8, 9] });
463 expect(display
.blitRgbImage
).to
.have
.been
.calledOnce
;
464 expect(display
.blitRgbImage
).to
.have
.been
.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
467 it('should copy a region on type "copy"', function () {
468 display
.copyImage
= sinon
.spy();
469 display
._renderQ_push({ type
: 'copy', x
: 3, y
: 4, width
: 5, height
: 6, old_x
: 7, old_y
: 8 });
470 expect(display
.copyImage
).to
.have
.been
.calledOnce
;
471 expect(display
.copyImage
).to
.have
.been
.calledWith(7, 8, 3, 4, 5, 6);
474 it('should fill a rect with a given color on type "fill"', function () {
475 display
.fillRect
= sinon
.spy();
476 display
._renderQ_push({ type
: 'fill', x
: 3, y
: 4, width
: 5, height
: 6, color
: [7, 8, 9]});
477 expect(display
.fillRect
).to
.have
.been
.calledOnce
;
478 expect(display
.fillRect
).to
.have
.been
.calledWith(3, 4, 5, 6, [7, 8, 9]);
481 it('should draw an image from an image object on type "img" (if complete)', function () {
482 display
.drawImage
= sinon
.spy();
483 display
._renderQ_push({ type
: 'img', x
: 3, y
: 4, img
: { complete
: true } });
484 expect(display
.drawImage
).to
.have
.been
.calledOnce
;
485 expect(display
.drawImage
).to
.have
.been
.calledWith({ complete
: true }, 3, 4);