1- name: 2d.transformation.order
2  desc: Transformations are applied in the right order
3  testing:
4  - 2d.transformation.order
5  code: |
6    ctx.fillStyle = '#f00';
7    ctx.fillRect(0, 0, 100, 50);
8
9    ctx.scale(2, 1);
10    ctx.rotate(Math.PI / 2);
11    ctx.fillStyle = '#0f0';
12    ctx.fillRect(0, -50, 50, 50);
13    @assert pixel 75,25 == 0,255,0,255;
14  expected: green
15
16
17- name: 2d.transformation.scale.basic
18  desc: scale() works
19  testing:
20  - 2d.transformation.scale
21  code: |
22    ctx.fillStyle = '#f00';
23    ctx.fillRect(0, 0, 100, 50);
24
25    ctx.scale(2, 4);
26    ctx.fillStyle = '#0f0';
27    ctx.fillRect(0, 0, 50, 12.5);
28    @assert pixel 90,40 == 0,255,0,255;
29  expected: green
30
31- name: 2d.transformation.scale.zero
32  desc: scale() with a scale factor of zero works
33  testing:
34  - 2d.transformation.scale
35  code: |
36    ctx.fillStyle = '#0f0';
37    ctx.fillRect(0, 0, 100, 50);
38
39    ctx.save();
40    ctx.translate(50, 0);
41    ctx.scale(0, 1);
42    ctx.fillStyle = '#f00';
43    ctx.fillRect(0, 0, 100, 50);
44    ctx.restore();
45
46    ctx.save();
47    ctx.translate(0, 25);
48    ctx.scale(1, 0);
49    ctx.fillStyle = '#f00';
50    ctx.fillRect(0, 0, 100, 50);
51    ctx.restore();
52
53    canvas.toDataURL();
54
55    @assert pixel 50,25 == 0,255,0,255;
56  expected: green
57
58- name: 2d.transformation.scale.negative
59  desc: scale() with negative scale factors works
60  testing:
61  - 2d.transformation.scale
62  code: |
63    ctx.fillStyle = '#f00';
64    ctx.fillRect(0, 0, 100, 50);
65
66    ctx.save();
67    ctx.scale(-1, 1);
68    ctx.fillStyle = '#0f0';
69    ctx.fillRect(-50, 0, 50, 50);
70    ctx.restore();
71
72    ctx.save();
73    ctx.scale(1, -1);
74    ctx.fillStyle = '#0f0';
75    ctx.fillRect(50, -50, 50, 50);
76    ctx.restore();
77    @assert pixel 25,25 == 0,255,0,255;
78    @assert pixel 75,25 == 0,255,0,255;
79  expected: green
80
81- name: 2d.transformation.scale.large
82  desc: scale() with large scale factors works
83  notes: Not really that large at all, but it hits the limits in Firefox.
84  testing:
85  - 2d.transformation.scale
86  code: |
87    ctx.fillStyle = '#f00';
88    ctx.fillRect(0, 0, 100, 50);
89
90    ctx.scale(1e5, 1e5);
91    ctx.fillStyle = '#0f0';
92    ctx.fillRect(0, 0, 1, 1);
93    @assert pixel 50,25 == 0,255,0,255;
94  expected: green
95
96- name: 2d.transformation.scale.nonfinite
97  desc: scale() with Infinity/NaN is ignored
98  testing:
99  - 2d.nonfinite
100  code: |
101    ctx.fillStyle = '#f00';
102    ctx.fillRect(0, 0, 100, 50);
103
104    ctx.translate(100, 10);
105    @nonfinite ctx.scale(<0.1 Infinity -Infinity NaN>, <0.1 Infinity -Infinity NaN>);
106
107    ctx.fillStyle = '#0f0';
108    ctx.fillRect(-100, -10, 100, 50);
109
110    @assert pixel 50,25 == 0,255,0,255;
111  expected: green
112
113- name: 2d.transformation.scale.multiple
114  desc: Multiple scale()s combine
115  testing:
116  - 2d.transformation.scale.multiple
117  code: |
118    ctx.fillStyle = '#f00';
119    ctx.fillRect(0, 0, 100, 50);
120
121    ctx.scale(Math.sqrt(2), Math.sqrt(2));
122    ctx.scale(Math.sqrt(2), Math.sqrt(2));
123    ctx.fillStyle = '#0f0';
124    ctx.fillRect(0, 0, 50, 25);
125    @assert pixel 90,40 == 0,255,0,255;
126  expected: green
127
128
129- name: 2d.transformation.rotate.zero
130  desc: rotate() by 0 does nothing
131  testing:
132  - 2d.transformation.rotate
133  code: |
134    ctx.fillStyle = '#f00';
135    ctx.fillRect(0, 0, 100, 50);
136
137    ctx.rotate(0);
138    ctx.fillStyle = '#0f0';
139    ctx.fillRect(0, 0, 100, 50);
140    @assert pixel 50,25 == 0,255,0,255;
141  expected: green
142
143- name: 2d.transformation.rotate.radians
144  desc: rotate() uses radians
145  testing:
146  - 2d.transformation.rotate.radians
147  code: |
148    ctx.fillStyle = '#f00';
149    ctx.fillRect(0, 0, 100, 50);
150
151    ctx.rotate(Math.PI); // should fail obviously if this is 3.1 degrees
152    ctx.fillStyle = '#0f0';
153    ctx.fillRect(-100, -50, 100, 50);
154    @assert pixel 50,25 == 0,255,0,255;
155  expected: green
156
157- name: 2d.transformation.rotate.direction
158  desc: rotate() is clockwise
159  testing:
160  - 2d.transformation.rotate.direction
161  code: |
162    ctx.fillStyle = '#f00';
163    ctx.fillRect(0, 0, 100, 50);
164
165    ctx.rotate(Math.PI / 2);
166    ctx.fillStyle = '#0f0';
167    ctx.fillRect(0, -100, 50, 100);
168    @assert pixel 50,25 == 0,255,0,255;
169  expected: green
170
171- name: 2d.transformation.rotate.wrap
172  desc: rotate() wraps large positive values correctly
173  testing:
174  - 2d.transformation.rotate
175  code: |
176    ctx.fillStyle = '#f00';
177    ctx.fillRect(0, 0, 100, 50);
178
179    ctx.rotate(Math.PI * (1 + 4096)); // == pi (mod 2*pi)
180    // We need about pi +/- 0.001 in order to get correct-looking results
181    // 32-bit floats can store pi*4097 with precision 2^-10, so that should
182    // be safe enough on reasonable implementations
183    ctx.fillStyle = '#0f0';
184    ctx.fillRect(-100, -50, 100, 50);
185    @assert pixel 50,25 == 0,255,0,255;
186    @assert pixel 98,2 == 0,255,0,255;
187    @assert pixel 98,47 == 0,255,0,255;
188  expected: green
189
190- name: 2d.transformation.rotate.wrapnegative
191  desc: rotate() wraps large negative values correctly
192  testing:
193  - 2d.transformation.rotate
194  code: |
195    ctx.fillStyle = '#f00';
196    ctx.fillRect(0, 0, 100, 50);
197
198    ctx.rotate(-Math.PI * (1 + 4096));
199    ctx.fillStyle = '#0f0';
200    ctx.fillRect(-100, -50, 100, 50);
201    @assert pixel 50,25 == 0,255,0,255;
202    @assert pixel 98,2 == 0,255,0,255;
203    @assert pixel 98,47 == 0,255,0,255;
204  expected: green
205
206- name: 2d.transformation.rotate.nonfinite
207  desc: rotate() with Infinity/NaN is ignored
208  testing:
209  - 2d.nonfinite
210  code: |
211    ctx.fillStyle = '#f00';
212    ctx.fillRect(0, 0, 100, 50);
213
214    ctx.translate(100, 10);
215    @nonfinite ctx.rotate(<0.1 Infinity -Infinity NaN>);
216
217    ctx.fillStyle = '#0f0';
218    ctx.fillRect(-100, -10, 100, 50);
219
220    @assert pixel 50,25 == 0,255,0,255;
221  expected: green
222
223- name: 2d.transformation.translate.basic
224  desc: translate() works
225  testing:
226  - 2d.transformation.translate
227  code: |
228    ctx.fillStyle = '#f00';
229    ctx.fillRect(0, 0, 100, 50);
230
231    ctx.translate(100, 50);
232    ctx.fillStyle = '#0f0';
233    ctx.fillRect(-100, -50, 100, 50);
234    @assert pixel 90,40 == 0,255,0,255;
235  expected: green
236
237- name: 2d.transformation.translate.nonfinite
238  desc: translate() with Infinity/NaN is ignored
239  testing:
240  - 2d.nonfinite
241  code: |
242    ctx.fillStyle = '#f00';
243    ctx.fillRect(0, 0, 100, 50);
244
245    ctx.translate(100, 10);
246    @nonfinite ctx.translate(<0.1 Infinity -Infinity NaN>, <0.1 Infinity -Infinity NaN>);
247
248    ctx.fillStyle = '#0f0';
249    ctx.fillRect(-100, -10, 100, 50);
250
251    @assert pixel 50,25 == 0,255,0,255;
252  expected: green
253
254
255- name: 2d.transformation.transform.identity
256  desc: transform() with the identity matrix does nothing
257  testing:
258  - 2d.transformation.transform
259  code: |
260    ctx.fillStyle = '#f00';
261    ctx.fillRect(0, 0, 100, 50);
262
263    ctx.transform(1,0, 0,1, 0,0);
264    ctx.fillStyle = '#0f0';
265    ctx.fillRect(0, 0, 100, 50);
266    @assert pixel 50,25 == 0,255,0,255;
267  expected: green
268
269- name: 2d.transformation.transform.skewed
270  desc: transform() with skewy matrix transforms correctly
271  testing:
272  - 2d.transformation.transform
273  code: |
274    // Create green with a red square ring inside it
275    ctx.fillStyle = '#0f0';
276    ctx.fillRect(0, 0, 100, 50);
277    ctx.fillStyle = '#f00';
278    ctx.fillRect(20, 10, 60, 30);
279    ctx.fillStyle = '#0f0';
280    ctx.fillRect(40, 20, 20, 10);
281
282    // Draw a skewed shape to fill that gap, to make sure it is aligned correctly
283    ctx.transform(1,4, 2,3, 5,6);
284    // Post-transform coordinates:
285    //   [[20,10],[80,10],[80,40],[20,40],[20,10],[40,20],[40,30],[60,30],[60,20],[40,20],[20,10]];
286    // Hence pre-transform coordinates:
287    var pts=[[-7.4,11.2],[-43.4,59.2],[-31.4,53.2],[4.6,5.2],[-7.4,11.2],
288             [-15.4,25.2],[-11.4,23.2],[-23.4,39.2],[-27.4,41.2],[-15.4,25.2],
289             [-7.4,11.2]];
290    ctx.beginPath();
291    ctx.moveTo(pts[0][0], pts[0][1]);
292    for (var i = 0; i < pts.length; ++i)
293        ctx.lineTo(pts[i][0], pts[i][1]);
294    ctx.fill();
295    @assert pixel 21,11 == 0,255,0,255;
296    @assert pixel 79,11 == 0,255,0,255;
297    @assert pixel 21,39 == 0,255,0,255;
298    @assert pixel 79,39 == 0,255,0,255;
299    @assert pixel 39,19 == 0,255,0,255;
300    @assert pixel 61,19 == 0,255,0,255;
301    @assert pixel 39,31 == 0,255,0,255;
302    @assert pixel 61,31 == 0,255,0,255;
303  expected: green
304
305- name: 2d.transformation.transform.multiply
306  desc: transform() multiplies the CTM
307  testing:
308  - 2d.transformation.transform.multiply
309  code: |
310    ctx.fillStyle = '#f00';
311    ctx.fillRect(0, 0, 100, 50);
312
313    ctx.transform(1,2, 3,4, 5,6);
314    ctx.transform(-2,1, 3/2,-1/2, 1,-2);
315    ctx.fillStyle = '#0f0';
316    ctx.fillRect(0, 0, 100, 50);
317    @assert pixel 50,25 == 0,255,0,255;
318  expected: green
319
320- name: 2d.transformation.transform.nonfinite
321  desc: transform() with Infinity/NaN is ignored
322  testing:
323  - 2d.nonfinite
324  code: |
325    ctx.fillStyle = '#f00';
326    ctx.fillRect(0, 0, 100, 50);
327
328    ctx.translate(100, 10);
329    @nonfinite ctx.transform(<0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>);
330
331    ctx.fillStyle = '#0f0';
332    ctx.fillRect(-100, -10, 100, 50);
333
334    @assert pixel 50,25 == 0,255,0,255;
335  expected: green
336
337- name: 2d.transformation.setTransform.skewed
338  testing:
339  - 2d.transformation.setTransform
340  code: |
341    // Create green with a red square ring inside it
342    ctx.fillStyle = '#0f0';
343    ctx.fillRect(0, 0, 100, 50);
344    ctx.fillStyle = '#f00';
345    ctx.fillRect(20, 10, 60, 30);
346    ctx.fillStyle = '#0f0';
347    ctx.fillRect(40, 20, 20, 10);
348
349    // Draw a skewed shape to fill that gap, to make sure it is aligned correctly
350    ctx.setTransform(1,4, 2,3, 5,6);
351    // Post-transform coordinates:
352    //   [[20,10],[80,10],[80,40],[20,40],[20,10],[40,20],[40,30],[60,30],[60,20],[40,20],[20,10]];
353    // Hence pre-transform coordinates:
354    var pts=[[-7.4,11.2],[-43.4,59.2],[-31.4,53.2],[4.6,5.2],[-7.4,11.2],
355             [-15.4,25.2],[-11.4,23.2],[-23.4,39.2],[-27.4,41.2],[-15.4,25.2],
356             [-7.4,11.2]];
357    ctx.beginPath();
358    ctx.moveTo(pts[0][0], pts[0][1]);
359    for (var i = 0; i < pts.length; ++i)
360        ctx.lineTo(pts[i][0], pts[i][1]);
361    ctx.fill();
362    @assert pixel 21,11 == 0,255,0,255;
363    @assert pixel 79,11 == 0,255,0,255;
364    @assert pixel 21,39 == 0,255,0,255;
365    @assert pixel 79,39 == 0,255,0,255;
366    @assert pixel 39,19 == 0,255,0,255;
367    @assert pixel 61,19 == 0,255,0,255;
368    @assert pixel 39,31 == 0,255,0,255;
369    @assert pixel 61,31 == 0,255,0,255;
370  expected: green
371
372- name: 2d.transformation.setTransform.multiple
373  testing:
374  - 2d.transformation.setTransform.identity
375  code: |
376    ctx.fillStyle = '#f00';
377    ctx.fillRect(0, 0, 100, 50);
378
379    ctx.setTransform(1/2,0, 0,1/2, 0,0);
380    ctx.setTransform();
381    ctx.setTransform(2,0, 0,2, 0,0);
382    ctx.fillStyle = '#0f0';
383    ctx.fillRect(0, 0, 50, 25);
384    @assert pixel 75,35 == 0,255,0,255;
385  expected: green
386
387- name: 2d.transformation.setTransform.nonfinite
388  desc: setTransform() with Infinity/NaN is ignored
389  testing:
390  - 2d.nonfinite
391  code: |
392    ctx.fillStyle = '#f00';
393    ctx.fillRect(0, 0, 100, 50);
394
395    ctx.translate(100, 10);
396    @nonfinite ctx.setTransform(<0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>);
397
398    ctx.fillStyle = '#0f0';
399    ctx.fillRect(-100, -10, 100, 50);
400
401    @assert pixel 50,25 == 0,255,0,255;
402  expected: green
403
404- name: 2d.transformation.setTransform.3d
405  desc: setTransform() with 4x4 matrix keeps all parameters
406  testing:
407  - 2d.transformation.setTransform.3d
408  code: |
409    const transform = new DOMMatrix([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]);
410    ctx.setTransform(transform);
411    const canvasTransform = ctx.getTransform();
412    @assert transform.toLocaleString() == canvasTransform.toLocaleString();
413
414- name: 2d.transformation.transform.3d
415  desc: transform() with 4x4 matrix concatenates properly
416  testing:
417  - 2d.transformation.transform.3d
418  code: |
419    const transform = new DOMMatrix([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]);
420    ctx.transform(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
421    let canvasTransform = ctx.getTransform();
422    @assert transform.toLocaleString() == canvasTransform.toLocaleString();
423    ctx.transform(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
424    canvasTransform = ctx.getTransform();
425    transform.multiplySelf(transform);
426    @assert transform.toLocaleString() == canvasTransform.toLocaleString();
427
428- name: 2d.transformation.translate.3d
429  desc: translate() function with three arguments modifies the underlying matrix appropriately
430  testing:
431  - 2d.transformation.translate.3d
432  code: |
433    const dx = 2;
434    const dy = 3;
435    const dz = 4;
436    ctx.translate(dx, dy, dz);
437    let canvasTransform = ctx.getTransform();
438    @assert canvasTransform.m41 = dx;
439    @assert canvasTransform.m42 = dy;
440    @assert canvasTransform.m43 = dz;
441    ctx.translate(dx, dy, dz);
442    canvasTransform = ctx.getTransform();
443    @assert canvasTransform.m41 = 2 * dx;
444    @assert canvasTransform.m42 = 2 * dy;
445    @assert canvasTransform.m43 = 2 * dz;
446
447- name: 2d.transformation.scale.3d
448  desc: scale() function with three arguments modifies the underlying matrix appropriately
449  testing:
450  - 2d.transformation.scale.3d
451  code: |
452    const sx = 2;
453    const sy = 3;
454    const sz = 4;
455    ctx.scale(sx, sy, sz);
456    let canvasTransform = ctx.getTransform();
457    @assert canvasTransform.m11 = sx;
458    @assert canvasTransform.m22 = sy;
459    @assert canvasTransform.m33 = sz;
460    ctx.scale(sx, sy, sz);
461    canvasTransform = ctx.getTransform();
462    @assert canvasTransform.m11 = 2 * sx;
463    @assert canvasTransform.m22 = 2 * sy;
464    @assert canvasTransform.m33 = 2 * sz;
465
466- name: 2d.transformation.rotate3d.x
467  desc: rotate3d() around the x axis results in the correct transformation matrix
468  testing:
469  - 2d.transformation.rotate3d.x
470  code: |
471    // angles are in radians, test something that is not a multiple of pi
472    const angle = 2;
473    const domMatrix = new DOMMatrix();
474    ctx.rotate3d(angle, 0, 0);
475    domMatrix.rotateAxisAngleSelf(1, 0, 0, rad2deg(angle));
476    _assertMatricesApproxEqual(domMatrix, ctx.getTransform())
477    ctx.rotate3d(angle, 0, 0);
478    domMatrix.rotateAxisAngleSelf(1, 0, 0, rad2deg(angle));
479    _assertMatricesApproxEqual(domMatrix, ctx.getTransform())
480
481- name: 2d.transformation.rotate3d.y
482  desc: rotate3d() around the y axis results in the correct transformation matrix
483  testing:
484  - 2d.transformation.rotate3d.y
485  code: |
486    // angles are in radians, test something that is not a multiple of pi
487    const angle = 2;
488    const domMatrix = new DOMMatrix();
489    ctx.rotate3d(0, angle, 0);
490    domMatrix.rotateAxisAngleSelf(0, 1, 0, rad2deg(angle));
491    _assertMatricesApproxEqual(domMatrix, ctx.getTransform())
492    ctx.rotate3d(0, angle, 0);
493    domMatrix.rotateAxisAngleSelf(0, 1, 0, rad2deg(angle));
494    _assertMatricesApproxEqual(domMatrix, ctx.getTransform())
495
496- name: 2d.transformation.rotate3d.z
497  desc: rotate3d() around the z axis results in the correct transformation matrix
498  testing:
499  - 2d.transformation.rotate3d.z
500  code: |
501    // angles are in radians, test something that is not a multiple of pi
502    const angle = 2;
503    const domMatrix = new DOMMatrix();
504    ctx.rotate3d(0, 0, angle);
505    domMatrix.rotateAxisAngleSelf(0, 0, 1, rad2deg(angle));
506    _assertMatricesApproxEqual(domMatrix, ctx.getTransform())
507    ctx.rotate3d(0, 0, angle);
508    domMatrix.rotateAxisAngleSelf(0, 0, 1, rad2deg(angle));
509    _assertMatricesApproxEqual(domMatrix, ctx.getTransform())
510
511- name: 2d.transformation.rotate3d
512  desc: rotate3d() results in the correct transformation matrix
513  testing:
514  - 2d.transformation.rotate3d
515  code: |
516    // angles are in radians, test something that is not a multiple of pi
517    const angleX = 2;
518    const angleY = 3;
519    const angleZ = 4;
520    const domMatrix = new DOMMatrix();
521    ctx.rotate3d(angleX, angleY, angleZ);
522    domMatrix.rotateSelf(rad2deg(angleX), rad2deg(angleY), rad2deg(angleZ));
523    _assertMatricesApproxEqual(domMatrix, ctx.getTransform());
524    ctx.rotate3d(angleX, angleY, angleZ);
525    domMatrix.rotateSelf(rad2deg(angleX), rad2deg(angleY), rad2deg(angleZ));
526    _assertMatricesApproxEqual(domMatrix, ctx.getTransform());
527
528- name: 2d.transformation.rotateAxis
529  desc: rotateAxis() results in the correct transformation matrix
530  testing:
531  - 2d.transformation.rotateAxis
532  code: |
533    // angles are in radians, test something that is not a multiple of pi
534    const angle = 2;
535    const axis = {x: 1, y: 2, z: 3}
536    const domMatrix = new DOMMatrix();
537    ctx.rotateAxis(axis.x, axis.y, axis.z, angle);
538    domMatrix.rotateAxisAngleSelf(axis.x, axis.y, axis.z, rad2deg(angle));
539    _assertMatricesApproxEqual(domMatrix, ctx.getTransform());
540    ctx.rotateAxis(axis.x, axis.y, axis.z, angle);
541    domMatrix.rotateAxisAngleSelf(axis.x, axis.y, axis.z, rad2deg(angle));
542    _assertMatricesApproxEqual(domMatrix, ctx.getTransform());
543
544- name: 2d.transformation.perspective
545  desc: perspective() results in the correct transformation matrix
546  testing:
547  - 2d.transformation.perspective
548  code: |
549    const length = 100;
550    ctx.perspective(length);
551    const domMatrix = new DOMMatrix();
552    domMatrix.m34 = -1/length;
553    _assertMatricesApproxEqual(domMatrix, ctx.getTransform());
554    ctx.rotateAxis(1, 2, 3, 4);
555    domMatrix.rotateAxisAngleSelf(1, 2, 3, rad2deg(4));
556    _assertMatricesApproxEqual(domMatrix, ctx.getTransform());
557
558- name: 2d.transformation.combined.3d.transforms
559  desc: perspective and rotate3d work together
560  testing:
561  - 2d.transformation.3d.transforms
562  code: |
563    ctx.fillStyle = '#0f0';
564    ctx.fillRect(0, 0, 100, 50);
565
566    // Create a perspective transform in combiation with fillRect to draw a red
567    // trapezoid then draw a smaller green trapezoid inside it.
568    ctx.translate(50, 5);
569    ctx.perspective(100);
570    ctx.rotate3d(Math.PI/4, 0, 0);
571    ctx.fillStyle = "#f00";
572    ctx.fillRect(-30, 0, 60, 40);
573    ctx.fillStyle = "#0f0";
574    ctx.fillRect(-15, 10, 30, 20);
575
576    // These are the expected points of those two trapezoids from putting
577    // the corners of the filled rectangles through the perspective transform.
578    const bigTrapezoid = [[81, 5], [93, 45], [7, 45], [19, 5]];
579    const smallTrapezoid = [[32, 31], [68, 31], [65, 13], [35, 13]];
580
581    // Now filling a shape with green at those exact points with an identity
582    // transform should result in a purely green image.
583    ctx.resetTransform();
584    ctx.beginPath();
585    ctx.moveTo(bigTrapezoid[3][0], bigTrapezoid[3][1]);
586    for (const point of bigTrapezoid) ctx.lineTo(point[0], point[1])
587    ctx.lineTo(smallTrapezoid[3][0], smallTrapezoid[3][1]);
588    for (const point of smallTrapezoid) ctx.lineTo(point[0], point[1])
589    ctx.fill();
590    @assert pixel 21,11 == 0,255,0,255;
591    @assert pixel 79,11 == 0,255,0,255;
592    @assert pixel 21,39 == 0,255,0,255;
593    @assert pixel 79,39 == 0,255,0,255;
594    @assert pixel 39,19 == 0,255,0,255;
595    @assert pixel 61,19 == 0,255,0,255;
596    @assert pixel 39,31 == 0,255,0,255;
597    @assert pixel 61,31 == 0,255,0,255;
598  expected: green
599