xref: /aosp_15_r20/external/skia/modules/canvaskit/tests/canvas_test.js (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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('savelayerrec_canvas_backdrop_tilemode', (canvas) => {
536        // Note: fiddle.skia.org quietly draws a white background before doing
537        // other things, which is noticed in cases like this where we use saveLayer
538        // with the rec struct.
539        canvas.scale(8, 8);
540        const redPaint = new CanvasKit.Paint();
541        redPaint.setColor(CanvasKit.RED);
542        redPaint.setAntiAlias(true);
543        canvas.drawCircle(21, 21, 8, redPaint);
544
545        const bluePaint = new CanvasKit.Paint();
546        bluePaint.setColor(CanvasKit.BLUE);
547        canvas.drawCircle(31, 21, 8, bluePaint);
548
549        const blurIF = CanvasKit.ImageFilter.MakeBlur(8, 0.2, CanvasKit.TileMode.Decal, null);
550
551        const count = canvas.saveLayer(null, null, blurIF, 0, CanvasKit.TileMode.Decal);
552        expect(count).toEqual(1);
553        canvas.scale(1/4, 1/4);
554        canvas.drawCircle(125, 85, 8, redPaint);
555        canvas.restore();
556
557        blurIF.delete();
558        redPaint.delete();
559        bluePaint.delete();
560    });
561
562    gm('drawpoints_canvas', (canvas) => {
563        const paint = new CanvasKit.Paint();
564        paint.setAntiAlias(true);
565        paint.setStyle(CanvasKit.PaintStyle.Stroke);
566        paint.setStrokeWidth(10);
567        paint.setColor(CanvasKit.Color(153, 204, 162, 0.82));
568
569        const points = [32, 16, 48, 48, 16, 32];
570
571        const caps = [CanvasKit.StrokeCap.Round, CanvasKit.StrokeCap.Square,
572                      CanvasKit.StrokeCap.Butt];
573        const joins = [CanvasKit.StrokeJoin.Round, CanvasKit.StrokeJoin.Miter,
574                       CanvasKit.StrokeJoin.Bevel];
575        const modes = [CanvasKit.PointMode.Points, CanvasKit.PointMode.Lines,
576                       CanvasKit.PointMode.Polygon];
577
578        for (let i = 0; i < caps.length; i++) {
579            paint.setStrokeCap(caps[i]);
580            paint.setStrokeJoin(joins[i]);
581
582            for (const m of modes) {
583                canvas.drawPoints(m, points, paint);
584                canvas.translate(64, 0);
585            }
586            // Try with the malloc approach. Note that the drawPoints
587            // will free the pointer when done.
588            const mPointsObj = CanvasKit.Malloc(Float32Array, 3*2);
589            const mPoints = mPointsObj.toTypedArray();
590            mPoints.set([32, 16, 48, 48, 16, 32]);
591
592            // The obj from Malloc can be passed in instead of the typed array.
593            canvas.drawPoints(CanvasKit.PointMode.Polygon, mPointsObj, paint);
594            canvas.translate(-192, 64);
595            CanvasKit.Free(mPointsObj);
596        }
597
598        paint.delete();
599    });
600
601    gm('drawPoints_in_different_modes', (canvas) => {
602        // From https://bugs.chromium.org/p/skia/issues/detail?id=11012
603        const boxPaint = new CanvasKit.Paint();
604        boxPaint.setStyle(CanvasKit.PaintStyle.Stroke);
605        boxPaint.setStrokeWidth(1);
606
607        const paint = new CanvasKit.Paint();
608        paint.setStyle(CanvasKit.PaintStyle.Stroke);
609        paint.setStrokeWidth(5);
610        paint.setStrokeCap(CanvasKit.StrokeCap.Round);
611        paint.setColorInt(0xFF0000FF); // Blue
612        paint.setAntiAlias(true);
613
614        const points = Float32Array.of(40, 40, 80, 40, 120, 80, 160, 80);
615
616        canvas.drawRect(CanvasKit.LTRBRect(35, 35, 165, 85), boxPaint);
617        canvas.drawPoints(CanvasKit.PointMode.Points, points, paint);
618
619        canvas.translate(0, 50);
620        canvas.drawRect(CanvasKit.LTRBRect(35, 35, 165, 85), boxPaint);
621        canvas.drawPoints(CanvasKit.PointMode.Lines, points, paint);
622
623        canvas.translate(0, 50);
624        canvas.drawRect(CanvasKit.LTRBRect(35, 35, 165, 85), boxPaint);
625        canvas.drawPoints(CanvasKit.PointMode.Polygon, points, paint);
626
627        // The control version using drawPath
628        canvas.translate(0, 50);
629        canvas.drawRect(CanvasKit.LTRBRect(35, 35, 165, 85), boxPaint);
630        const path = new CanvasKit.Path();
631        path.moveTo(40, 40);
632        path.lineTo(80, 40);
633        path.lineTo(120, 80);
634        path.lineTo(160, 80);
635        paint.setColorInt(0xFFFF0000); // RED
636        canvas.drawPath(path, paint);
637
638        paint.delete();
639        path.delete();
640        boxPaint.delete();
641    });
642
643    gm('drawImageNine_canvas', (canvas, fetchedByteBuffers) => {
644        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
645        expect(img).toBeTruthy();
646        const paint = new CanvasKit.Paint();
647
648        canvas.drawImageNine(img, CanvasKit.LTRBiRect(40, 40, 400, 300),
649            CanvasKit.LTRBRect(5, 5, 300, 650), CanvasKit.FilterMode.Nearest, paint);
650        paint.delete();
651        img.delete();
652    }, '/assets/mandrill_512.png');
653
654        // This should be a nice, clear image.
655    gm('makeImageShaderCubic_canvas', (canvas, fetchedByteBuffers) => {
656        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
657        expect(img).toBeTruthy();
658        const paint = new CanvasKit.Paint();
659        const shader = img.makeShaderCubic(CanvasKit.TileMode.Decal, CanvasKit.TileMode.Clamp,
660                                           1/3 /*B*/, 1/3 /*C*/,
661                                           CanvasKit.Matrix.rotated(0.1));
662        paint.setShader(shader);
663
664        canvas.drawPaint(paint);
665        paint.delete();
666        shader.delete();
667        img.delete();
668    }, '/assets/mandrill_512.png');
669
670    // This will look more blocky than the version above.
671    gm('makeImageShaderOptions_canvas', (canvas, fetchedByteBuffers) => {
672        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
673        expect(img).toBeTruthy();
674        const imgWithMipMap = img.makeCopyWithDefaultMipmaps();
675        const paint = new CanvasKit.Paint();
676        const shader = imgWithMipMap.makeShaderOptions(CanvasKit.TileMode.Decal,
677                                                       CanvasKit.TileMode.Clamp,
678                                                       CanvasKit.FilterMode.Nearest,
679                                                       CanvasKit.MipmapMode.Linear,
680                                                       CanvasKit.Matrix.rotated(0.1));
681        paint.setShader(shader);
682
683        canvas.drawPaint(paint);
684        paint.delete();
685        shader.delete();
686        img.delete();
687        imgWithMipMap.delete();
688    }, '/assets/mandrill_512.png');
689
690    gm('drawvertices_canvas', (canvas) => {
691        const paint = new CanvasKit.Paint();
692        paint.setAntiAlias(true);
693
694        const points = [0, 0,  250, 0,  100, 100,  0, 250];
695        // 2d float color array
696        const colors = [CanvasKit.RED, CanvasKit.BLUE,
697                        CanvasKit.YELLOW, CanvasKit.CYAN];
698        const vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TriangleFan,
699            points, null /*textureCoordinates*/, colors, false /*isVolatile*/);
700
701        const bounds = vertices.bounds();
702        expect(bounds).toEqual(CanvasKit.LTRBRect(0, 0, 250, 250));
703
704        canvas.drawVertices(vertices, CanvasKit.BlendMode.Dst, paint);
705        vertices.delete();
706        paint.delete();
707    });
708
709    gm('drawvertices_canvas_flat_floats', (canvas) => {
710        const paint = new CanvasKit.Paint();
711        paint.setAntiAlias(true);
712
713        const points = [0, 0,  250, 0,  100, 100,  0, 250];
714        // 1d float color array
715        const colors = Float32Array.of(...CanvasKit.RED, ...CanvasKit.BLUE,
716                                       ...CanvasKit.YELLOW, ...CanvasKit.CYAN);
717        const vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TriangleFan,
718            points, null /*textureCoordinates*/, colors, false /*isVolatile*/);
719
720        const bounds = vertices.bounds();
721        expect(bounds).toEqual(CanvasKit.LTRBRect(0, 0, 250, 250));
722
723        canvas.drawVertices(vertices, CanvasKit.BlendMode.Dst, paint);
724        vertices.delete();
725        paint.delete();
726    });
727
728    gm('drawvertices_texture_canvas', (canvas, fetchedByteBuffers) => {
729        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
730
731        const paint = new CanvasKit.Paint();
732        paint.setAntiAlias(true);
733
734        const points = [
735             70, 170,   40, 90,  130, 150,  100, 50,
736            225, 150,  225, 60,  310, 180,  330, 100,
737        ];
738        const textureCoordinates = [
739              0, 240,    0, 0,   80, 240,   80, 0,
740            160, 240,  160, 0,  240, 240,  240, 0,
741        ];
742        const vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TrianglesStrip,
743            points, textureCoordinates, null /* colors */, false /*isVolatile*/);
744
745        const shader = img.makeShaderCubic(CanvasKit.TileMode.Repeat, CanvasKit.TileMode.Mirror,
746            1/3 /*B*/, 1/3 /*C*/,);
747        paint.setShader(shader);
748        canvas.drawVertices(vertices, CanvasKit.BlendMode.Src, paint);
749
750        shader.delete();
751        vertices.delete();
752        paint.delete();
753        img.delete();
754    }, '/assets/brickwork-texture.jpg');
755
756    it('can change the 3x3 matrix on the canvas and read it back', () => {
757        const canvas = new CanvasKit.Canvas();
758
759        let matr = canvas.getTotalMatrix();
760        expect(matr).toEqual(CanvasKit.Matrix.identity());
761
762        // This fills the internal _scratch4x4MatrixPtr with garbage (aka sentinel) values to
763        // make sure the 3x3 matrix properly sets these to 0 when it uses the same buffer.
764        canvas.save();
765        const garbageMatrix = new Float32Array(16);
766        garbageMatrix.fill(-3);
767        canvas.concat(garbageMatrix);
768        canvas.restore();
769
770        canvas.concat(CanvasKit.Matrix.rotated(Math.PI/4));
771        const d = new DOMMatrix().translate(20, 10);
772        canvas.concat(d);
773
774        matr = canvas.getTotalMatrix();
775        const expected = CanvasKit.Matrix.multiply(
776            CanvasKit.Matrix.rotated(Math.PI/4),
777            CanvasKit.Matrix.translated(20, 10)
778        );
779        expect3x3MatricesToMatch(expected, matr);
780
781        // The 3x3 should be expanded into a 4x4, with identity in the 3rd row and column.
782        matr = canvas.getLocalToDevice();
783        expect4x4MatricesToMatch([
784            0.707106, -0.707106, 0,  7.071067,
785            0.707106,  0.707106, 0, 21.213203,
786            0       ,  0       , 1,  0       ,
787            0       ,  0       , 0,  1       ], matr);
788    });
789
790    it('can quickly tell if a rect is in the current clip region', () => {
791      const canvas = new CanvasKit.Canvas(200, 200);
792
793      canvas.save();
794      const rejectWithNoClip = canvas.quickReject(CanvasKit.LTRBRect(10, 10, 20, 20));
795      expect(rejectWithNoClip).toBeFalse();
796      canvas.restore();
797
798      canvas.save();
799      canvas.clipRect(CanvasKit.LTRBRect(10, 10, 20, 20), CanvasKit.ClipOp.Intersect, false);
800      const rejectPartiallyInsideClip = canvas.quickReject(CanvasKit.LTRBRect(15, 15, 25, 25));
801      expect(rejectPartiallyInsideClip).toBeFalse();
802      const rejectEntirelyOutsideClip = canvas.quickReject(CanvasKit.LTRBRect(30, 30, 50, 50));
803      expect(rejectEntirelyOutsideClip).toBeTrue();
804      canvas.restore();
805    });
806
807    it('can accept a 3x2 matrix', () => {
808        const canvas = new CanvasKit.Canvas();
809
810        let matr = canvas.getTotalMatrix();
811        expect(matr).toEqual(CanvasKit.Matrix.identity());
812
813        // This fills the internal _scratch4x4MatrixPtr with garbage (aka sentinel) values to
814        // make sure the 3x2 matrix properly sets these to 0 when it uses the same buffer.
815        canvas.save();
816        const garbageMatrix = new Float32Array(16);
817        garbageMatrix.fill(-3);
818        canvas.concat(garbageMatrix);
819        canvas.restore();
820
821        canvas.concat([1.4, -0.2, 12,
822                       0.2,  1.4, 24]);
823
824        matr = canvas.getTotalMatrix();
825        const expected = [1.4, -0.2, 12,
826                          0.2,  1.4, 24,
827                            0,    0,  1];
828        expect3x3MatricesToMatch(expected, matr);
829
830        // The 3x2 should be expanded into a 4x4, with identity in the 3rd row and column
831        // and the perspective filled in.
832        matr = canvas.getLocalToDevice();
833        expect4x4MatricesToMatch([
834            1.4, -0.2, 0, 12,
835            0.2,  1.4, 0, 24,
836            0  ,  0  , 1,  0,
837            0  ,  0  , 0,  1], matr);
838    });
839
840    it('can change the 4x4 matrix on the canvas and read it back', () => {
841        const canvas = new CanvasKit.Canvas();
842
843        let matr = canvas.getLocalToDevice();
844        expect(matr).toEqual(CanvasKit.M44.identity());
845
846        canvas.concat(CanvasKit.M44.rotated([0, 1, 0], Math.PI/4));
847        canvas.concat(CanvasKit.M44.rotated([1, 0, 1], Math.PI/8));
848
849        const expected = CanvasKit.M44.multiply(
850          CanvasKit.M44.rotated([0, 1, 0], Math.PI/4),
851          CanvasKit.M44.rotated([1, 0, 1], Math.PI/8),
852        );
853
854        expect4x4MatricesToMatch(expected, canvas.getLocalToDevice());
855        // TODO(kjlubick) add test for DOMMatrix
856        // TODO(nifong) add more involved test for camera-related math.
857    });
858
859    it('can change the device clip bounds to the canvas and read it back', () => {
860        // We need to use the Canvas constructor with a width/height or there is no maximum
861        // clip area, and all clipping will result in a clip of [0, 0, 0, 0]
862        const canvas = new CanvasKit.Canvas(300, 400);
863        let clip = canvas.getDeviceClipBounds();
864        expect(clip).toEqual(Int32Array.of(0, 0, 300, 400));
865
866        canvas.clipRect(CanvasKit.LTRBRect(10, 20, 30, 45), CanvasKit.ClipOp.Intersect, false);
867        canvas.getDeviceClipBounds(clip);
868        expect(clip).toEqual(Int32Array.of(10, 20, 30, 45));
869    });
870
871    gm('concat_with4x4_canvas', (canvas) => {
872        const path = starPath(CanvasKit, CANVAS_WIDTH/2, CANVAS_HEIGHT/2);
873        const paint = new CanvasKit.Paint();
874        paint.setAntiAlias(true);
875
876        // Rotate it a bit on all 3 major axis, centered on the screen.
877        // To play with rotations, see https://jsfiddle.skia.org/canvaskit/0525300405796aa87c3b84cc0d5748516fca0045d7d6d9c7840710ab771edcd4
878        const turn = CanvasKit.M44.multiply(
879          CanvasKit.M44.translated([CANVAS_WIDTH/2, 0, 0]),
880          CanvasKit.M44.rotated([1, 0, 0], Math.PI/3),
881          CanvasKit.M44.rotated([0, 1, 0], Math.PI/4),
882          CanvasKit.M44.rotated([0, 0, 1], Math.PI/16),
883          CanvasKit.M44.translated([-CANVAS_WIDTH/2, 0, 0]),
884        );
885        canvas.concat(turn);
886
887        // Draw some stripes to help the eye detect the turn
888        const stripeWidth = 10;
889        paint.setColor(CanvasKit.BLACK);
890        for (let i = 0; i < CANVAS_WIDTH; i += 2*stripeWidth) {
891            canvas.drawRect(CanvasKit.LTRBRect(i, 0, i + stripeWidth, CANVAS_HEIGHT), paint);
892        }
893
894        paint.setColor(CanvasKit.YELLOW);
895        canvas.drawPath(path, paint);
896        paint.delete();
897        path.delete();
898    });
899});
900
901const expect3x3MatricesToMatch = (expected, actual) => {
902    expect(expected.length).toEqual(9);
903    expect(actual.length).toEqual(9);
904    for (let i = 0; i < expected.length; i++) {
905        expect(expected[i]).toBeCloseTo(actual[i], 5);
906    }
907};
908
909const expect4x4MatricesToMatch = (expected, actual) => {
910    expect(expected.length).toEqual(16);
911    expect(actual.length).toEqual(16);
912    for (let i = 0; i < expected.length; i++) {
913        expect(expected[i]).toBeCloseTo(actual[i], 5);
914    }
915};
916