1 /* jshint expr: true */
2 var expect
= chai
.expect
;
4 import Base64
from '../core/base64.js';
5 import Display
from '../core/display.js';
7 import sinon
from '../vendor/sinon.js';
9 describe('Display/Canvas Helper', function () {
11 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
12 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
13 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
14 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
16 checked_data
= new Uint8Array(checked_data
);
18 var basic_data
= [0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255];
19 basic_data
= new Uint8Array(basic_data
);
21 function make_image_canvas (input_data
) {
22 var canvas
= document
.createElement('canvas');
25 var ctx
= canvas
.getContext('2d');
26 var data
= ctx
.createImageData(4, 4);
27 for (var i
= 0; i
< checked_data
.length
; i
++) { data
.data
[i
] = input_data
[i
]; }
28 ctx
.putImageData(data
, 0, 0);
32 function make_image_png (input_data
) {
33 var canvas
= make_image_canvas(input_data
);
34 var url
= canvas
.toDataURL();
35 var data
= url
.split(",")[1];
36 return Base64
.decode(data
);
39 describe('viewport handling', function () {
41 beforeEach(function () {
42 display
= new Display(document
.createElement('canvas'));
43 display
.clipViewport
= true;
45 display
.viewportChangeSize(3, 3);
46 display
.viewportChangePos(1, 1);
49 it('should take viewport location into consideration when drawing images', function () {
51 display
.viewportChangeSize(2, 2);
52 display
.drawImage(make_image_canvas(basic_data
), 1, 1);
55 var expected
= new Uint8Array(16);
57 for (i
= 0; i
< 8; i
++) { expected
[i
] = basic_data
[i
]; }
58 for (i
= 8; i
< 16; i
++) { expected
[i
] = 0; }
59 expect(display
).to
.have
.displayed(expected
);
62 it('should resize the target canvas when resizing the viewport', function() {
63 display
.viewportChangeSize(2, 2);
64 expect(display
._target
.width
).to
.equal(2);
65 expect(display
._target
.height
).to
.equal(2);
68 it('should move the viewport if necessary', function() {
69 display
.viewportChangeSize(5, 5);
70 expect(display
.absX(0)).to
.equal(0);
71 expect(display
.absY(0)).to
.equal(0);
72 expect(display
._target
.width
).to
.equal(5);
73 expect(display
._target
.height
).to
.equal(5);
76 it('should limit the viewport to the framebuffer size', function() {
77 display
.viewportChangeSize(6, 6);
78 expect(display
._target
.width
).to
.equal(5);
79 expect(display
._target
.height
).to
.equal(5);
82 it('should redraw when moving the viewport', function () {
83 display
.flip
= sinon
.spy();
84 display
.viewportChangePos(-1, 1);
85 expect(display
.flip
).to
.have
.been
.calledOnce
;
88 it('should redraw when resizing the viewport', function () {
89 display
.flip
= sinon
.spy();
90 display
.viewportChangeSize(2, 2);
91 expect(display
.flip
).to
.have
.been
.calledOnce
;
94 it('should show the entire framebuffer when disabling the viewport', function() {
95 display
.clipViewport
= false;
96 expect(display
.absX(0)).to
.equal(0);
97 expect(display
.absY(0)).to
.equal(0);
98 expect(display
._target
.width
).to
.equal(5);
99 expect(display
._target
.height
).to
.equal(5);
102 it('should ignore viewport changes when the viewport is disabled', function() {
103 display
.clipViewport
= false;
104 display
.viewportChangeSize(2, 2);
105 display
.viewportChangePos(1, 1);
106 expect(display
.absX(0)).to
.equal(0);
107 expect(display
.absY(0)).to
.equal(0);
108 expect(display
._target
.width
).to
.equal(5);
109 expect(display
._target
.height
).to
.equal(5);
112 it('should show the entire framebuffer just after enabling the viewport', function() {
113 display
.clipViewport
= false;
114 display
.clipViewport
= true;
115 expect(display
.absX(0)).to
.equal(0);
116 expect(display
.absY(0)).to
.equal(0);
117 expect(display
._target
.width
).to
.equal(5);
118 expect(display
._target
.height
).to
.equal(5);
122 describe('resizing', function () {
124 beforeEach(function () {
125 display
= new Display(document
.createElement('canvas'));
126 display
.clipViewport
= false;
127 display
.resize(4, 4);
130 it('should change the size of the logical canvas', function () {
131 display
.resize(5, 7);
132 expect(display
._fb_width
).to
.equal(5);
133 expect(display
._fb_height
).to
.equal(7);
136 it('should keep the framebuffer data', function () {
137 display
.fillRect(0, 0, 4, 4, [0, 0, 0xff]);
138 display
.resize(2, 2);
141 for (var i
= 0; i
< 4 * 2*2; i
+= 4) {
143 expected
[i
+1] = expected
[i
+2] = 0;
144 expected
[i
+3] = 0xff;
146 expect(display
).to
.have
.displayed(new Uint8Array(expected
));
149 describe('viewport', function () {
150 beforeEach(function () {
151 display
.clipViewport
= true;
152 display
.viewportChangeSize(3, 3);
153 display
.viewportChangePos(1, 1);
156 it('should keep the viewport position and size if possible', function () {
157 display
.resize(6, 6);
158 expect(display
.absX(0)).to
.equal(1);
159 expect(display
.absY(0)).to
.equal(1);
160 expect(display
._target
.width
).to
.equal(3);
161 expect(display
._target
.height
).to
.equal(3);
164 it('should move the viewport if necessary', function () {
165 display
.resize(3, 3);
166 expect(display
.absX(0)).to
.equal(0);
167 expect(display
.absY(0)).to
.equal(0);
168 expect(display
._target
.width
).to
.equal(3);
169 expect(display
._target
.height
).to
.equal(3);
172 it('should shrink the viewport if necessary', function () {
173 display
.resize(2, 2);
174 expect(display
.absX(0)).to
.equal(0);
175 expect(display
.absY(0)).to
.equal(0);
176 expect(display
._target
.width
).to
.equal(2);
177 expect(display
._target
.height
).to
.equal(2);
182 describe('rescaling', function () {
186 beforeEach(function () {
187 canvas
= document
.createElement('canvas');
188 display
= new Display(canvas
);
189 display
.clipViewport
= true;
190 display
.resize(4, 4);
191 display
.viewportChangeSize(3, 3);
192 display
.viewportChangePos(1, 1);
193 document
.body
.appendChild(canvas
);
196 afterEach(function () {
197 document
.body
.removeChild(canvas
);
200 it('should not change the bitmap size of the canvas', function () {
202 expect(canvas
.width
).to
.equal(3);
203 expect(canvas
.height
).to
.equal(3);
206 it('should change the effective rendered size of the canvas', function () {
208 expect(canvas
.clientWidth
).to
.equal(6);
209 expect(canvas
.clientHeight
).to
.equal(6);
212 it('should not change when resizing', function () {
214 display
.resize(5, 5);
215 expect(display
.scale
).to
.equal(2.0);
216 expect(canvas
.width
).to
.equal(3);
217 expect(canvas
.height
).to
.equal(3);
218 expect(canvas
.clientWidth
).to
.equal(6);
219 expect(canvas
.clientHeight
).to
.equal(6);
223 describe('autoscaling', function () {
227 beforeEach(function () {
228 canvas
= document
.createElement('canvas');
229 display
= new Display(canvas
);
230 display
.clipViewport
= true;
231 display
.resize(4, 3);
232 document
.body
.appendChild(canvas
);
235 afterEach(function () {
236 document
.body
.removeChild(canvas
);
239 it('should preserve aspect ratio while autoscaling', function () {
240 display
.autoscale(16, 9);
241 expect(canvas
.clientWidth
/ canvas
.clientHeight
).to
.equal(4 / 3);
244 it('should use width to determine scale when the current aspect ratio is wider than the target', function () {
245 display
.autoscale(9, 16);
246 expect(display
.absX(9)).to
.equal(4);
247 expect(display
.absY(18)).to
.equal(8);
248 expect(canvas
.clientWidth
).to
.equal(9);
249 expect(canvas
.clientHeight
).to
.equal(7); // round 9 / (4 / 3)
252 it('should use height to determine scale when the current aspect ratio is taller than the target', function () {
253 display
.autoscale(16, 9);
254 expect(display
.absX(9)).to
.equal(3);
255 expect(display
.absY(18)).to
.equal(6);
256 expect(canvas
.clientWidth
).to
.equal(12); // 16 * (4 / 3)
257 expect(canvas
.clientHeight
).to
.equal(9);
261 it('should not change the bitmap size of the canvas', function () {
262 display
.autoscale(16, 9);
263 expect(canvas
.width
).to
.equal(4);
264 expect(canvas
.height
).to
.equal(3);
268 describe('drawing', function () {
270 // TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the
273 beforeEach(function () {
274 display
= new Display(document
.createElement('canvas'));
275 display
.resize(4, 4);
278 it('should clear the screen on #clear without a logo set', function () {
279 display
.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]);
280 display
._logo
= null;
282 display
.resize(4, 4);
284 for (var i
= 0; i
< 4 * display
._fb_width
* display
._fb_height
; i
++) { empty
[i
] = 0; }
285 expect(display
).to
.have
.displayed(new Uint8Array(empty
));
288 it('should draw the logo on #clear with a logo set', function (done
) {
289 display
._logo
= { width
: 4, height
: 4, type
: "image/png", data
: make_image_png(checked_data
) };
291 display
.onflush = function () {
292 expect(display
).to
.have
.displayed(checked_data
);
293 expect(display
._fb_width
).to
.equal(4);
294 expect(display
._fb_height
).to
.equal(4);
300 it('should not draw directly on the target canvas', function () {
301 display
.fillRect(0, 0, 4, 4, [0, 0, 0xff]);
303 display
.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
305 for (var i
= 0; i
< 4 * display
._fb_width
* display
._fb_height
; i
+= 4) {
307 expected
[i
+1] = expected
[i
+2] = 0;
308 expected
[i
+3] = 0xff;
310 expect(display
).to
.have
.displayed(new Uint8Array(expected
));
313 it('should support filling a rectangle with particular color via #fillRect', function () {
314 display
.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
315 display
.fillRect(0, 0, 2, 2, [0xff, 0, 0]);
316 display
.fillRect(2, 2, 2, 2, [0xff, 0, 0]);
318 expect(display
).to
.have
.displayed(checked_data
);
321 it('should support copying an portion of the canvas via #copyImage', function () {
322 display
.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
323 display
.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]);
324 display
.copyImage(0, 0, 2, 2, 2, 2);
326 expect(display
).to
.have
.displayed(checked_data
);
329 it('should support drawing images via #imageRect', function (done
) {
330 display
.imageRect(0, 0, "image/png", make_image_png(checked_data
));
332 display
.onflush = function () {
333 expect(display
).to
.have
.displayed(checked_data
);
339 it('should support drawing tile data with a background color and sub tiles', function () {
340 display
.startTile(0, 0, 4, 4, [0, 0xff, 0]);
341 display
.subTile(0, 0, 2, 2, [0xff, 0, 0]);
342 display
.subTile(2, 2, 2, 2, [0xff, 0, 0]);
343 display
.finishTile();
345 expect(display
).to
.have
.displayed(checked_data
);
348 // We have a special cache for 16x16 tiles that we need to test
349 it('should support drawing a 16x16 tile', function () {
350 let large_checked_data
= new Uint8Array(16*16*4);
351 display
.resize(16, 16);
353 for (let y
= 0;y
< 16;y
++) {
354 for (let x
= 0;x
< 16;x
++) {
356 if ((x
< 4) && (y
< 4)) {
357 // NB: of course IE11 doesn't support #slice on ArrayBufferViews...
358 pixel
= Array
.prototype.slice
.call(checked_data
, (y
*4+x
)*4, (y
*4+x
+1)*4);
360 pixel
= [0, 0xff, 0, 255];
362 large_checked_data
.set(pixel
, (y
*16+x
)*4);
366 display
.startTile(0, 0, 16, 16, [0, 0xff, 0]);
367 display
.subTile(0, 0, 2, 2, [0xff, 0, 0]);
368 display
.subTile(2, 2, 2, 2, [0xff, 0, 0]);
369 display
.finishTile();
371 expect(display
).to
.have
.displayed(large_checked_data
);
374 it('should support drawing BGRX blit images with true color via #blitImage', function () {
376 for (var i
= 0; i
< 16; i
++) {
377 data
[i
* 4] = checked_data
[i
* 4 + 2];
378 data
[i
* 4 + 1] = checked_data
[i
* 4 + 1];
379 data
[i
* 4 + 2] = checked_data
[i
* 4];
380 data
[i
* 4 + 3] = checked_data
[i
* 4 + 3];
382 display
.blitImage(0, 0, 4, 4, data
, 0);
384 expect(display
).to
.have
.displayed(checked_data
);
387 it('should support drawing RGB blit images with true color via #blitRgbImage', function () {
389 for (var i
= 0; i
< 16; i
++) {
390 data
[i
* 3] = checked_data
[i
* 4];
391 data
[i
* 3 + 1] = checked_data
[i
* 4 + 1];
392 data
[i
* 3 + 2] = checked_data
[i
* 4 + 2];
394 display
.blitRgbImage(0, 0, 4, 4, data
, 0);
396 expect(display
).to
.have
.displayed(checked_data
);
399 it('should support drawing an image object via #drawImage', function () {
400 var img
= make_image_canvas(checked_data
);
401 display
.drawImage(img
, 0, 0);
403 expect(display
).to
.have
.displayed(checked_data
);
407 describe('the render queue processor', function () {
409 beforeEach(function () {
410 display
= new Display(document
.createElement('canvas'));
411 display
.resize(4, 4);
412 sinon
.spy(display
, '_scan_renderQ');
415 afterEach(function () {
416 window
.requestAnimationFrame
= this.old_requestAnimationFrame
;
419 it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {
420 display
._renderQ_push({ type
: 'noop' }); // does nothing
421 expect(display
._scan_renderQ
).to
.have
.been
.calledOnce
;
424 it('should not try to process an item when it is pushed on if we are waiting for other items', function () {
425 display
._renderQ
.length
= 2;
426 display
._renderQ_push({ type
: 'noop' });
427 expect(display
._scan_renderQ
).to
.not
.have
.been
.called
;
430 it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () {
431 var img
= { complete
: false, addEventListener
: sinon
.spy() }
432 display
._renderQ
= [{ type
: 'img', x
: 3, y
: 4, img
: img
},
433 { type
: 'fill', x
: 1, y
: 2, width
: 3, height
: 4, color
: 5 }];
434 display
.drawImage
= sinon
.spy();
435 display
.fillRect
= sinon
.spy();
437 display
._scan_renderQ();
438 expect(display
.drawImage
).to
.not
.have
.been
.called
;
439 expect(display
.fillRect
).to
.not
.have
.been
.called
;
440 expect(img
.addEventListener
).to
.have
.been
.calledOnce
;
442 display
._renderQ
[0].img
.complete
= true;
443 display
._scan_renderQ();
444 expect(display
.drawImage
).to
.have
.been
.calledOnce
;
445 expect(display
.fillRect
).to
.have
.been
.calledOnce
;
446 expect(img
.addEventListener
).to
.have
.been
.calledOnce
;
449 it('should call callback when queue is flushed', function () {
450 display
.onflush
= sinon
.spy();
451 display
.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
452 expect(display
.onflush
).to
.not
.have
.been
.called
;
454 expect(display
.onflush
).to
.have
.been
.calledOnce
;
457 it('should draw a blit image on type "blit"', function () {
458 display
.blitImage
= sinon
.spy();
459 display
._renderQ_push({ type
: 'blit', x
: 3, y
: 4, width
: 5, height
: 6, data
: [7, 8, 9] });
460 expect(display
.blitImage
).to
.have
.been
.calledOnce
;
461 expect(display
.blitImage
).to
.have
.been
.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
464 it('should draw a blit RGB image on type "blitRgb"', function () {
465 display
.blitRgbImage
= sinon
.spy();
466 display
._renderQ_push({ type
: 'blitRgb', x
: 3, y
: 4, width
: 5, height
: 6, data
: [7, 8, 9] });
467 expect(display
.blitRgbImage
).to
.have
.been
.calledOnce
;
468 expect(display
.blitRgbImage
).to
.have
.been
.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
471 it('should copy a region on type "copy"', function () {
472 display
.copyImage
= sinon
.spy();
473 display
._renderQ_push({ type
: 'copy', x
: 3, y
: 4, width
: 5, height
: 6, old_x
: 7, old_y
: 8 });
474 expect(display
.copyImage
).to
.have
.been
.calledOnce
;
475 expect(display
.copyImage
).to
.have
.been
.calledWith(7, 8, 3, 4, 5, 6);
478 it('should fill a rect with a given color on type "fill"', function () {
479 display
.fillRect
= sinon
.spy();
480 display
._renderQ_push({ type
: 'fill', x
: 3, y
: 4, width
: 5, height
: 6, color
: [7, 8, 9]});
481 expect(display
.fillRect
).to
.have
.been
.calledOnce
;
482 expect(display
.fillRect
).to
.have
.been
.calledWith(3, 4, 5, 6, [7, 8, 9]);
485 it('should draw an image from an image object on type "img" (if complete)', function () {
486 display
.drawImage
= sinon
.spy();
487 display
._renderQ_push({ type
: 'img', x
: 3, y
: 4, img
: { complete
: true } });
488 expect(display
.drawImage
).to
.have
.been
.calledOnce
;
489 expect(display
.drawImage
).to
.have
.been
.calledWith({ complete
: true }, 3, 4);