• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1describe('Canvas Behavior', () => {
2    let container;
3
4    beforeEach(async () => {
5        await EverythingLoaded;
6        container = document.createElement('div');
7        container.innerHTML = `
8            <canvas width=600 height=600 id=test></canvas>
9            <canvas width=600 height=600 id=report></canvas>`;
10        document.body.appendChild(container);
11    });
12
13    afterEach(() => {
14        document.body.removeChild(container);
15    });
16
17    gm('canvas_api_example', (canvas) => {
18        const paint = new CanvasKit.Paint();
19        paint.setStrokeWidth(2.0);
20        paint.setAntiAlias(true);
21        paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
22        paint.setStyle(CanvasKit.PaintStyle.Stroke);
23        paint.setDither(false);
24
25        canvas.drawLine(3, 10, 30, 15, paint);
26        const rrect = CanvasKit.RRectXY([5, 35, 45, 80], 15, 10);
27        canvas.drawRRect(rrect, paint);
28
29        canvas.drawOval(CanvasKit.LTRBRect(5, 35, 45, 80), paint);
30
31        canvas.drawArc(CanvasKit.LTRBRect(55, 35, 95, 80), 15, 270, true, paint);
32
33        const font = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 20);
34        canvas.drawText('this is ascii text', 5, 100, paint, font);
35
36        const blob = CanvasKit.TextBlob.MakeFromText('Unicode chars �� é É ص', font);
37        canvas.drawTextBlob(blob, 5, 130, paint);
38
39        font.delete();
40        blob.delete();
41        paint.delete();
42        // See canvas2d for more API tests
43    });
44
45    gm('effect_and_text_example', (canvas) => {
46        const path = starPath(CanvasKit);
47        const paint = new CanvasKit.Paint();
48
49        const textPaint = new CanvasKit.Paint();
50        textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0));
51        textPaint.setAntiAlias(true);
52
53        const textFont = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 30);
54
55        const dpe = CanvasKit.PathEffect.MakeDash([15, 5, 5, 10], 1);
56
57        paint.setPathEffect(dpe);
58        paint.setStyle(CanvasKit.PaintStyle.Stroke);
59        paint.setStrokeWidth(5.0);
60        paint.setAntiAlias(true);
61        paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
62
63        canvas.drawPath(path, paint);
64        canvas.drawText('This is text', 10, 280, textPaint, textFont);
65
66        dpe.delete();
67        path.delete();
68        paint.delete();
69        textFont.delete();
70        textPaint.delete();
71    });
72
73    gm('patheffects_canvas', (canvas) => {
74        const path = starPath(CanvasKit, 100, 100, 100);
75        const paint = new CanvasKit.Paint();
76
77        const cornerEffect = CanvasKit.PathEffect.MakeCorner(10);
78        const discreteEffect = CanvasKit.PathEffect.MakeDiscrete(5, 10, 0);
79
80        paint.setPathEffect(cornerEffect);
81        paint.setStyle(CanvasKit.PaintStyle.Stroke);
82        paint.setStrokeWidth(5.0);
83        paint.setAntiAlias(true);
84        paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
85        canvas.drawPath(path, paint);
86
87        canvas.translate(200, 0);
88
89        paint.setPathEffect(discreteEffect);
90        canvas.drawPath(path, paint);
91
92        cornerEffect.delete();
93        path.delete();
94        paint.delete();
95    });
96
97    it('returns the depth of the save state stack', () => {
98        const canvas = new CanvasKit.Canvas();
99        expect(canvas.getSaveCount()).toEqual(1);
100        canvas.save();
101        canvas.save();
102        canvas.restore();
103        canvas.save();
104        canvas.save();
105        expect(canvas.getSaveCount()).toEqual(4);
106        // does nothing, by the SkCanvas API
107        canvas.restoreToCount(500);
108        expect(canvas.getSaveCount()).toEqual(4);
109        canvas.restore();
110        expect(canvas.getSaveCount()).toEqual(3);
111        canvas.save();
112        canvas.restoreToCount(2);
113        expect(canvas.getSaveCount()).toEqual(2);
114    });
115
116    gm('circle_canvas', (canvas) => {
117        const path = starPath(CanvasKit);
118
119        const paint = new CanvasKit.Paint();
120
121        paint.setStyle(CanvasKit.PaintStyle.Stroke);
122        paint.setStrokeWidth(5.0);
123        paint.setAntiAlias(true);
124        paint.setColor(CanvasKit.CYAN);
125
126        canvas.drawCircle(30, 50, 15, paint);
127
128        paint.setStyle(CanvasKit.PaintStyle.Fill);
129        paint.setColor(CanvasKit.RED);
130        canvas.drawCircle(130, 80, 60, paint);
131        canvas.drawCircle(20, 150, 60, paint);
132
133        path.delete();
134        paint.delete();
135    });
136
137    gm('rrect_canvas', (canvas) => {
138        const path = starPath(CanvasKit);
139
140        const paint = new CanvasKit.Paint();
141
142        paint.setStyle(CanvasKit.PaintStyle.Stroke);
143        paint.setStrokeWidth(3.0);
144        paint.setAntiAlias(true);
145        paint.setColor(CanvasKit.BLACK);
146
147        canvas.drawRRect(CanvasKit.RRectXY(
148            CanvasKit.LTRBRect(10, 10, 50, 50), 5, 10), paint);
149
150        canvas.drawRRect(CanvasKit.RRectXY(
151            CanvasKit.LTRBRect(60, 10, 110, 50), 10, 5), paint);
152
153        canvas.drawRRect(CanvasKit.RRectXY(
154            CanvasKit.LTRBRect(10, 60, 210, 260), 0, 30), paint);
155
156        canvas.drawRRect(CanvasKit.RRectXY(
157            CanvasKit.LTRBRect(50, 90, 160, 210), 30, 30), paint);
158
159        path.delete();
160        paint.delete();
161    });
162
163    gm('rrect_8corners_canvas', (canvas) => {
164        const path = starPath(CanvasKit);
165
166        const paint = new CanvasKit.Paint();
167
168        paint.setStyle(CanvasKit.PaintStyle.Stroke);
169        paint.setStrokeWidth(3.0);
170        paint.setAntiAlias(true);
171        paint.setColor(CanvasKit.BLACK);
172
173        canvas.drawRRect([10, 10, 210, 210,
174          // top left corner, going clockwise
175          10, 30,
176          30, 10,
177          50, 75,
178          120, 120,
179        ], paint);
180
181        path.delete();
182        paint.delete();
183    });
184
185    // As above, except with the array passed in via malloc'd memory.
186    gm('rrect_8corners_malloc_canvas', (canvas) => {
187        const path = starPath(CanvasKit);
188
189        const paint = new CanvasKit.Paint();
190
191        paint.setStyle(CanvasKit.PaintStyle.Stroke);
192        paint.setStrokeWidth(3.0);
193        paint.setAntiAlias(true);
194        paint.setColor(CanvasKit.BLACK);
195
196        const rrect = CanvasKit.Malloc(Float32Array, 12);
197        rrect.toTypedArray().set([10, 10, 210, 210,
198          // top left corner, going clockwise
199          10, 30,
200          30, 10,
201          50, 75,
202          120, 120,
203        ]);
204
205        canvas.drawRRect(rrect, paint);
206
207        CanvasKit.Free(rrect);
208        path.delete();
209        paint.delete();
210    });
211
212    gm('drawDRRect_canvas', (canvas) => {
213        const path = starPath(CanvasKit);
214
215        const paint = new CanvasKit.Paint();
216
217        paint.setStyle(CanvasKit.PaintStyle.Fill);
218        paint.setStrokeWidth(3.0);
219        paint.setAntiAlias(true);
220        paint.setColor(CanvasKit.BLACK);
221
222        const outer = CanvasKit.RRectXY(CanvasKit.LTRBRect(10, 60, 210, 260), 10, 5);
223        const inner = CanvasKit.RRectXY(CanvasKit.LTRBRect(50, 90, 160, 210), 30, 30);
224
225        canvas.drawDRRect(outer, inner, paint);
226
227        path.delete();
228        paint.delete();
229    });
230
231    gm('colorfilters_canvas', (canvas) => {
232        canvas.clear(CanvasKit.Color(230, 230, 230));
233
234        const paint = new CanvasKit.Paint();
235
236        const blue = CanvasKit.ColorFilter.MakeBlend(
237            CanvasKit.BLUE, CanvasKit.BlendMode.SrcIn);
238        const red =  CanvasKit.ColorFilter.MakeBlend(
239            CanvasKit.Color(255, 0, 0, 0.8), CanvasKit.BlendMode.SrcOver);
240        const lerp = CanvasKit.ColorFilter.MakeLerp(0.6, red, blue);
241
242        paint.setStyle(CanvasKit.PaintStyle.Fill);
243        paint.setAntiAlias(true);
244
245        paint.setColorFilter(blue)
246        canvas.drawRect(CanvasKit.LTRBRect(10, 10, 60, 60), paint);
247        paint.setColorFilter(lerp)
248        canvas.drawRect(CanvasKit.LTRBRect(50, 10, 100, 60), paint);
249        paint.setColorFilter(red)
250        canvas.drawRect4f(90, 10, 140, 60, paint);
251
252        const r = CanvasKit.ColorMatrix.rotated(0, .707, -.707);
253        const b = CanvasKit.ColorMatrix.rotated(2, .5, .866);
254        const s = CanvasKit.ColorMatrix.scaled(0.9, 1.5, 0.8, 0.8);
255        let cm = CanvasKit.ColorMatrix.concat(r, s);
256        cm = CanvasKit.ColorMatrix.concat(cm, b);
257        CanvasKit.ColorMatrix.postTranslate(cm, 20, 0, -10, 0);
258
259        const mat = CanvasKit.ColorFilter.MakeMatrix(cm);
260        const final = CanvasKit.ColorFilter.MakeCompose(mat, lerp);
261
262        paint.setColorFilter(final)
263        canvas.drawRect(CanvasKit.LTRBRect(10, 70, 140, 120), paint);
264
265        paint.delete();
266        blue.delete();
267        red.delete();
268        lerp.delete();
269        final.delete();
270    });
271
272    gm('blendmodes_canvas', (canvas) => {
273
274        const blendModeNames = Object.keys(CanvasKit.BlendMode).filter((key) => key !== 'values');
275
276        const PASTEL_MUSTARD_YELLOW = CanvasKit.Color(248, 213, 85, 1.0);
277        const PASTEL_SKY_BLUE = CanvasKit.Color(74, 174, 245, 1.0);
278
279        const shapePaint = new CanvasKit.Paint();
280        shapePaint.setColor(PASTEL_MUSTARD_YELLOW);
281        shapePaint.setAntiAlias(true);
282
283        const textPaint = new CanvasKit.Paint();
284        textPaint.setAntiAlias(true);
285
286        const textFont = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 10);
287
288        let x = 10;
289        let y = 20;
290        for (const blendModeName of blendModeNames) {
291            // Draw a checkerboard for each blend mode.
292            // Each checkerboard is labelled with a blendmode's name.
293            canvas.drawText(blendModeName, x, y - 5, textPaint, textFont);
294            drawCheckerboard(canvas, x, y, x + 80, y + 80);
295
296            // A blue square is drawn on to each checkerboard with yellow circle.
297            // In each checkerboard the blue square is drawn using a different blendmode.
298            const blendMode = CanvasKit.BlendMode[blendModeName];
299            canvas.drawOval(CanvasKit.LTRBRect(x + 5, y + 5, x + 55, y + 55), shapePaint);
300            drawRectangle(x + 30, y + 30, x + 70, y + 70, PASTEL_SKY_BLUE, blendMode);
301
302            x += 90;
303            if (x > 500) {
304                x = 10;
305                y += 110;
306            }
307        }
308
309        function drawCheckerboard(canvas, x1, y1, x2, y2) {
310            const CHECKERBOARD_SQUARE_SIZE = 5;
311            const GREY = CanvasKit.Color(220, 220, 220, 0.5);
312            // Draw black border and white background for checkerboard
313            drawRectangle(x1-1, y1-1, x2+1, y2+1, CanvasKit.BLACK);
314            drawRectangle(x1, y1, x2, y2, CanvasKit.WHITE);
315
316            // Draw checkerboard squares
317            const numberOfColumns = (x2 - x1) / CHECKERBOARD_SQUARE_SIZE;
318            const numberOfRows = (y2 - y1) / CHECKERBOARD_SQUARE_SIZE
319
320            for (let row = 0; row < numberOfRows; row++) {
321                for (let column = 0; column < numberOfColumns; column++) {
322                    const rowIsEven = row % 2 === 0;
323                    const columnIsEven = column % 2 === 0;
324
325                    if ((rowIsEven && !columnIsEven) || (!rowIsEven && columnIsEven)) {
326                        drawRectangle(
327                            x1 + CHECKERBOARD_SQUARE_SIZE * row,
328                            y1 + CHECKERBOARD_SQUARE_SIZE * column,
329                            Math.min(x1 + CHECKERBOARD_SQUARE_SIZE * row + CHECKERBOARD_SQUARE_SIZE, x2),
330                            Math.min(y1 + CHECKERBOARD_SQUARE_SIZE * column + CHECKERBOARD_SQUARE_SIZE, y2),
331                            GREY
332                        );
333                    }
334                }
335            }
336        }
337
338        function drawRectangle(x1, y1, x2, y2, color, blendMode=CanvasKit.BlendMode.srcOver) {
339            canvas.save();
340            canvas.clipRect(CanvasKit.LTRBRect(x1, y1, x2, y2), CanvasKit.ClipOp.Intersect, true);
341            canvas.drawColor(color, blendMode);
342            canvas.restore();
343        }
344    });
345
346    gm('colorfilters_malloc_canvas', (canvas) => {
347        const paint = new CanvasKit.Paint();
348
349        const src = [
350             0.8,   0.45,      2,   0,  20,
351            0.53, -0.918,  0.566,   0,   0,
352            0.53, -0.918, -0.566,   0, -10,
353               0,      0,      0, 0.8,   0,
354        ]
355        const colorObj = new CanvasKit.Malloc(Float32Array, 20);
356        const cm = colorObj.toTypedArray();
357        for (i in src) {
358            cm[i] = src[i];
359        }
360        // MakeMatrix will free the malloc'd array when it is done with it.
361        const final = CanvasKit.ColorFilter.MakeMatrix(cm);
362
363        paint.setColorFilter(final)
364        canvas.drawRect(CanvasKit.LTRBRect(10, 70, 140, 120), paint);
365
366        CanvasKit.Free(colorObj);
367        paint.delete();
368        final.delete();
369    });
370
371    gm('clips_canvas', (canvas) => {
372        const path = starPath(CanvasKit);
373        const paint = new CanvasKit.Paint();
374        paint.setColor(CanvasKit.BLUE);
375        const rrect = CanvasKit.RRectXY(CanvasKit.LTRBRect(300, 300, 500, 500), 40, 40);
376
377        canvas.save();
378        // draw magenta around the outside edge of an rrect.
379        canvas.clipRRect(rrect, CanvasKit.ClipOp.Difference, true);
380        canvas.drawColorComponents(250/255, 30/255, 240/255, 0.9, CanvasKit.BlendMode.SrcOver);
381        canvas.restore();
382
383        // draw grey inside of a star pattern, then the blue star on top
384        canvas.clipPath(path, CanvasKit.ClipOp.Intersect, false);
385        canvas.drawColorInt(CanvasKit.ColorAsInt(200, 200, 200, 255), CanvasKit.BlendMode.SrcOver);
386        canvas.drawPath(path, paint);
387
388        path.delete();
389        paint.delete();
390    });
391
392    // inspired by https://fiddle.skia.org/c/feb2a08bb09ede5309678d6a0ab3f981
393    gm('savelayer_rect_paint_canvas', (canvas) => {
394        const redPaint = new CanvasKit.Paint();
395        redPaint.setColor(CanvasKit.RED);
396        const solidBluePaint = new CanvasKit.Paint();
397        solidBluePaint.setColor(CanvasKit.BLUE);
398
399        const thirtyBluePaint = new CanvasKit.Paint();
400        thirtyBluePaint.setColor(CanvasKit.BLUE);
401        thirtyBluePaint.setAlphaf(0.3);
402
403        const alpha = new CanvasKit.Paint();
404        alpha.setAlphaf(0.3);
405
406        // Draw 4 solid red rectangles on the 0th layer.
407        canvas.drawRect(CanvasKit.LTRBRect(10, 10, 60, 60), redPaint);
408        canvas.drawRect(CanvasKit.LTRBRect(150, 10, 200, 60), redPaint);
409        canvas.drawRect(CanvasKit.LTRBRect(10, 70, 60, 120), redPaint);
410        canvas.drawRect(CanvasKit.LTRBRect(150, 70, 200, 120), redPaint);
411
412        // Draw 2 blue rectangles that overlap. One is solid, the other
413        // is 30% transparent. We should see purple from the right one,
414        // the left one overlaps the red because it is opaque.
415        canvas.drawRect(CanvasKit.LTRBRect(30, 10, 80, 60), solidBluePaint);
416        canvas.drawRect(CanvasKit.LTRBRect(170, 10, 220, 60), thirtyBluePaint);
417
418        // Save a new layer. When the 1st layer gets merged onto the
419        // 0th layer (i.e. when restore() is called), it will use the provided
420        // paint to do so. The provided paint is set to have 30% opacity, but
421        // it could also have things set like blend modes or image filters.
422        // The rectangle is just a hint, so I've set it to be the area that
423        // we actually draw in before restore is called. It could also be omitted,
424        // see the test below.
425        canvas.saveLayer(alpha, CanvasKit.LTRBRect(10, 10, 220, 180));
426
427        // Draw the same blue overlapping rectangles as before. Notice in the
428        // final output, we have two different shades of purple instead of the
429        // solid blue overwriting the red. This proves the opacity was applied.
430        canvas.drawRect(CanvasKit.LTRBRect(30, 70, 80, 120), solidBluePaint);
431        canvas.drawRect(CanvasKit.LTRBRect(170, 70, 220, 120), thirtyBluePaint);
432
433        // We draw two more sets of overlapping red and blue rectangles. Notice
434        // the solid blue overwrites the red. This proves that the opacity from
435        // the alpha paint isn't available when the drawing happens - it only
436        // matters when restore() is called.
437        canvas.drawRect(CanvasKit.LTRBRect(10, 130, 60, 180), redPaint);
438        canvas.drawRect(CanvasKit.LTRBRect(30, 130, 80, 180), solidBluePaint);
439
440        canvas.drawRect(CanvasKit.LTRBRect(150, 130, 200, 180), redPaint);
441        canvas.drawRect(CanvasKit.LTRBRect(170, 130, 220, 180), thirtyBluePaint);
442
443        canvas.restore();
444
445        redPaint.delete();
446        solidBluePaint.delete();
447        thirtyBluePaint.delete();
448        alpha.delete();
449    });
450
451    // identical to the test above, except the save layer only has the paint, not
452    // the rectangle.
453    gm('savelayer_paint_canvas', (canvas) => {
454        const redPaint = new CanvasKit.Paint();
455        redPaint.setColor(CanvasKit.RED);
456        const solidBluePaint = new CanvasKit.Paint();
457        solidBluePaint.setColor(CanvasKit.BLUE);
458
459        const thirtyBluePaint = new CanvasKit.Paint();
460        thirtyBluePaint.setColor(CanvasKit.BLUE);
461        thirtyBluePaint.setAlphaf(0.3);
462
463        const alpha = new CanvasKit.Paint();
464        alpha.setAlphaf(0.3);
465
466        // Draw 4 solid red rectangles on the 0th layer.
467        canvas.drawRect(CanvasKit.LTRBRect(10, 10, 60, 60), redPaint);
468        canvas.drawRect(CanvasKit.LTRBRect(150, 10, 200, 60), redPaint);
469        canvas.drawRect(CanvasKit.LTRBRect(10, 70, 60, 120), redPaint);
470        canvas.drawRect(CanvasKit.LTRBRect(150, 70, 200, 120), redPaint);
471
472        // Draw 2 blue rectangles that overlap. One is solid, the other
473        // is 30% transparent. We should see purple from the right one,
474        // the left one overlaps the red because it is opaque.
475        canvas.drawRect(CanvasKit.LTRBRect(30, 10, 80, 60), solidBluePaint);
476        canvas.drawRect(CanvasKit.LTRBRect(170, 10, 220, 60), thirtyBluePaint);
477
478        // Save a new layer. When the 1st layer gets merged onto the
479        // 0th layer (i.e. when restore() is called), it will use the provided
480        // paint to do so. The provided paint is set to have 30% opacity, but
481        // it could also have things set like blend modes or image filters.
482        canvas.saveLayerPaint(alpha);
483
484        // Draw the same blue overlapping rectangles as before. Notice in the
485        // final output, we have two different shades of purple instead of the
486        // solid blue overwriting the red. This proves the opacity was applied.
487        canvas.drawRect(CanvasKit.LTRBRect(30, 70, 80, 120), solidBluePaint);
488        canvas.drawRect(CanvasKit.LTRBRect(170, 70, 220, 120), thirtyBluePaint);
489
490        // We draw two more sets of overlapping red and blue rectangles. Notice
491        // the solid blue overwrites the red. This proves that the opacity from
492        // the alpha paint isn't available when the drawing happens - it only
493        // matters when restore() is called.
494        canvas.drawRect(CanvasKit.LTRBRect(10, 130, 60, 180), redPaint);
495        canvas.drawRect(CanvasKit.LTRBRect(30, 130, 80, 180), solidBluePaint);
496
497        canvas.drawRect(CanvasKit.LTRBRect(150, 130, 200, 180), redPaint);
498        canvas.drawRect(CanvasKit.LTRBRect(170, 130, 220, 180), thirtyBluePaint);
499
500        canvas.restore();
501
502        redPaint.delete();
503        solidBluePaint.delete();
504        thirtyBluePaint.delete();
505        alpha.delete();
506    });
507
508    gm('savelayerrec_canvas', (canvas) => {
509        // Note: fiddle.skia.org quietly draws a white background before doing
510        // other things, which is noticed in cases like this where we use saveLayer
511        // with the rec struct.
512        canvas.scale(8, 8);
513        const redPaint = new CanvasKit.Paint();
514        redPaint.setColor(CanvasKit.RED);
515        redPaint.setAntiAlias(true);
516        canvas.drawCircle(21, 21, 8, redPaint);
517
518        const bluePaint = new CanvasKit.Paint();
519        bluePaint.setColor(CanvasKit.BLUE);
520        canvas.drawCircle(31, 21, 8, bluePaint);
521
522        const blurIF = CanvasKit.ImageFilter.MakeBlur(8, 0.2, CanvasKit.TileMode.Decal, null);
523
524        const count = canvas.saveLayer(null, null, blurIF, 0);
525        expect(count).toEqual(1);
526        canvas.scale(1/4, 1/4);
527        canvas.drawCircle(125, 85, 8, redPaint);
528        canvas.restore();
529
530        blurIF.delete();
531        redPaint.delete();
532        bluePaint.delete();
533    });
534
535    gm('drawpoints_canvas', (canvas) => {
536        const paint = new CanvasKit.Paint();
537        paint.setAntiAlias(true);
538        paint.setStyle(CanvasKit.PaintStyle.Stroke);
539        paint.setStrokeWidth(10);
540        paint.setColor(CanvasKit.Color(153, 204, 162, 0.82));
541
542        const points = [32, 16, 48, 48, 16, 32];
543
544        const caps = [CanvasKit.StrokeCap.Round, CanvasKit.StrokeCap.Square,
545                      CanvasKit.StrokeCap.Butt];
546        const joins = [CanvasKit.StrokeJoin.Round, CanvasKit.StrokeJoin.Miter,
547                       CanvasKit.StrokeJoin.Bevel];
548        const modes = [CanvasKit.PointMode.Points, CanvasKit.PointMode.Lines,
549                       CanvasKit.PointMode.Polygon];
550
551        for (let i = 0; i < caps.length; i++) {
552            paint.setStrokeCap(caps[i]);
553            paint.setStrokeJoin(joins[i]);
554
555            for (const m of modes) {
556                canvas.drawPoints(m, points, paint);
557                canvas.translate(64, 0);
558            }
559            // Try with the malloc approach. Note that the drawPoints
560            // will free the pointer when done.
561            const mPointsObj = CanvasKit.Malloc(Float32Array, 3*2);
562            const mPoints = mPointsObj.toTypedArray();
563            mPoints.set([32, 16, 48, 48, 16, 32]);
564
565            // The obj from Malloc can be passed in instead of the typed array.
566            canvas.drawPoints(CanvasKit.PointMode.Polygon, mPointsObj, paint);
567            canvas.translate(-192, 64);
568            CanvasKit.Free(mPointsObj);
569        }
570
571        paint.delete();
572    });
573
574    gm('drawPoints_in_different_modes', (canvas) => {
575        // From https://bugs.chromium.org/p/skia/issues/detail?id=11012
576        const boxPaint = new CanvasKit.Paint();
577        boxPaint.setStyle(CanvasKit.PaintStyle.Stroke);
578        boxPaint.setStrokeWidth(1);
579
580        const paint = new CanvasKit.Paint();
581        paint.setStyle(CanvasKit.PaintStyle.Stroke);
582        paint.setStrokeWidth(5);
583        paint.setStrokeCap(CanvasKit.StrokeCap.Round);
584        paint.setColorInt(0xFF0000FF); // Blue
585        paint.setAntiAlias(true);
586
587        const points = Float32Array.of(40, 40, 80, 40, 120, 80, 160, 80);
588
589        canvas.drawRect(CanvasKit.LTRBRect(35, 35, 165, 85), boxPaint);
590        canvas.drawPoints(CanvasKit.PointMode.Points, points, paint);
591
592        canvas.translate(0, 50);
593        canvas.drawRect(CanvasKit.LTRBRect(35, 35, 165, 85), boxPaint);
594        canvas.drawPoints(CanvasKit.PointMode.Lines, points, paint);
595
596        canvas.translate(0, 50);
597        canvas.drawRect(CanvasKit.LTRBRect(35, 35, 165, 85), boxPaint);
598        canvas.drawPoints(CanvasKit.PointMode.Polygon, points, paint);
599
600        // The control version using drawPath
601        canvas.translate(0, 50);
602        canvas.drawRect(CanvasKit.LTRBRect(35, 35, 165, 85), boxPaint);
603        const path = new CanvasKit.Path();
604        path.moveTo(40, 40);
605        path.lineTo(80, 40);
606        path.lineTo(120, 80);
607        path.lineTo(160, 80);
608        paint.setColorInt(0xFFFF0000); // RED
609        canvas.drawPath(path, paint);
610
611        paint.delete();
612        path.delete();
613        boxPaint.delete();
614    });
615
616    gm('drawImageNine_canvas', (canvas, fetchedByteBuffers) => {
617        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
618        expect(img).toBeTruthy();
619        const paint = new CanvasKit.Paint();
620
621        canvas.drawImageNine(img, CanvasKit.LTRBiRect(40, 40, 400, 300),
622            CanvasKit.LTRBRect(5, 5, 300, 650), CanvasKit.FilterMode.Nearest, paint);
623        paint.delete();
624        img.delete();
625    }, '/assets/mandrill_512.png');
626
627        // This should be a nice, clear image.
628    gm('makeImageShaderCubic_canvas', (canvas, fetchedByteBuffers) => {
629        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
630        expect(img).toBeTruthy();
631        const paint = new CanvasKit.Paint();
632        const shader = img.makeShaderCubic(CanvasKit.TileMode.Decal, CanvasKit.TileMode.Clamp,
633                                           1/3 /*B*/, 1/3 /*C*/,
634                                           CanvasKit.Matrix.rotated(0.1));
635        paint.setShader(shader);
636
637        canvas.drawPaint(paint);
638        paint.delete();
639        shader.delete();
640        img.delete();
641    }, '/assets/mandrill_512.png');
642
643    // This will look more blocky than the version above.
644    gm('makeImageShaderOptions_canvas', (canvas, fetchedByteBuffers) => {
645        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
646        expect(img).toBeTruthy();
647        const imgWithMipMap = img.makeCopyWithDefaultMipmaps();
648        const paint = new CanvasKit.Paint();
649        const shader = imgWithMipMap.makeShaderOptions(CanvasKit.TileMode.Decal,
650                                                       CanvasKit.TileMode.Clamp,
651                                                       CanvasKit.FilterMode.Nearest,
652                                                       CanvasKit.MipmapMode.Linear,
653                                                       CanvasKit.Matrix.rotated(0.1));
654        paint.setShader(shader);
655
656        canvas.drawPaint(paint);
657        paint.delete();
658        shader.delete();
659        img.delete();
660        imgWithMipMap.delete();
661    }, '/assets/mandrill_512.png');
662
663    gm('drawvertices_canvas', (canvas) => {
664        const paint = new CanvasKit.Paint();
665        paint.setAntiAlias(true);
666
667        const points = [0, 0,  250, 0,  100, 100,  0, 250];
668        // 2d float color array
669        const colors = [CanvasKit.RED, CanvasKit.BLUE,
670                        CanvasKit.YELLOW, CanvasKit.CYAN];
671        const vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TriangleFan,
672            points, null /*textureCoordinates*/, colors, false /*isVolatile*/);
673
674        const bounds = vertices.bounds();
675        expect(bounds).toEqual(CanvasKit.LTRBRect(0, 0, 250, 250));
676
677        canvas.drawVertices(vertices, CanvasKit.BlendMode.Dst, paint);
678        vertices.delete();
679        paint.delete();
680    });
681
682    gm('drawvertices_canvas_flat_floats', (canvas) => {
683        const paint = new CanvasKit.Paint();
684        paint.setAntiAlias(true);
685
686        const points = [0, 0,  250, 0,  100, 100,  0, 250];
687        // 1d float color array
688        const colors = Float32Array.of(...CanvasKit.RED, ...CanvasKit.BLUE,
689                                       ...CanvasKit.YELLOW, ...CanvasKit.CYAN);
690        const vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TriangleFan,
691            points, null /*textureCoordinates*/, colors, false /*isVolatile*/);
692
693        const bounds = vertices.bounds();
694        expect(bounds).toEqual(CanvasKit.LTRBRect(0, 0, 250, 250));
695
696        canvas.drawVertices(vertices, CanvasKit.BlendMode.Dst, paint);
697        vertices.delete();
698        paint.delete();
699    });
700
701    gm('drawvertices_texture_canvas', (canvas, fetchedByteBuffers) => {
702        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
703
704        const paint = new CanvasKit.Paint();
705        paint.setAntiAlias(true);
706
707        const points = [
708             70, 170,   40, 90,  130, 150,  100, 50,
709            225, 150,  225, 60,  310, 180,  330, 100,
710        ];
711        const textureCoordinates = [
712              0, 240,    0, 0,   80, 240,   80, 0,
713            160, 240,  160, 0,  240, 240,  240, 0,
714        ];
715        const vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TrianglesStrip,
716            points, textureCoordinates, null /* colors */, false /*isVolatile*/);
717
718        const shader = img.makeShaderCubic(CanvasKit.TileMode.Repeat, CanvasKit.TileMode.Mirror,
719            1/3 /*B*/, 1/3 /*C*/,);
720        paint.setShader(shader);
721        canvas.drawVertices(vertices, CanvasKit.BlendMode.Src, paint);
722
723        shader.delete();
724        vertices.delete();
725        paint.delete();
726        img.delete();
727    }, '/assets/brickwork-texture.jpg');
728
729    it('can change the 3x3 matrix on the canvas and read it back', () => {
730        const canvas = new CanvasKit.Canvas();
731
732        let matr = canvas.getTotalMatrix();
733        expect(matr).toEqual(CanvasKit.Matrix.identity());
734
735        // This fills the internal _scratch4x4MatrixPtr with garbage (aka sentinel) values to
736        // make sure the 3x3 matrix properly sets these to 0 when it uses the same buffer.
737        canvas.save();
738        const garbageMatrix = new Float32Array(16);
739        garbageMatrix.fill(-3);
740        canvas.concat(garbageMatrix);
741        canvas.restore();
742
743        canvas.concat(CanvasKit.Matrix.rotated(Math.PI/4));
744        const d = new DOMMatrix().translate(20, 10);
745        canvas.concat(d);
746
747        matr = canvas.getTotalMatrix();
748        const expected = CanvasKit.Matrix.multiply(
749            CanvasKit.Matrix.rotated(Math.PI/4),
750            CanvasKit.Matrix.translated(20, 10)
751        );
752        expect3x3MatricesToMatch(expected, matr);
753
754        // The 3x3 should be expanded into a 4x4, with identity in the 3rd row and column.
755        matr = canvas.getLocalToDevice();
756        expect4x4MatricesToMatch([
757            0.707106, -0.707106, 0,  7.071067,
758            0.707106,  0.707106, 0, 21.213203,
759            0       ,  0       , 1,  0       ,
760            0       ,  0       , 0,  1       ], matr);
761    });
762
763    it('can accept a 3x2 matrix', () => {
764        const canvas = new CanvasKit.Canvas();
765
766        let matr = canvas.getTotalMatrix();
767        expect(matr).toEqual(CanvasKit.Matrix.identity());
768
769        // This fills the internal _scratch4x4MatrixPtr with garbage (aka sentinel) values to
770        // make sure the 3x2 matrix properly sets these to 0 when it uses the same buffer.
771        canvas.save();
772        const garbageMatrix = new Float32Array(16);
773        garbageMatrix.fill(-3);
774        canvas.concat(garbageMatrix);
775        canvas.restore();
776
777        canvas.concat([1.4, -0.2, 12,
778                       0.2,  1.4, 24]);
779
780        matr = canvas.getTotalMatrix();
781        const expected = [1.4, -0.2, 12,
782                          0.2,  1.4, 24,
783                            0,    0,  1];
784        expect3x3MatricesToMatch(expected, matr);
785
786        // The 3x2 should be expanded into a 4x4, with identity in the 3rd row and column
787        // and the perspective filled in.
788        matr = canvas.getLocalToDevice();
789        expect4x4MatricesToMatch([
790            1.4, -0.2, 0, 12,
791            0.2,  1.4, 0, 24,
792            0  ,  0  , 1,  0,
793            0  ,  0  , 0,  1], matr);
794    });
795
796    it('can change the 4x4 matrix on the canvas and read it back', () => {
797        const canvas = new CanvasKit.Canvas();
798
799        let matr = canvas.getLocalToDevice();
800        expect(matr).toEqual(CanvasKit.M44.identity());
801
802        canvas.concat(CanvasKit.M44.rotated([0, 1, 0], Math.PI/4));
803        canvas.concat(CanvasKit.M44.rotated([1, 0, 1], Math.PI/8));
804
805        const expected = CanvasKit.M44.multiply(
806          CanvasKit.M44.rotated([0, 1, 0], Math.PI/4),
807          CanvasKit.M44.rotated([1, 0, 1], Math.PI/8),
808        );
809
810        expect4x4MatricesToMatch(expected, canvas.getLocalToDevice());
811        // TODO(kjlubick) add test for DOMMatrix
812        // TODO(nifong) add more involved test for camera-related math.
813    });
814
815    it('can change the device clip bounds to the canvas and read it back', () => {
816        // We need to use the Canvas constructor with a width/height or there is no maximum
817        // clip area, and all clipping will result in a clip of [0, 0, 0, 0]
818        const canvas = new CanvasKit.Canvas(300, 400);
819        let clip = canvas.getDeviceClipBounds();
820        expect(clip).toEqual(Int32Array.of(0, 0, 300, 400));
821
822        canvas.clipRect(CanvasKit.LTRBRect(10, 20, 30, 45), CanvasKit.ClipOp.Intersect, false);
823        canvas.getDeviceClipBounds(clip);
824        expect(clip).toEqual(Int32Array.of(10, 20, 30, 45));
825    });
826
827    gm('concat_with4x4_canvas', (canvas) => {
828        const path = starPath(CanvasKit, CANVAS_WIDTH/2, CANVAS_HEIGHT/2);
829        const paint = new CanvasKit.Paint();
830        paint.setAntiAlias(true);
831
832        // Rotate it a bit on all 3 major axis, centered on the screen.
833        // To play with rotations, see https://jsfiddle.skia.org/canvaskit/0525300405796aa87c3b84cc0d5748516fca0045d7d6d9c7840710ab771edcd4
834        const turn = CanvasKit.M44.multiply(
835          CanvasKit.M44.translated([CANVAS_WIDTH/2, 0, 0]),
836          CanvasKit.M44.rotated([1, 0, 0], Math.PI/3),
837          CanvasKit.M44.rotated([0, 1, 0], Math.PI/4),
838          CanvasKit.M44.rotated([0, 0, 1], Math.PI/16),
839          CanvasKit.M44.translated([-CANVAS_WIDTH/2, 0, 0]),
840        );
841        canvas.concat(turn);
842
843        // Draw some stripes to help the eye detect the turn
844        const stripeWidth = 10;
845        paint.setColor(CanvasKit.BLACK);
846        for (let i = 0; i < CANVAS_WIDTH; i += 2*stripeWidth) {
847            canvas.drawRect(CanvasKit.LTRBRect(i, 0, i + stripeWidth, CANVAS_HEIGHT), paint);
848        }
849
850        paint.setColor(CanvasKit.YELLOW);
851        canvas.drawPath(path, paint);
852        paint.delete();
853        path.delete();
854    });
855});
856
857const expect3x3MatricesToMatch = (expected, actual) => {
858    expect(expected.length).toEqual(9);
859    expect(actual.length).toEqual(9);
860    for (let i = 0; i < expected.length; i++) {
861        expect(expected[i]).toBeCloseTo(actual[i], 5);
862    }
863};
864
865const expect4x4MatricesToMatch = (expected, actual) => {
866    expect(expected.length).toEqual(16);
867    expect(actual.length).toEqual(16);
868    for (let i = 0; i < expected.length; i++) {
869        expect(expected[i]).toBeCloseTo(actual[i], 5);
870    }
871};
872