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