1 var 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 () {
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
15 checked_data
= new Uint8Array(checked_data
);
17 var basic_data
= [0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255];
18 basic_data
= new Uint8Array(basic_data
);
20 function make_image_canvas (input_data
) {
21 var canvas
= document
.createElement('canvas');
24 var ctx
= canvas
.getContext('2d');
25 var data
= ctx
.createImageData(4, 4);
26 for (var i
= 0; i
< checked_data
.length
; i
++) { data
.data
[i
] = input_data
[i
]; }
27 ctx
.putImageData(data
, 0, 0);
31 function make_image_png (input_data
) {
32 var canvas
= make_image_canvas(input_data
);
33 var url
= canvas
.toDataURL();
34 var data
= url
.split(",")[1];
35 return Base64
.decode(data
);
38 describe('viewport handling', function () {
40 beforeEach(function () {
41 display
= new Display(document
.createElement('canvas'));
42 display
.clipViewport
= true;
44 display
.viewportChangeSize(3, 3);
45 display
.viewportChangePos(1, 1);
48 it('should take viewport location into consideration when drawing images', function () {
50 display
.viewportChangeSize(2, 2);
51 display
.drawImage(make_image_canvas(basic_data
), 1, 1);
54 var expected
= new Uint8Array(16);
56 for (i
= 0; i
< 8; i
++) { expected
[i
] = basic_data
[i
]; }
57 for (i
= 8; i
< 16; i
++) { expected
[i
] = 0; }
58 expect(display
).to
.have
.displayed(expected
);
61 it('should resize the target canvas when resizing the viewport', function() {
62 display
.viewportChangeSize(2, 2);
63 expect(display
._target
.width
).to
.equal(2);
64 expect(display
._target
.height
).to
.equal(2);
67 it('should move the viewport if necessary', function() {
68 display
.viewportChangeSize(5, 5);
69 expect(display
.absX(0)).to
.equal(0);
70 expect(display
.absY(0)).to
.equal(0);
71 expect(display
._target
.width
).to
.equal(5);
72 expect(display
._target
.height
).to
.equal(5);
75 it('should limit the viewport to the framebuffer size', function() {
76 display
.viewportChangeSize(6, 6);
77 expect(display
._target
.width
).to
.equal(5);
78 expect(display
._target
.height
).to
.equal(5);
81 it('should redraw when moving the viewport', function () {
82 display
.flip
= sinon
.spy();
83 display
.viewportChangePos(-1, 1);
84 expect(display
.flip
).to
.have
.been
.calledOnce
;
87 it('should redraw when resizing the viewport', function () {
88 display
.flip
= sinon
.spy();
89 display
.viewportChangeSize(2, 2);
90 expect(display
.flip
).to
.have
.been
.calledOnce
;
93 it('should show the entire framebuffer when disabling the viewport', function() {
94 display
.clipViewport
= false;
95 expect(display
.absX(0)).to
.equal(0);
96 expect(display
.absY(0)).to
.equal(0);
97 expect(display
._target
.width
).to
.equal(5);
98 expect(display
._target
.height
).to
.equal(5);
101 it('should ignore viewport changes when the viewport is disabled', function() {
102 display
.clipViewport
= false;
103 display
.viewportChangeSize(2, 2);
104 display
.viewportChangePos(1, 1);
105 expect(display
.absX(0)).to
.equal(0);
106 expect(display
.absY(0)).to
.equal(0);
107 expect(display
._target
.width
).to
.equal(5);
108 expect(display
._target
.height
).to
.equal(5);
111 it('should show the entire framebuffer just after enabling the viewport', function() {
112 display
.clipViewport
= false;
113 display
.clipViewport
= true;
114 expect(display
.absX(0)).to
.equal(0);
115 expect(display
.absY(0)).to
.equal(0);
116 expect(display
._target
.width
).to
.equal(5);
117 expect(display
._target
.height
).to
.equal(5);
121 describe('resizing', function () {
123 beforeEach(function () {
124 display
= new Display(document
.createElement('canvas'));
125 display
.clipViewport
= false;
126 display
.resize(4, 4);
129 it('should change the size of the logical canvas', function () {
130 display
.resize(5, 7);
131 expect(display
._fb_width
).to
.equal(5);
132 expect(display
._fb_height
).to
.equal(7);
135 it('should keep the framebuffer data', function () {
136 display
.fillRect(0, 0, 4, 4, [0, 0, 0xff]);
137 display
.resize(2, 2);
140 for (var i
= 0; i
< 4 * 2*2; i
+= 4) {
142 expected
[i
+1] = expected
[i
+2] = 0;
143 expected
[i
+3] = 0xff;
145 expect(display
).to
.have
.displayed(new Uint8Array(expected
));
148 describe('viewport', function () {
149 beforeEach(function () {
150 display
.clipViewport
= true;
151 display
.viewportChangeSize(3, 3);
152 display
.viewportChangePos(1, 1);
155 it('should keep the viewport position and size if possible', function () {
156 display
.resize(6, 6);
157 expect(display
.absX(0)).to
.equal(1);
158 expect(display
.absY(0)).to
.equal(1);
159 expect(display
._target
.width
).to
.equal(3);
160 expect(display
._target
.height
).to
.equal(3);
163 it('should move the viewport if necessary', function () {
164 display
.resize(3, 3);
165 expect(display
.absX(0)).to
.equal(0);
166 expect(display
.absY(0)).to
.equal(0);
167 expect(display
._target
.width
).to
.equal(3);
168 expect(display
._target
.height
).to
.equal(3);
171 it('should shrink the viewport if necessary', function () {
172 display
.resize(2, 2);
173 expect(display
.absX(0)).to
.equal(0);
174 expect(display
.absY(0)).to
.equal(0);
175 expect(display
._target
.width
).to
.equal(2);
176 expect(display
._target
.height
).to
.equal(2);
181 describe('rescaling', function () {
185 beforeEach(function () {
186 canvas
= document
.createElement('canvas');
187 display
= new Display(canvas
);
188 display
.clipViewport
= true;
189 display
.resize(4, 4);
190 display
.viewportChangeSize(3, 3);
191 display
.viewportChangePos(1, 1);
192 document
.body
.appendChild(canvas
);
195 afterEach(function () {
196 document
.body
.removeChild(canvas
);
199 it('should not change the bitmap size of the canvas', function () {
201 expect(canvas
.width
).to
.equal(3);
202 expect(canvas
.height
).to
.equal(3);
205 it('should change the effective rendered size of the canvas', function () {
207 expect(canvas
.clientWidth
).to
.equal(6);
208 expect(canvas
.clientHeight
).to
.equal(6);
211 it('should not change when resizing', function () {
213 display
.resize(5, 5);
214 expect(display
.scale
).to
.equal(2.0);
215 expect(canvas
.width
).to
.equal(3);
216 expect(canvas
.height
).to
.equal(3);
217 expect(canvas
.clientWidth
).to
.equal(6);
218 expect(canvas
.clientHeight
).to
.equal(6);
222 describe('autoscaling', function () {
226 beforeEach(function () {
227 canvas
= document
.createElement('canvas');
228 display
= new Display(canvas
);
229 display
.clipViewport
= true;
230 display
.resize(4, 3);
231 document
.body
.appendChild(canvas
);
234 afterEach(function () {
235 document
.body
.removeChild(canvas
);
238 it('should preserve aspect ratio while autoscaling', function () {
239 display
.autoscale(16, 9);
240 expect(canvas
.clientWidth
/ canvas
.clientHeight
).to
.equal(4 / 3);
243 it('should use width to determine scale when the current aspect ratio is wider than the target', function () {
244 display
.autoscale(9, 16);
245 expect(display
.absX(9)).to
.equal(4);
246 expect(display
.absY(18)).to
.equal(8);
247 expect(canvas
.clientWidth
).to
.equal(9);
248 expect(canvas
.clientHeight
).to
.equal(7); // round 9 / (4 / 3)
251 it('should use height to determine scale when the current aspect ratio is taller than the target', function () {
252 display
.autoscale(16, 9);
253 expect(display
.absX(9)).to
.equal(3);
254 expect(display
.absY(18)).to
.equal(6);
255 expect(canvas
.clientWidth
).to
.equal(12); // 16 * (4 / 3)
256 expect(canvas
.clientHeight
).to
.equal(9);
260 it('should not change the bitmap size of the canvas', function () {
261 display
.autoscale(16, 9);
262 expect(canvas
.width
).to
.equal(4);
263 expect(canvas
.height
).to
.equal(3);
267 describe('drawing', function () {
269 // TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the
272 beforeEach(function () {
273 display
= new Display(document
.createElement('canvas'));
274 display
.resize(4, 4);
277 it('should clear the screen on #clear without a logo set', function () {
278 display
.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]);
279 display
._logo
= null;
281 display
.resize(4, 4);
283 for (var i
= 0; i
< 4 * display
._fb_width
* display
._fb_height
; i
++) { empty
[i
] = 0; }
284 expect(display
).to
.have
.displayed(new Uint8Array(empty
));
287 it('should draw the logo on #clear with a logo set', function (done
) {
288 display
._logo
= { width
: 4, height
: 4, type
: "image/png", data
: make_image_png(checked_data
) };
290 display
.onflush = function () {
291 expect(display
).to
.have
.displayed(checked_data
);
292 expect(display
._fb_width
).to
.equal(4);
293 expect(display
._fb_height
).to
.equal(4);
299 it('should not draw directly on the target canvas', function () {
300 display
.fillRect(0, 0, 4, 4, [0, 0, 0xff]);
302 display
.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
304 for (var i
= 0; i
< 4 * display
._fb_width
* display
._fb_height
; i
+= 4) {
306 expected
[i
+1] = expected
[i
+2] = 0;
307 expected
[i
+3] = 0xff;
309 expect(display
).to
.have
.displayed(new Uint8Array(expected
));
312 it('should support filling a rectangle with particular color via #fillRect', function () {
313 display
.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
314 display
.fillRect(0, 0, 2, 2, [0xff, 0, 0]);
315 display
.fillRect(2, 2, 2, 2, [0xff, 0, 0]);
317 expect(display
).to
.have
.displayed(checked_data
);
320 it('should support copying an portion of the canvas via #copyImage', function () {
321 display
.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
322 display
.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]);
323 display
.copyImage(0, 0, 2, 2, 2, 2);
325 expect(display
).to
.have
.displayed(checked_data
);
328 it('should support drawing images via #imageRect', function (done
) {
329 display
.imageRect(0, 0, "image/png", make_image_png(checked_data
));
331 display
.onflush = function () {
332 expect(display
).to
.have
.displayed(checked_data
);
338 it('should support drawing tile data with a background color and sub tiles', function () {
339 display
.startTile(0, 0, 4, 4, [0, 0xff, 0]);
340 display
.subTile(0, 0, 2, 2, [0xff, 0, 0]);
341 display
.subTile(2, 2, 2, 2, [0xff, 0, 0]);
342 display
.finishTile();
344 expect(display
).to
.have
.displayed(checked_data
);
347 // We have a special cache for 16x16 tiles that we need to test
348 it('should support drawing a 16x16 tile', function () {
349 let large_checked_data
= new Uint8Array(16*16*4);
350 display
.resize(16, 16);
352 for (let y
= 0;y
< 16;y
++) {
353 for (let x
= 0;x
< 16;x
++) {
355 if ((x
< 4) && (y
< 4)) {
356 // NB: of course IE11 doesn't support #slice on ArrayBufferViews...
357 pixel
= Array
.prototype.slice
.call(checked_data
, (y
*4+x
)*4, (y
*4+x
+1)*4);
359 pixel
= [0, 0xff, 0, 255];
361 large_checked_data
.set(pixel
, (y
*16+x
)*4);
365 display
.startTile(0, 0, 16, 16, [0, 0xff, 0]);
366 display
.subTile(0, 0, 2, 2, [0xff, 0, 0]);
367 display
.subTile(2, 2, 2, 2, [0xff, 0, 0]);
368 display
.finishTile();
370 expect(display
).to
.have
.displayed(large_checked_data
);
373 it('should support drawing BGRX blit images with true color via #blitImage', function () {
375 for (var i
= 0; i
< 16; i
++) {
376 data
[i
* 4] = checked_data
[i
* 4 + 2];
377 data
[i
* 4 + 1] = checked_data
[i
* 4 + 1];
378 data
[i
* 4 + 2] = checked_data
[i
* 4];
379 data
[i
* 4 + 3] = checked_data
[i
* 4 + 3];
381 display
.blitImage(0, 0, 4, 4, data
, 0);
383 expect(display
).to
.have
.displayed(checked_data
);
386 it('should support drawing RGB blit images with true color via #blitRgbImage', function () {
388 for (var i
= 0; i
< 16; i
++) {
389 data
[i
* 3] = checked_data
[i
* 4];
390 data
[i
* 3 + 1] = checked_data
[i
* 4 + 1];
391 data
[i
* 3 + 2] = checked_data
[i
* 4 + 2];
393 display
.blitRgbImage(0, 0, 4, 4, data
, 0);
395 expect(display
).to
.have
.displayed(checked_data
);
398 it('should support drawing an image object via #drawImage', function () {
399 var img
= make_image_canvas(checked_data
);
400 display
.drawImage(img
, 0, 0);
402 expect(display
).to
.have
.displayed(checked_data
);
406 describe('the render queue processor', function () {
408 beforeEach(function () {
409 display
= new Display(document
.createElement('canvas'));
410 display
.resize(4, 4);
411 sinon
.spy(display
, '_scan_renderQ');
414 afterEach(function () {
415 window
.requestAnimationFrame
= this.old_requestAnimationFrame
;
418 it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {
419 display
._renderQ_push({ type
: 'noop' }); // does nothing
420 expect(display
._scan_renderQ
).to
.have
.been
.calledOnce
;
423 it('should not try to process an item when it is pushed on if we are waiting for other items', function () {
424 display
._renderQ
.length
= 2;
425 display
._renderQ_push({ type
: 'noop' });
426 expect(display
._scan_renderQ
).to
.not
.have
.been
.called
;
429 it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () {
430 var img
= { complete
: false, addEventListener
: sinon
.spy() }
431 display
._renderQ
= [{ type
: 'img', x
: 3, y
: 4, img
: img
},
432 { type
: 'fill', x
: 1, y
: 2, width
: 3, height
: 4, color
: 5 }];
433 display
.drawImage
= sinon
.spy();
434 display
.fillRect
= sinon
.spy();
436 display
._scan_renderQ();
437 expect(display
.drawImage
).to
.not
.have
.been
.called
;
438 expect(display
.fillRect
).to
.not
.have
.been
.called
;
439 expect(img
.addEventListener
).to
.have
.been
.calledOnce
;
441 display
._renderQ
[0].img
.complete
= true;
442 display
._scan_renderQ();
443 expect(display
.drawImage
).to
.have
.been
.calledOnce
;
444 expect(display
.fillRect
).to
.have
.been
.calledOnce
;
445 expect(img
.addEventListener
).to
.have
.been
.calledOnce
;
448 it('should call callback when queue is flushed', function () {
449 display
.onflush
= sinon
.spy();
450 display
.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
451 expect(display
.onflush
).to
.not
.have
.been
.called
;
453 expect(display
.onflush
).to
.have
.been
.calledOnce
;
456 it('should draw a blit image on type "blit"', function () {
457 display
.blitImage
= sinon
.spy();
458 display
._renderQ_push({ type
: 'blit', x
: 3, y
: 4, width
: 5, height
: 6, data
: [7, 8, 9] });
459 expect(display
.blitImage
).to
.have
.been
.calledOnce
;
460 expect(display
.blitImage
).to
.have
.been
.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
463 it('should draw a blit RGB image on type "blitRgb"', function () {
464 display
.blitRgbImage
= sinon
.spy();
465 display
._renderQ_push({ type
: 'blitRgb', x
: 3, y
: 4, width
: 5, height
: 6, data
: [7, 8, 9] });
466 expect(display
.blitRgbImage
).to
.have
.been
.calledOnce
;
467 expect(display
.blitRgbImage
).to
.have
.been
.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
470 it('should copy a region on type "copy"', function () {
471 display
.copyImage
= sinon
.spy();
472 display
._renderQ_push({ type
: 'copy', x
: 3, y
: 4, width
: 5, height
: 6, old_x
: 7, old_y
: 8 });
473 expect(display
.copyImage
).to
.have
.been
.calledOnce
;
474 expect(display
.copyImage
).to
.have
.been
.calledWith(7, 8, 3, 4, 5, 6);
477 it('should fill a rect with a given color on type "fill"', function () {
478 display
.fillRect
= sinon
.spy();
479 display
._renderQ_push({ type
: 'fill', x
: 3, y
: 4, width
: 5, height
: 6, color
: [7, 8, 9]});
480 expect(display
.fillRect
).to
.have
.been
.calledOnce
;
481 expect(display
.fillRect
).to
.have
.been
.calledWith(3, 4, 5, 6, [7, 8, 9]);
484 it('should draw an image from an image object on type "img" (if complete)', function () {
485 display
.drawImage
= sinon
.spy();
486 display
._renderQ_push({ type
: 'img', x
: 3, y
: 4, img
: { complete
: true } });
487 expect(display
.drawImage
).to
.have
.been
.calledOnce
;
488 expect(display
.drawImage
).to
.have
.been
.calledWith({ complete
: true }, 3, 4);