xref: /aosp_15_r20/external/skia/modules/canvaskit/tests/core_test.js (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1describe('Core 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('picture_test', (canvas) => {
18        const spr = new CanvasKit.PictureRecorder();
19        const bounds = CanvasKit.LTRBRect(0, 0, 400, 120);
20        const rcanvas = spr.beginRecording(bounds, true);
21        const paint = new CanvasKit.Paint();
22        paint.setStrokeWidth(2.0);
23        paint.setAntiAlias(true);
24        paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
25        paint.setStyle(CanvasKit.PaintStyle.Stroke);
26
27        rcanvas.drawRRect(CanvasKit.RRectXY([5, 35, 45, 80], 15, 10), paint);
28
29        const font = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 20);
30        rcanvas.drawText('this picture has a round rect', 5, 100, paint, font);
31        const pic = spr.finishRecordingAsPicture();
32        const cullRect = pic.cullRect();
33        const approxBytesUsed = pic.approximateBytesUsed();
34        expect(approxBytesUsed).toBeGreaterThan(0);
35        expect(cullRect[0]).toBeCloseTo(0);
36        expect(cullRect[1]).toBeCloseTo(31);
37        expect(cullRect[2]).toBeCloseTo(357.84);
38        expect(cullRect[3]).toBeCloseTo(109.42);
39        spr.delete();
40        paint.delete();
41
42        canvas.drawPicture(pic);
43        const paint2 = new CanvasKit.Paint();
44        paint2.setColor(CanvasKit.RED);
45        paint2.setStyle(CanvasKit.PaintStyle.Stroke);
46        canvas.drawRect(bounds, paint2);
47
48        const bytes = pic.serialize();
49        expect(bytes).toBeTruthy();
50
51
52        const matr = CanvasKit.Matrix.scaled(0.33, 0.33);
53        // Give a 5 pixel margin between the original content.
54        const tileRect = CanvasKit.LTRBRect(-5, -5, 405, 125);
55        const shader = pic.makeShader(CanvasKit.TileMode.Mirror, CanvasKit.TileMode.Mirror,
56        CanvasKit.FilterMode.Linear, matr, tileRect);
57        paint2.setStyle(CanvasKit.PaintStyle.Fill);
58        paint2.setShader(shader);
59        canvas.drawRect(CanvasKit.LTRBRect(0, 150, CANVAS_WIDTH, CANVAS_HEIGHT), paint2);
60
61        paint2.delete();
62        shader.delete();
63        pic.delete();
64    });
65
66    const uIntColorToCanvasKitColor = (c) => {
67        return CanvasKit.Color(
68         (c >> 16) & 0xFF,
69         (c >>  8) & 0xFF,
70         (c >>  0) & 0xFF,
71        ((c >> 24) & 0xFF) / 255
72      );
73    };
74
75    it('can compute tonal colors', () => {
76        const input = {
77            ambient: CanvasKit.BLUE,
78            spot: CanvasKit.RED,
79        };
80        const out = CanvasKit.computeTonalColors(input);
81        expect(new Float32Array(out.ambient)).toEqual(CanvasKit.BLACK);
82        const expectedSpot = [0.173, 0, 0, 0.969];
83        expect(out.spot.length).toEqual(4);
84        expect(out.spot[0]).toBeCloseTo(expectedSpot[0], 3);
85        expect(out.spot[1]).toBeCloseTo(expectedSpot[1], 3);
86        expect(out.spot[2]).toBeCloseTo(expectedSpot[2], 3);
87        expect(out.spot[3]).toBeCloseTo(expectedSpot[3], 3);
88    });
89
90    it('can compute tonal colors with malloced values', () => {
91        const ambientColor = CanvasKit.Malloc(Float32Array, 4);
92        ambientColor.toTypedArray().set(CanvasKit.BLUE);
93        const spotColor = CanvasKit.Malloc(Float32Array, 4);
94        spotColor.toTypedArray().set(CanvasKit.RED);
95        const input = {
96            ambient: ambientColor,
97            spot: spotColor,
98        };
99        const out = CanvasKit.computeTonalColors(input);
100        expect(new Float32Array(out.ambient)).toEqual(CanvasKit.BLACK);
101        const expectedSpot = [0.173, 0, 0, 0.969];
102        expect(out.spot.length).toEqual(4);
103        expect(out.spot[0]).toBeCloseTo(expectedSpot[0], 3);
104        expect(out.spot[1]).toBeCloseTo(expectedSpot[1], 3);
105        expect(out.spot[2]).toBeCloseTo(expectedSpot[2], 3);
106        expect(out.spot[3]).toBeCloseTo(expectedSpot[3], 3);
107    });
108
109    // This helper is used for all the MakeImageFromEncoded tests.
110    // TODO(kjlubick): rewrite this and callers to use gm
111    function decodeAndDrawSingleFrameImage(imgName, goldName, done) {
112        const imgPromise = fetch(imgName)
113            .then((response) => response.arrayBuffer());
114        Promise.all([imgPromise, EverythingLoaded]).then((values) => {
115            const imgData = values[0];
116            expect(imgData).toBeTruthy();
117            catchException(done, () => {
118                let img = CanvasKit.MakeImageFromEncoded(imgData);
119                expect(img).toBeTruthy();
120                const surface = CanvasKit.MakeCanvasSurface('test');
121                expect(surface).toBeTruthy('Could not make surface');
122                if (!surface) {
123                    done();
124                    return;
125                }
126                const canvas = surface.getCanvas();
127                let paint = new CanvasKit.Paint();
128                canvas.drawImage(img, 0, 0, paint);
129
130                paint.delete();
131                img.delete();
132
133                reportSurface(surface, goldName, done);
134            })();
135        });
136    }
137
138    it('can decode and draw a png', (done) => {
139        decodeAndDrawSingleFrameImage('/assets/mandrill_512.png', 'drawImage_png', done);
140    });
141
142    it('can decode and draw a jpg', (done) => {
143        decodeAndDrawSingleFrameImage('/assets/mandrill_h1v1.jpg', 'drawImage_jpg', done);
144    });
145
146    it('can decode and draw a (still) gif', (done) => {
147        decodeAndDrawSingleFrameImage('/assets/flightAnim.gif', 'drawImage_gif', done);
148    });
149
150    it('can decode and draw a still webp', (done) => {
151        decodeAndDrawSingleFrameImage('/assets/color_wheel.webp', 'drawImage_webp', done);
152    });
153
154   it('can readPixels from an Image', (done) => {
155        const imgPromise = fetch('/assets/mandrill_512.png')
156            .then((response) => response.arrayBuffer());
157        Promise.all([imgPromise, EverythingLoaded]).then((values) => {
158            const imgData = values[0];
159            expect(imgData).toBeTruthy();
160            catchException(done, () => {
161                let img = CanvasKit.MakeImageFromEncoded(imgData);
162                expect(img).toBeTruthy();
163                const imageInfo = {
164                    alphaType: CanvasKit.AlphaType.Unpremul,
165                    colorType: CanvasKit.ColorType.RGBA_8888,
166                    colorSpace: CanvasKit.ColorSpace.SRGB,
167                    width: img.width(),
168                    height: img.height(),
169                };
170                const rowBytes = 4 * img.width();
171
172                const pixels = img.readPixels(0, 0, imageInfo);
173                // We know the image is 512 by 512 pixels in size, each pixel
174                // requires 4 bytes (R, G, B, A).
175                expect(pixels.length).toEqual(512 * 512 * 4);
176
177                // Make enough space for a 5x5 8888 surface (4 bytes for R, G, B, A)
178                const rdsData = CanvasKit.Malloc(Uint8Array, 512 * 5*512 * 4);
179                const pixels2 = rdsData.toTypedArray();
180                pixels2[0] = 127;  // sentinel value, should be overwritten by readPixels.
181                img.readPixels(0, 0, imageInfo, rdsData, rowBytes);
182                expect(rdsData.toTypedArray()[0]).toEqual(pixels[0]);
183
184                img.delete();
185                CanvasKit.Free(rdsData);
186                done();
187            })();
188        });
189    });
190
191    gm('drawDrawable_animated_gif', (canvas, fetchedByteBuffers) => {
192        let aImg = CanvasKit.MakeAnimatedImageFromEncoded(fetchedByteBuffers[0]);
193        expect(aImg).toBeTruthy();
194        expect(aImg.getRepetitionCount()).toEqual(-1); // infinite loop
195        expect(aImg.width()).toEqual(320);
196        expect(aImg.height()).toEqual(240);
197        expect(aImg.getFrameCount()).toEqual(60);
198        expect(aImg.currentFrameDuration()).toEqual(60);
199
200        const drawCurrentFrame = function(x, y) {
201            let img = aImg.makeImageAtCurrentFrame();
202            canvas.drawImage(img, x, y, null);
203            img.delete();
204        }
205
206        drawCurrentFrame(0, 0);
207
208        let c = aImg.decodeNextFrame();
209        expect(c).not.toEqual(-1);
210        drawCurrentFrame(300, 0);
211        for(let i = 0; i < 10; i++) {
212            c = aImg.decodeNextFrame();
213            expect(c).not.toEqual(-1);
214        }
215        drawCurrentFrame(0, 300);
216        for(let i = 0; i < 10; i++) {
217            c = aImg.decodeNextFrame();
218            expect(c).not.toEqual(-1);
219        }
220        drawCurrentFrame(300, 300);
221
222        aImg.delete();
223    }, '/assets/flightAnim.gif');
224
225    gm('exif_orientation', (canvas, fetchedByteBuffers) => {
226        const paint = new CanvasKit.Paint();
227        const font = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 14);
228        canvas.drawText('The following heart should be rotated 90 CCW due to exif.',
229            5, 25, paint, font);
230
231        // TODO(kjlubick) it would be nice to also to test MakeAnimatedImageFromEncoded but
232        //   I could not create a sample animated image that worked.
233        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
234        expect(img).toBeTruthy();
235        canvas.drawImage(img, 5, 35, null);
236
237        img.delete();
238        paint.delete();
239        font.delete();
240    }, '/assets/exif_rotated_heart.jpg');
241
242    gm('1x4_from_scratch', (canvas) => {
243        const paint = new CanvasKit.Paint();
244
245        // This creates and draws an Image that is 1 pixel wide, 4 pixels tall with
246        // the colors listed below.
247        const pixels = Uint8Array.from([
248            255,   0,   0, 255, // opaque red
249              0, 255,   0, 255, // opaque green
250              0,   0, 255, 255, // opaque blue
251            255,   0, 255, 100, // transparent purple
252        ]);
253        const img = CanvasKit.MakeImage({
254          'width': 1,
255          'height': 4,
256          'alphaType': CanvasKit.AlphaType.Unpremul,
257          'colorType': CanvasKit.ColorType.RGBA_8888,
258          'colorSpace': CanvasKit.ColorSpace.SRGB
259        }, pixels, 4);
260        canvas.drawImage(img, 1, 1, paint);
261
262        const info = img.getImageInfo();
263        expect(info).toEqual({
264          'width': 1,
265          'height': 4,
266          'alphaType': CanvasKit.AlphaType.Unpremul,
267          'colorType': CanvasKit.ColorType.RGBA_8888,
268        });
269        const cs = img.getColorSpace();
270        expect(CanvasKit.ColorSpace.Equals(cs, CanvasKit.ColorSpace.SRGB)).toBeTruthy();
271
272        cs.delete();
273        img.delete();
274        paint.delete();
275    });
276
277    gm('draw_atlas_with_builders', (canvas, fetchedByteBuffers) => {
278        const atlas = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
279        expect(atlas).toBeTruthy();
280
281        const paint = new CanvasKit.Paint();
282        paint.setColor(CanvasKit.Color(0, 0, 0, 0.8));
283
284        // Allocate space for 4 rectangles.
285        const srcs = CanvasKit.Malloc(Float32Array, 16);
286        srcs.toTypedArray().set([
287            0,   0, 256, 256, // LTRB
288          256,   0, 512, 256,
289            0, 256, 256, 512,
290          256, 256, 512, 512
291        ]);
292
293        // Allocate space for 4 RSXForms.
294        const dsts = CanvasKit.Malloc(Float32Array, 16);
295        dsts.toTypedArray().set([
296            0.5, 0,  20,  20, // scos, ssin, tx, ty
297            0.5, 0, 300,  20,
298            0.5, 0,  20, 300,
299            0.5, 0, 300, 300
300        ]);
301
302        // Allocate space for 4 colors.
303        const colors = new CanvasKit.Malloc(Uint32Array, 4);
304        colors.toTypedArray().set([
305          CanvasKit.ColorAsInt( 85, 170,  10, 128), // light green
306          CanvasKit.ColorAsInt( 51,  51, 191, 128), // light blue
307          CanvasKit.ColorAsInt(  0,   0,   0, 128),
308          CanvasKit.ColorAsInt(256, 256, 256, 128),
309        ]);
310
311        canvas.drawAtlas(atlas, srcs, dsts, paint, CanvasKit.BlendMode.Modulate, colors);
312
313        atlas.delete();
314        CanvasKit.Free(srcs);
315        CanvasKit.Free(dsts);
316        CanvasKit.Free(colors);
317        paint.delete();
318    }, '/assets/mandrill_512.png');
319
320    gm('draw_atlas_with_arrays', (canvas, fetchedByteBuffers) => {
321        const atlas = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
322        expect(atlas).toBeTruthy();
323
324        const paint = new CanvasKit.Paint();
325        paint.setColor(CanvasKit.Color(0, 0, 0, 0.8));
326
327        const srcs = [
328            0, 0,  8,  8,
329            8, 0, 16,  8,
330            0, 8,  8, 16,
331            8, 8, 16, 16,
332        ];
333
334        const dsts = [
335            10, 0,   0,   0,
336            10, 0, 100,   0,
337            10, 0,   0, 100,
338            10, 0, 100, 100,
339        ];
340
341        const colors = Uint32Array.of(
342            CanvasKit.ColorAsInt( 85, 170,  10, 128), // light green
343            CanvasKit.ColorAsInt( 51,  51, 191, 128), // light blue
344            CanvasKit.ColorAsInt(  0,   0,   0, 128),
345            CanvasKit.ColorAsInt(255, 255, 255, 128),
346        );
347
348        // sampling for each of the 4 instances
349        const sampling = [
350            null,
351            {B: 0, C: 0.5},
352            {filter: CanvasKit.FilterMode.Nearest, mipmap: CanvasKit.MipmapMode.None},
353            {filter: CanvasKit.FilterMode.Linear,  mipmap: CanvasKit.MipmapMode.Nearest},
354        ];
355
356        // positioning for each of the 4 instances
357        const offset = [
358            [0, 0], [256, 0], [0, 256], [256, 256]
359        ];
360
361        canvas.translate(20, 20);
362        for (let i = 0; i < 4; ++i) {
363            canvas.save();
364            canvas.translate(offset[i][0], offset[i][1]);
365            canvas.drawAtlas(atlas, srcs, dsts, paint, CanvasKit.BlendMode.SrcOver, colors,
366                             sampling[i]);
367            canvas.restore();
368        }
369
370        atlas.delete();
371        paint.delete();
372    }, '/assets/mandrill_16.png');
373
374    gm('draw_patch', (canvas, fetchedByteBuffers) => {
375        const image = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
376        expect(image).toBeTruthy();
377
378        const paint = new CanvasKit.Paint();
379        const shader = image.makeShaderOptions(CanvasKit.TileMode.Clamp,
380                                               CanvasKit.TileMode.Clamp,
381                                               CanvasKit.FilterMode.Linear,
382                                               CanvasKit.MipmapMode.None);
383        const cubics = [0,0, 80,50, 160,50,
384                        240,0, 200,80, 200,160,
385                        240,240, 160,160, 80,240,
386                        0,240, 50,160, 0,80];
387         const colors = [CanvasKit.RED, CanvasKit.BLUE, CanvasKit.YELLOW, CanvasKit.CYAN];
388         const texs = [0,0, 16,0, 16,16, 0,16];
389
390         const params = [
391             [  0,   0, colors, null, null,   CanvasKit.BlendMode.Dst],
392             [256,   0, null,   texs, shader, null],
393             [  0, 256, colors, texs, shader, null],
394             [256, 256, colors, texs, shader, CanvasKit.BlendMode.Screen],
395         ];
396         for (const p of params) {
397             paint.setShader(p[4]);
398             canvas.save();
399             canvas.translate(p[0], p[1]);
400             canvas.drawPatch(cubics, p[2], p[3], p[5], paint);
401             canvas.restore();
402         }
403        paint.delete();
404    }, '/assets/mandrill_16.png');
405
406    gm('draw_glyphs', (canvas) => {
407
408        const paint = new CanvasKit.Paint();
409        const font = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 24);
410        paint.setAntiAlias(true);
411
412        const DIM = 16; // row/col count for the grid
413        const GAP = 32; // spacing between each glyph
414        const glyphs = new Uint16Array(256);
415        const positions = new Float32Array(256*2);
416        for (let i = 0; i < 256; ++i) {
417            glyphs[i] = i;
418            positions[2*i+0] = (i%DIM) * GAP;
419            positions[2*i+1] = Math.round(i/DIM) * GAP;
420        }
421        canvas.drawGlyphs(glyphs, positions, 16, 20, font, paint);
422
423        font.delete();
424        paint.delete();
425    });
426
427    gm('image_decoding_methods', async (canvas) => {
428
429        const IMAGE_FILE_PATHS = [
430            '/assets/brickwork-texture.jpg',
431            '/assets/mandrill_512.png',
432            '/assets/color_wheel.gif'
433        ];
434
435        let row = 1;
436        // Test 4 different methods of decoding an image for each of the three images in
437        // IMAGE_FILE_PATHS.
438        // Resulting Images are drawn to visually show that all methods decode correctly.
439        for (const imageFilePath of IMAGE_FILE_PATHS) {
440            const response = await fetch(imageFilePath);
441            const arrayBuffer = await response.arrayBuffer();
442            // response.blob() is preferable when you don't need both a Blob *and* an ArrayBuffer.
443            const blob = new Blob([ arrayBuffer ]);
444
445            // Method 1 - decode TypedArray using wasm codecs:
446            const skImage1 = CanvasKit.MakeImageFromEncoded(arrayBuffer);
447
448            // Method 2 (slower and does not work in Safari) decode using ImageBitmap:
449            const imageBitmap = await createImageBitmap(blob);
450            // Testing showed that transferring an ImageBitmap to a canvas using the 'bitmaprenderer'
451            // context and passing that canvas to CanvasKit.MakeImageFromCanvasImageSource() is
452            // marginally faster than passing ImageBitmap to
453            // CanvasKit.MakeImageFromCanvasImageSource() directly.
454            const canvasBitmapElement = document.createElement('canvas');
455            canvasBitmapElement.width = imageBitmap.width;
456            canvasBitmapElement.height = imageBitmap.height;
457            const ctxBitmap = canvasBitmapElement.getContext('bitmaprenderer');
458            ctxBitmap.transferFromImageBitmap(imageBitmap);
459            const skImage2 = CanvasKit.MakeImageFromCanvasImageSource(canvasBitmapElement);
460
461            // Method 3 (slowest) decode using HTMLImageElement directly:
462            const image = new Image();
463            // Testing showed that waiting for a load event is faster than waiting on image.decode()
464            // HTMLImageElement.decode() reference: https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode
465            const promise1 = new Promise((resolve) => image.addEventListener('load', resolve));
466            image.src = imageFilePath;
467            await promise1;
468            const skImage3 = CanvasKit.MakeImageFromCanvasImageSource(image);
469
470            // Method 4 (roundabout, but works if all you have is a Blob) decode from Blob using
471            // HTMLImageElement:
472            const imageObjectUrl = URL.createObjectURL( blob );
473            const image2 = new Image();
474            const promise2 = new Promise((resolve) => image2.addEventListener('load', resolve));
475            image2.src = imageObjectUrl;
476            await promise2;
477            const skImage4 = CanvasKit.MakeImageFromCanvasImageSource(image2);
478
479            // Draw decoded images
480            const sourceRect = CanvasKit.XYWHRect(0, 0, 150, 150);
481            canvas.drawImageRect(skImage1, sourceRect, CanvasKit.XYWHRect(0, row * 100, 90, 90), null, false);
482            canvas.drawImageRect(skImage2, sourceRect, CanvasKit.XYWHRect(100, row * 100, 90, 90), null, false);
483            canvas.drawImageRect(skImage3, sourceRect, CanvasKit.XYWHRect(200, row * 100, 90, 90), null, false);
484            canvas.drawImageRect(skImage4, sourceRect, CanvasKit.XYWHRect(300, row * 100, 90, 90), null, false);
485
486            row++;
487        }
488        // Label images with the method used to decode them
489        const paint = new CanvasKit.Paint();
490        const textFont = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 7);
491        canvas.drawText('WASM Decoding', 0, 90, paint, textFont);
492        canvas.drawText('ImageBitmap Decoding', 100, 90, paint, textFont);
493        canvas.drawText('HTMLImageEl Decoding', 200, 90, paint, textFont);
494        canvas.drawText('Blob Decoding', 300, 90, paint, textFont);
495    });
496
497    gm('sweep_gradient', (canvas) => {
498        const paint = new CanvasKit.Paint();
499        const shader = CanvasKit.Shader.MakeSweepGradient(
500            100, 100, // X, Y coordinates
501            [CanvasKit.GREEN, CanvasKit.BLUE],
502            [0.0, 1.0],
503            CanvasKit.TileMode.Clamp,
504        );
505        expect(shader).toBeTruthy('Could not make shader');
506
507        paint.setShader(shader);
508        canvas.drawPaint(paint);
509
510        paint.delete();
511        shader.delete();
512    });
513
514    // TODO(kjlubick): There's a lot of shared code between the gradient gms
515    // It would be best to deduplicate that in a nice DAMP way.
516    // Inspired by https://fiddle.skia.org/c/b29ce50a341510784ac7d5281586d076
517    gm('linear_gradients', (canvas) => {
518        canvas.scale(2, 2);
519        const strokePaint = new CanvasKit.Paint();
520        strokePaint.setStyle(CanvasKit.PaintStyle.Stroke);
521        strokePaint.setColor(CanvasKit.BLACK);
522
523        const paint = new CanvasKit.Paint();
524        paint.setStyle(CanvasKit.PaintStyle.Fill);
525        const transparentGreen = CanvasKit.Color(0, 255, 255, 0);
526
527        const lgs = CanvasKit.Shader.MakeLinearGradient(
528            [0, 0], [50, 100], // start and stop points
529            [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
530            [0, 0.65, 1.0],
531            CanvasKit.TileMode.Mirror
532        );
533        paint.setShader(lgs);
534        let r = CanvasKit.LTRBRect(0, 0, 100, 100);
535        canvas.drawRect(r, paint);
536        canvas.drawRect(r, strokePaint);
537
538        const lgsPremul = CanvasKit.Shader.MakeLinearGradient(
539            [100, 0], [150, 100], // start and stop points
540            Uint32Array.of(
541                CanvasKit.ColorAsInt(0, 255, 255, 0),
542                CanvasKit.ColorAsInt(0, 0, 255, 255),
543                CanvasKit.ColorAsInt(255, 0, 0, 255)),
544            [0, 0.65, 1.0],
545            CanvasKit.TileMode.Mirror,
546            null, // no local matrix
547            1 // interpolate colors in premul
548        );
549        paint.setShader(lgsPremul);
550        r = CanvasKit.LTRBRect(100, 0, 200, 100);
551        canvas.drawRect(r, paint);
552        canvas.drawRect(r, strokePaint);
553
554        const lgs45 = CanvasKit.Shader.MakeLinearGradient(
555            [0, 100], [50, 200], // start and stop points
556            Float32Array.of(...transparentGreen, ...CanvasKit.BLUE, ...CanvasKit.RED),
557            [0, 0.65, 1.0],
558            CanvasKit.TileMode.Mirror,
559            CanvasKit.Matrix.rotated(Math.PI/4, 0, 100),
560        );
561        paint.setShader(lgs45);
562        r = CanvasKit.LTRBRect(0, 100, 100, 200);
563        canvas.drawRect(r, paint);
564        canvas.drawRect(r, strokePaint);
565
566        // malloc'd color array
567        const colors = CanvasKit.Malloc(Float32Array, 12);
568        const typedColorsArray = colors.toTypedArray();
569        typedColorsArray.set(transparentGreen, 0);
570        typedColorsArray.set(CanvasKit.BLUE, 4);
571        typedColorsArray.set(CanvasKit.RED, 8);
572        const lgs45Premul = CanvasKit.Shader.MakeLinearGradient(
573            [100, 100], [150, 200], // start and stop points
574            typedColorsArray,
575            [0, 0.65, 1.0],
576            CanvasKit.TileMode.Mirror,
577            CanvasKit.Matrix.rotated(Math.PI/4, 100, 100),
578            1 // interpolate colors in premul
579        );
580        CanvasKit.Free(colors);
581        paint.setShader(lgs45Premul);
582        r = CanvasKit.LTRBRect(100, 100, 200, 200);
583        canvas.drawRect(r, paint);
584        canvas.drawRect(r, strokePaint);
585
586        lgs.delete();
587        lgs45.delete();
588        lgsPremul.delete();
589        lgs45Premul.delete();
590        strokePaint.delete();
591        paint.delete();
592    });
593
594    gm('radial_gradients', (canvas) => {
595        canvas.scale(2, 2);
596        const strokePaint = new CanvasKit.Paint();
597        strokePaint.setStyle(CanvasKit.PaintStyle.Stroke);
598        strokePaint.setColor(CanvasKit.BLACK);
599
600        const paint = new CanvasKit.Paint();
601        paint.setStyle(CanvasKit.PaintStyle.Fill);
602        const transparentGreen = CanvasKit.Color(0, 255, 255, 0);
603
604        const rgs = CanvasKit.Shader.MakeRadialGradient(
605            [50, 50], 50, // center, radius
606            [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
607            [0, 0.65, 1.0],
608            CanvasKit.TileMode.Mirror
609        );
610        paint.setShader(rgs);
611        let r = CanvasKit.LTRBRect(0, 0, 100, 100);
612        canvas.drawRect(r, paint);
613        canvas.drawRect(r, strokePaint);
614
615        const rgsPremul = CanvasKit.Shader.MakeRadialGradient(
616            [150, 50], 50, // center, radius
617            [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
618            [0, 0.65, 1.0],
619            CanvasKit.TileMode.Mirror,
620            null, // no local matrix
621            1 // interpolate colors in premul
622        );
623        paint.setShader(rgsPremul);
624        r = CanvasKit.LTRBRect(100, 0, 200, 100);
625        canvas.drawRect(r, paint);
626        canvas.drawRect(r, strokePaint);
627
628        const rgsSkew = CanvasKit.Shader.MakeRadialGradient(
629            [50, 150], 50, // center, radius
630            [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
631            [0, 0.65, 1.0],
632            CanvasKit.TileMode.Mirror,
633            CanvasKit.Matrix.skewed(0.5, 0, 100, 100),
634            null, // color space
635        );
636        paint.setShader(rgsSkew);
637        r = CanvasKit.LTRBRect(0, 100, 100, 200);
638        canvas.drawRect(r, paint);
639        canvas.drawRect(r, strokePaint);
640
641        const rgsSkewPremul = CanvasKit.Shader.MakeRadialGradient(
642            [150, 150], 50, // center, radius
643            [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
644            [0, 0.65, 1.0],
645            CanvasKit.TileMode.Mirror,
646            CanvasKit.Matrix.skewed(0.5, 0, 100, 100),
647            1, // interpolate colors in premul
648            null, // color space
649        );
650        paint.setShader(rgsSkewPremul);
651        r = CanvasKit.LTRBRect(100, 100, 200, 200);
652        canvas.drawRect(r, paint);
653        canvas.drawRect(r, strokePaint);
654
655        rgs.delete();
656        rgsPremul.delete();
657        rgsSkew.delete();
658        rgsSkewPremul.delete();
659        strokePaint.delete();
660        paint.delete();
661    });
662
663    gm('conical_gradients', (canvas) => {
664        canvas.scale(2, 2);
665        const strokePaint = new CanvasKit.Paint();
666        strokePaint.setStyle(CanvasKit.PaintStyle.Stroke);
667        strokePaint.setColor(CanvasKit.BLACK);
668
669        const paint = new CanvasKit.Paint();
670        paint.setStyle(CanvasKit.PaintStyle.Fill);
671        paint.setAntiAlias(true);
672        const transparentGreen = CanvasKit.Color(0, 255, 255, 0);
673
674        const cgs = CanvasKit.Shader.MakeTwoPointConicalGradient(
675            [80, 10], 15, // start, radius
676            [10, 110], 60, // end, radius
677            [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
678            [0, 0.65, 1.0],
679            CanvasKit.TileMode.Mirror,
680            null, // no local matrix
681        );
682        paint.setShader(cgs);
683        let r = CanvasKit.LTRBRect(0, 0, 100, 100);
684        canvas.drawRect(r, paint);
685        canvas.drawRect(r, strokePaint);
686
687        const cgsPremul = CanvasKit.Shader.MakeTwoPointConicalGradient(
688            [180, 10], 15, // start, radius
689            [110, 110], 60, // end, radius
690            [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
691            [0, 0.65, 1.0],
692            CanvasKit.TileMode.Mirror,
693            null, // no local matrix
694            1, // interpolate colors in premul
695            null, // color space
696        );
697        paint.setShader(cgsPremul);
698        r = CanvasKit.LTRBRect(100, 0, 200, 100);
699        canvas.drawRect(r, paint);
700        canvas.drawRect(r, strokePaint);
701
702        const cgs45 = CanvasKit.Shader.MakeTwoPointConicalGradient(
703            [80, 110], 15, // start, radius
704            [10, 210], 60, // end, radius
705            [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
706            [0, 0.65, 1.0],
707            CanvasKit.TileMode.Mirror,
708            CanvasKit.Matrix.rotated(Math.PI/4, 0, 100),
709            null, // color space
710        );
711        paint.setShader(cgs45);
712        r = CanvasKit.LTRBRect(0, 100, 100, 200);
713        canvas.drawRect(r, paint);
714        canvas.drawRect(r, strokePaint);
715
716        const cgs45Premul = CanvasKit.Shader.MakeTwoPointConicalGradient(
717            [180, 110], 15, // start, radius
718            [110, 210], 60, // end, radius
719            [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
720            [0, 0.65, 1.0],
721            CanvasKit.TileMode.Mirror,
722            CanvasKit.Matrix.rotated(Math.PI/4, 100, 100),
723            1, // interpolate colors in premul
724            null, // color space
725        );
726        paint.setShader(cgs45Premul);
727        r = CanvasKit.LTRBRect(100, 100, 200, 200);
728        canvas.drawRect(r, paint);
729        canvas.drawRect(r, strokePaint);
730
731        cgs.delete();
732        cgsPremul.delete();
733        cgs45.delete();
734        strokePaint.delete();
735        paint.delete();
736    });
737
738    it('can compute ImageFilter filterBounds', () => {
739      const blurIF = CanvasKit.ImageFilter.MakeBlur(5, 10, CanvasKit.TileMode.Clamp, null);
740      const updatedBounds = blurIF.getOutputBounds(CanvasKit.LTRBRect(50, 50, 100, 100));
741      expect(updatedBounds[0]).toEqual(35);
742      expect(updatedBounds[1]).toEqual(20);
743      expect(updatedBounds[2]).toEqual(115);
744      expect(updatedBounds[3]).toEqual(130);
745    });
746
747    gm('blur_filters', (canvas) => {
748        const pathUL = starPath(CanvasKit, 100, 100, 80);
749        const pathBR = starPath(CanvasKit, 400, 300, 80);
750        const paint = new CanvasKit.Paint();
751        const textFont = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 24);
752
753        canvas.drawText('Above: MaskFilter', 20, 220, paint, textFont);
754        canvas.drawText('Right: ImageFilter', 20, 260, paint, textFont);
755
756        paint.setColor(CanvasKit.BLUE);
757
758        const blurMask = CanvasKit.MaskFilter.MakeBlur(CanvasKit.BlurStyle.Normal, 5, true);
759        paint.setMaskFilter(blurMask);
760        canvas.drawPath(pathUL, paint);
761
762        const blurIF = CanvasKit.ImageFilter.MakeBlur(8, 1, CanvasKit.TileMode.Decal, null);
763        paint.setImageFilter(blurIF);
764        canvas.drawPath(pathBR, paint);
765
766        pathUL.delete();
767        pathBR.delete();
768        paint.delete();
769        blurMask.delete();
770        blurIF.delete();
771        textFont.delete();
772    });
773
774    gm('luma_filter', (canvas) => {
775        const paint = new CanvasKit.Paint();
776        paint.setAntiAlias(true);
777        const lumaCF = CanvasKit.ColorFilter.MakeLuma();
778        paint.setColor(CanvasKit.BLUE);
779        paint.setColorFilter(lumaCF);
780        canvas.drawCircle(256, 256, 256, paint);
781        paint.delete();
782        lumaCF.delete();
783    });
784
785    gm('combined_filters', (canvas, fetchedByteBuffers) => {
786        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
787        expect(img).toBeTruthy();
788        const paint = new CanvasKit.Paint();
789        paint.setAntiAlias(true);
790        paint.setColor(CanvasKit.Color(0, 255, 0, 1.0));
791        const redCF =  CanvasKit.ColorFilter.MakeBlend(
792                CanvasKit.Color(255, 0, 0, 0.1), CanvasKit.BlendMode.SrcOver);
793        const redIF = CanvasKit.ImageFilter.MakeColorFilter(redCF, null);
794        const blurIF = CanvasKit.ImageFilter.MakeBlur(8, 0.2, CanvasKit.TileMode.Decal, null);
795        const combined = CanvasKit.ImageFilter.MakeCompose(redIF, blurIF);
796
797        // rotate 10 degrees centered on 200, 200
798        const m = CanvasKit.Matrix.rotated(Math.PI/18, 200, 200);
799        const filtering = { filter: CanvasKit.FilterMode.Linear };
800        const rotated = CanvasKit.ImageFilter.MakeMatrixTransform(m, filtering, combined);
801        paint.setImageFilter(rotated);
802
803        //canvas.rotate(10, 200, 200);
804        canvas.drawImage(img, 0, 0, paint);
805        canvas.drawRect(CanvasKit.LTRBRect(5, 35, 45, 80), paint);
806
807        paint.delete();
808        redIF.delete();
809        redCF.delete();
810        blurIF.delete();
811        combined.delete();
812        rotated.delete();
813        img.delete();
814    }, '/assets/mandrill_512.png');
815
816    gm('animated_filters', (canvas, fetchedByteBuffers) => {
817        const img = CanvasKit.MakeAnimatedImageFromEncoded(fetchedByteBuffers[0]);
818        expect(img).toBeTruthy();
819        img.decodeNextFrame();
820        img.decodeNextFrame();
821        const paint = new CanvasKit.Paint();
822        paint.setAntiAlias(true);
823        paint.setColor(CanvasKit.Color(0, 255, 0, 1.0));
824        const redCF =  CanvasKit.ColorFilter.MakeBlend(
825                CanvasKit.Color(255, 0, 0, 0.1), CanvasKit.BlendMode.SrcOver);
826        const redIF = CanvasKit.ImageFilter.MakeColorFilter(redCF, null);
827        const blurIF = CanvasKit.ImageFilter.MakeBlur(8, 0.2, CanvasKit.TileMode.Decal, null);
828        const combined = CanvasKit.ImageFilter.MakeCompose(redIF, blurIF);
829        paint.setImageFilter(combined);
830
831        const frame = img.makeImageAtCurrentFrame();
832        canvas.drawImage(frame, 100, 50, paint);
833
834        paint.delete();
835        redIF.delete();
836        redCF.delete();
837        blurIF.delete();
838        combined.delete();
839        frame.delete();
840        img.delete();
841    }, '/assets/flightAnim.gif');
842
843    gm('drawImageVariants', (canvas, fetchedByteBuffers) => {
844        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
845        expect(img).toBeTruthy();
846        canvas.scale(2, 2);
847        const paint = new CanvasKit.Paint();
848        const clipTo = (x, y) => {
849            canvas.save();
850            canvas.clipRect(CanvasKit.XYWHRect(x, y, 128, 128), CanvasKit.ClipOp.Intersect);
851        };
852
853        clipTo(0, 0);
854        canvas.drawImage(img, 0, 0, paint);
855        canvas.restore();
856
857        clipTo(128, 0);
858        canvas.drawImageCubic(img, 128, 0, 1/3, 1/3, null);
859        canvas.restore();
860
861        clipTo(0, 128);
862        canvas.drawImageOptions(img, 0, 128, CanvasKit.FilterMode.Linear, CanvasKit.MipmapMode.None, null);
863        canvas.restore();
864
865        const mipImg = img.makeCopyWithDefaultMipmaps();
866        clipTo(128, 128);
867        canvas.drawImageOptions(mipImg, 128, 128,
868                                CanvasKit.FilterMode.Nearest, CanvasKit.MipmapMode.Nearest, null);
869        canvas.restore();
870
871        paint.delete();
872        mipImg.delete();
873        img.delete();
874    }, '/assets/mandrill_512.png');
875
876    gm('drawImageRectVariants', (canvas, fetchedByteBuffers) => {
877        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
878        expect(img).toBeTruthy();
879        const paint = new CanvasKit.Paint();
880        const src = CanvasKit.XYWHRect(100, 100, 128, 128);
881        canvas.drawImageRect(img, src, CanvasKit.XYWHRect(0, 0, 256, 256), paint);
882        canvas.drawImageRectCubic(img, src, CanvasKit.XYWHRect(256, 0, 256, 256), 1/3, 1/3);
883        canvas.drawImageRectOptions(img, src, CanvasKit.XYWHRect(0, 256, 256, 256),
884                                    CanvasKit.FilterMode.Linear, CanvasKit.MipmapMode.None);
885        const mipImg = img.makeCopyWithDefaultMipmaps();
886        canvas.drawImageRectOptions(mipImg, src, CanvasKit.XYWHRect(256, 256, 256, 256),
887                                CanvasKit.FilterMode.Nearest, CanvasKit.MipmapMode.Nearest);
888
889        paint.delete();
890        mipImg.delete();
891        img.delete();
892    }, '/assets/mandrill_512.png');
893
894    gm('drawImage_skp', (canvas, fetchedByteBuffers) => {
895        canvas.clear(CanvasKit.TRANSPARENT);
896        const pic = CanvasKit.MakePicture(fetchedByteBuffers[0]);
897        canvas.drawPicture(pic);
898        // The asset below can be re-downloaded from
899        // https://fiddle.skia.org/c/cbb8dee39e9f1576cd97c2d504db8eee
900    }, '/assets/red_line.skp');
901
902    it('can draw once using drawOnce utility method', (done) => {
903        const surface = CanvasKit.MakeCanvasSurface('test');
904        expect(surface).toBeTruthy('Could not make surface');
905        if (!surface) {
906            done();
907            return;
908        }
909
910        const drawFrame = (canvas) => {
911            const paint = new CanvasKit.Paint();
912            paint.setStrokeWidth(1.0);
913            paint.setAntiAlias(true);
914            paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
915            paint.setStyle(CanvasKit.PaintStyle.Stroke);
916            const path = new CanvasKit.Path();
917            path.moveTo(20, 5);
918            path.lineTo(30, 20);
919            path.lineTo(40, 10);
920            canvas.drawPath(path, paint);
921            path.delete();
922            paint.delete();
923            // surface hasn't been flushed yet (nor should we call flush
924            // ourselves), so reportSurface would likely be blank if we
925            // were to call it.
926            done();
927        };
928        surface.drawOnce(drawFrame);
929        // Reminder: drawOnce is async. In this test, we are just making
930        // sure the drawOnce function is there and doesn't crash, so we can
931        // just call done() when the frame is rendered.
932    });
933
934    it('can draw client-supplied dirty rects', (done) => {
935        // dirty rects are only honored by software (CPU) canvases today.
936        const surface = CanvasKit.MakeSWCanvasSurface('test');
937        expect(surface).toBeTruthy('Could not make surface');
938        if (!surface) {
939            done();
940            return;
941        }
942
943        const drawFrame = (canvas) => {
944            const paint = new CanvasKit.Paint();
945            paint.setStrokeWidth(1.0);
946            paint.setAntiAlias(true);
947            paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
948            paint.setStyle(CanvasKit.PaintStyle.Stroke);
949            const path = new CanvasKit.Path();
950            path.moveTo(20, 5);
951            path.lineTo(30, 20);
952            path.lineTo(40, 10);
953            canvas.drawPath(path, paint);
954            path.delete();
955            paint.delete();
956            done();
957        };
958        const dirtyRect = CanvasKit.XYWHRect(10, 10, 15, 15);
959        surface.drawOnce(drawFrame, dirtyRect);
960        // We simply ensure that passing a dirty rect doesn't crash.
961    });
962
963    it('can use DecodeCache APIs', () => {
964        const initialLimit = CanvasKit.getDecodeCacheLimitBytes();
965        expect(initialLimit).toBeGreaterThan(1024 * 1024);
966
967        const newLimit = 42 * 1024 * 1024;
968        CanvasKit.setDecodeCacheLimitBytes(newLimit);
969        expect(CanvasKit.getDecodeCacheLimitBytes()).toEqual(newLimit);
970
971        // We cannot make any assumptions about this value,
972        // so we just make sure it doesn't crash.
973        CanvasKit.getDecodeCacheUsedBytes();
974    });
975
976    gm('combined_shaders', (canvas) => {
977        const rShader = CanvasKit.Shader.Color(CanvasKit.Color(255, 0, 0, 1.0)); // deprecated
978        const gShader = CanvasKit.Shader.MakeColor(CanvasKit.Color(0, 255, 0, 0.6));
979
980        const rgShader = CanvasKit.Shader.MakeBlend(CanvasKit.BlendMode.SrcOver, rShader, gShader);
981
982        const p = new CanvasKit.Paint();
983        p.setShader(rgShader);
984        canvas.drawPaint(p);
985
986        rShader.delete();
987        gShader.delete();
988        rgShader.delete();
989        p.delete();
990    });
991
992    it('exports consts correctly', () => {
993        expect(CanvasKit.TRANSPARENT).toEqual(Float32Array.of(0, 0, 0, 0));
994        expect(CanvasKit.RED).toEqual(Float32Array.of(1, 0, 0, 1));
995
996        expect(CanvasKit.QUAD_VERB).toEqual(2);
997        expect(CanvasKit.CONIC_VERB).toEqual(3);
998
999        expect(CanvasKit.SaveLayerInitWithPrevious).toEqual(4);
1000        expect(CanvasKit.SaveLayerF16ColorType).toEqual(16);
1001    });
1002
1003    it('can set color on a paint and get it as four floats', () => {
1004        const paint = new CanvasKit.Paint();
1005        paint.setColor(CanvasKit.Color4f(3.3, 2.2, 1.1, 0.5));
1006        expect(paint.getColor()).toEqual(Float32Array.of(3.3, 2.2, 1.1, 0.5));
1007
1008        paint.setColorComponents(0.5, 0.6, 0.7, 0.8);
1009        expect(paint.getColor()).toEqual(Float32Array.of(0.5, 0.6, 0.7, 0.8));
1010
1011        paint.setColorInt(CanvasKit.ColorAsInt(50, 100, 150, 200));
1012        let color = paint.getColor();
1013        expect(color.length).toEqual(4);
1014        expect(color[0]).toBeCloseTo(50/255, 5);  // Red
1015        expect(color[1]).toBeCloseTo(100/255, 5); // Green
1016        expect(color[2]).toBeCloseTo(150/255, 5); // Blue
1017        expect(color[3]).toBeCloseTo(200/255, 5); // Alpha
1018
1019        paint.setColorInt(0xFF000000);
1020        expect(paint.getColor()).toEqual(Float32Array.of(0, 0, 0, 1.0));
1021    });
1022
1023    gm('draw_shadow', (canvas) => {
1024        const lightRadius = 20;
1025        const lightPos = [500,500,20];
1026        const zPlaneParams = [0,0,1];
1027        const path = starPath(CanvasKit);
1028        const textFont = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 24);
1029        const textPaint = new CanvasKit.Paint();
1030
1031        canvas.drawShadow(path, zPlaneParams, lightPos, lightRadius,
1032                          CanvasKit.BLACK, CanvasKit.MAGENTA, 0);
1033        canvas.drawText('Default Flags', 5, 250, textPaint, textFont);
1034
1035        let bounds = CanvasKit.getShadowLocalBounds(CanvasKit.Matrix.identity(),
1036            path, zPlaneParams, lightPos, lightRadius, 0);
1037        expectTypedArraysToEqual(bounds, Float32Array.of(-3.64462, -12.67541, 245.50, 242.59164));
1038
1039        bounds = CanvasKit.getShadowLocalBounds(CanvasKit.M44.identity(),
1040            path, zPlaneParams, lightPos, lightRadius, 0);
1041        expectTypedArraysToEqual(bounds, Float32Array.of(-3.64462, -12.67541, 245.50, 242.59164));
1042
1043        // Test that the APIs accept Malloc objs and the Malloced typearray
1044        const mZPlane = CanvasKit.Malloc(Float32Array, 3);
1045        mZPlane.toTypedArray().set(zPlaneParams);
1046        const mLight = CanvasKit.Malloc(Float32Array, 3);
1047        const lightTA = mLight.toTypedArray();
1048        lightTA.set(lightPos);
1049
1050        canvas.translate(250, 250);
1051        canvas.drawShadow(path, mZPlane, lightTA, lightRadius,
1052                          CanvasKit.BLACK, CanvasKit.MAGENTA,
1053                          CanvasKit.ShadowTransparentOccluder | CanvasKit.ShadowGeometricOnly | CanvasKit.ShadowDirectionalLight);
1054        canvas.drawText('All Flags', 5, 250, textPaint, textFont);
1055
1056        const outBounds = new Float32Array(4);
1057        CanvasKit.getShadowLocalBounds(CanvasKit.Matrix.rotated(Math.PI / 6),
1058            path, mZPlane, mLight, lightRadius,
1059            CanvasKit.ShadowTransparentOccluder | CanvasKit.ShadowGeometricOnly | CanvasKit.ShadowDirectionalLight,
1060            outBounds);
1061        expectTypedArraysToEqual(outBounds, Float32Array.of(-31.6630249, -15.24227, 245.5, 252.94101));
1062
1063        CanvasKit.Free(mZPlane);
1064        CanvasKit.Free(mLight);
1065
1066        path.delete();
1067        textFont.delete();
1068        textPaint.delete();
1069    });
1070
1071    gm('fractal_noise_shader', (canvas) => {
1072        const shader = CanvasKit.Shader.MakeFractalNoise(0.1, 0.05, 2, 0, 0, 0);
1073        const paint = new CanvasKit.Paint();
1074        paint.setColor(CanvasKit.BLACK);
1075        paint.setShader(shader);
1076        canvas.drawPaint(paint);
1077        paint.delete();
1078        shader.delete();
1079    });
1080
1081    gm('turbulance_shader', (canvas) => {
1082        const shader = CanvasKit.Shader.MakeTurbulence(0.1, 0.05, 2, 117, 0, 0);
1083        const paint = new CanvasKit.Paint();
1084        paint.setColor(CanvasKit.BLACK);
1085        paint.setShader(shader);
1086        canvas.drawPaint(paint);
1087        paint.delete();
1088        shader.delete();
1089    });
1090
1091    gm('fractal_noise_tiled_shader', (canvas) => {
1092        const shader = CanvasKit.Shader.MakeFractalNoise(0.1, 0.05, 2, 0, 80, 80);
1093        const paint = new CanvasKit.Paint();
1094        paint.setColor(CanvasKit.BLACK);
1095        paint.setShader(shader);
1096        canvas.drawPaint(paint);
1097        paint.delete();
1098        shader.delete();
1099    });
1100
1101    gm('turbulance_tiled_shader', (canvas) => {
1102        const shader = CanvasKit.Shader.MakeTurbulence(0.1, 0.05, 2, 117, 80, 80);
1103        const paint = new CanvasKit.Paint();
1104        paint.setColor(CanvasKit.BLACK);
1105        paint.setShader(shader);
1106        canvas.drawPaint(paint);
1107        paint.delete();
1108        shader.delete();
1109    });
1110
1111    describe('ColorSpace Support', () => {
1112        it('Creates an SRGB 8888 surface by default', () => {
1113            const colorSpace = CanvasKit.ColorSpace.SRGB;
1114            const surface = CanvasKit.MakeCanvasSurface('test');
1115            expect(surface).toBeTruthy('Could not make surface');
1116            let info = surface.imageInfo();
1117            expect(info.alphaType).toEqual(CanvasKit.AlphaType.Unpremul);
1118            expect(info.colorType).toEqual(CanvasKit.ColorType.RGBA_8888);
1119            expect(CanvasKit.ColorSpace.Equals(info.colorSpace, colorSpace))
1120                .toBeTruthy("Surface not created with correct color space.");
1121
1122            const mObj = CanvasKit.Malloc(Uint8Array, CANVAS_WIDTH * CANVAS_HEIGHT * 4);
1123            mObj.toTypedArray()[0] = 127; // sentinel value. Should be overwritten by readPixels.
1124            const canvas = surface.getCanvas();
1125            canvas.clear(CanvasKit.TRANSPARENT);
1126            const pixels = canvas.readPixels(0, 0, {
1127                width: CANVAS_WIDTH,
1128                height: CANVAS_HEIGHT,
1129                colorType: CanvasKit.ColorType.RGBA_8888,
1130                alphaType: CanvasKit.AlphaType.Unpremul,
1131                colorSpace: colorSpace
1132            }, mObj, 4 * CANVAS_WIDTH);
1133            expect(pixels).toBeTruthy('Could not read pixels from surface');
1134            expect(pixels[0] !== 127).toBeTruthy();
1135            expect(pixels[0]).toEqual(mObj.toTypedArray()[0]);
1136            CanvasKit.Free(mObj);
1137            surface.delete();
1138        });
1139        it('Can create an SRGB 8888 surface', () => {
1140            const colorSpace = CanvasKit.ColorSpace.SRGB;
1141            const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.ColorSpace.SRGB);
1142            expect(surface).toBeTruthy('Could not make surface');
1143            let info = surface.imageInfo();
1144            expect(info.alphaType).toEqual(CanvasKit.AlphaType.Unpremul);
1145            expect(info.colorType).toEqual(CanvasKit.ColorType.RGBA_8888);
1146            expect(CanvasKit.ColorSpace.Equals(info.colorSpace, colorSpace))
1147                .toBeTruthy("Surface not created with correct color space.");
1148
1149            const mObj = CanvasKit.Malloc(Uint8Array, CANVAS_WIDTH * CANVAS_HEIGHT * 4);
1150            mObj.toTypedArray()[0] = 127; // sentinel value. Should be overwritten by readPixels.
1151            const canvas = surface.getCanvas();
1152            canvas.clear(CanvasKit.TRANSPARENT);
1153            const pixels = canvas.readPixels(0, 0, {
1154                width: CANVAS_WIDTH,
1155                height: CANVAS_HEIGHT,
1156                colorType: CanvasKit.ColorType.RGBA_8888,
1157                alphaType: CanvasKit.AlphaType.Unpremul,
1158                colorSpace: colorSpace
1159            }, mObj, 4 * CANVAS_WIDTH);
1160            expect(pixels).toBeTruthy('Could not read pixels from surface');
1161            expect(pixels[0] !== 127).toBeTruthy();
1162            expect(pixels[0]).toEqual(mObj.toTypedArray()[0]);
1163            CanvasKit.Free(mObj);
1164            surface.delete();
1165        });
1166        it('Can create a Display P3 surface', () => {
1167            const colorSpace = CanvasKit.ColorSpace.DISPLAY_P3;
1168            const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.ColorSpace.DISPLAY_P3);
1169            expect(surface).toBeTruthy('Could not make surface');
1170            if (!surface.reportBackendTypeIsGPU()) {
1171                console.log('Not expecting color space support in cpu backed suface.');
1172                return;
1173            }
1174            let info = surface.imageInfo();
1175            expect(info.alphaType).toEqual(CanvasKit.AlphaType.Unpremul);
1176            expect(info.colorType).toEqual(CanvasKit.ColorType.RGBA_F16);
1177            expect(CanvasKit.ColorSpace.Equals(info.colorSpace, colorSpace))
1178                .toBeTruthy("Surface not created with correct color space.");
1179
1180            const pixels = surface.getCanvas().readPixels(0, 0, {
1181                width: CANVAS_WIDTH,
1182                height: CANVAS_HEIGHT,
1183                colorType: CanvasKit.ColorType.RGBA_F16,
1184                alphaType: CanvasKit.AlphaType.Unpremul,
1185                colorSpace: colorSpace
1186            });
1187            expect(pixels).toBeTruthy('Could not read pixels from surface');
1188        });
1189        it('Can create an Adobe RGB surface', () => {
1190            const colorSpace = CanvasKit.ColorSpace.ADOBE_RGB;
1191            const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.ColorSpace.ADOBE_RGB);
1192            expect(surface).toBeTruthy('Could not make surface');
1193            if (!surface.reportBackendTypeIsGPU()) {
1194                console.log('Not expecting color space support in cpu backed surface.');
1195                return;
1196            }
1197            let info = surface.imageInfo();
1198            expect(info.alphaType).toEqual(CanvasKit.AlphaType.Unpremul);
1199            expect(info.colorType).toEqual(CanvasKit.ColorType.RGBA_F16);
1200            expect(CanvasKit.ColorSpace.Equals(info.colorSpace, colorSpace))
1201                .toBeTruthy("Surface not created with correct color space.");
1202
1203            const pixels = surface.getCanvas().readPixels(0, 0, {
1204                width: CANVAS_WIDTH,
1205                height: CANVAS_HEIGHT,
1206                colorType: CanvasKit.ColorType.RGBA_F16,
1207                alphaType: CanvasKit.AlphaType.Unpremul,
1208                colorSpace: colorSpace
1209            });
1210            expect(pixels).toBeTruthy('Could not read pixels from surface');
1211        });
1212
1213        it('combine draws from several color spaces', () => {
1214            const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.ColorSpace.ADOBE_RGB);
1215            expect(surface).toBeTruthy('Could not make surface');
1216            if (!surface.reportBackendTypeIsGPU()) {
1217                console.log('Not expecting color space support in cpu backed suface.');
1218                return;
1219            }
1220            const canvas = surface.getCanvas();
1221
1222            let paint = new CanvasKit.Paint();
1223            paint.setColor(CanvasKit.RED, CanvasKit.ColorSpace.ADOBE_RGB);
1224            canvas.drawPaint(paint);
1225            paint.setColor(CanvasKit.RED, CanvasKit.ColorSpace.DISPLAY_P3); // 93.7 in adobeRGB
1226            canvas.drawRect(CanvasKit.LTRBRect(200, 0, 400, 600), paint);
1227            paint.setColor(CanvasKit.RED, CanvasKit.ColorSpace.SRGB); // 85.9 in adobeRGB
1228            canvas.drawRect(CanvasKit.LTRBRect(400, 0, 600, 600), paint);
1229
1230            // this test paints three bands of red, each the maximum red that a color space supports.
1231            // They are each represented by skia by some color in the Adobe RGB space of the surface,
1232            // as floats between 0 and 1.
1233
1234            // TODO(nifong) readpixels and verify correctness after f32 readpixels bug is fixed
1235        });
1236    }); // end describe('ColorSpace Support')
1237
1238    describe('DOMMatrix support', () => {
1239        gm('sweep_gradient_dommatrix', (canvas) => {
1240            const paint = new CanvasKit.Paint();
1241            const shader = CanvasKit.Shader.MakeSweepGradient(
1242                100, 100, // x y coordinates
1243                [CanvasKit.GREEN, CanvasKit.BLUE],
1244                [0.0, 1.0],
1245                CanvasKit.TileMode.Clamp,
1246                new DOMMatrix().translate(-10, 100),
1247            );
1248            expect(shader).toBeTruthy('Could not make shader');
1249
1250            paint.setShader(shader);
1251            canvas.drawPaint(paint);
1252
1253            paint.delete();
1254            shader.delete();
1255        });
1256
1257        const radiansToDegrees = (rad) => {
1258           return (rad / Math.PI) * 180;
1259        };
1260
1261        // this should draw the same as concat_with4x4_canvas
1262        gm('concat_dommatrix', (canvas) => {
1263            const path = starPath(CanvasKit, CANVAS_WIDTH/2, CANVAS_HEIGHT/2);
1264            const paint = new CanvasKit.Paint();
1265            paint.setAntiAlias(true);
1266            canvas.concat(new DOMMatrix().translate(CANVAS_WIDTH/2, 0, 0));
1267            canvas.concat(new DOMMatrix().rotateAxisAngle(1, 0, 0, radiansToDegrees(Math.PI/3)));
1268            canvas.concat(new DOMMatrix().rotateAxisAngle(0, 1, 0, radiansToDegrees(Math.PI/4)));
1269            canvas.concat(new DOMMatrix().rotateAxisAngle(0, 0, 1, radiansToDegrees(Math.PI/16)));
1270            canvas.concat(new DOMMatrix().translate(-CANVAS_WIDTH/2, 0, 0));
1271
1272            const localMatrix = canvas.getLocalToDevice();
1273            expect4x4MatricesToMatch([
1274             0.693519, -0.137949,  0.707106,   91.944030,
1275             0.698150,  0.370924, -0.612372, -209.445297,
1276            -0.177806,  0.918359,  0.353553,   53.342029,
1277             0       ,  0       ,  0       ,    1       ], localMatrix);
1278
1279            // Draw some stripes to help the eye detect the turn
1280            const stripeWidth = 10;
1281            paint.setColor(CanvasKit.BLACK);
1282            for (let i = 0; i < CANVAS_WIDTH; i += 2*stripeWidth) {
1283                canvas.drawRect(CanvasKit.LTRBRect(i, 0, i + stripeWidth, CANVAS_HEIGHT), paint);
1284            }
1285
1286            paint.setColor(CanvasKit.YELLOW);
1287            canvas.drawPath(path, paint);
1288            paint.delete();
1289            path.delete();
1290        });
1291
1292        it('throws if an invalid matrix is passed in', () => {
1293            let threw;
1294            try {
1295                CanvasKit.ImageFilter.MakeMatrixTransform(
1296                  'invalid matrix value',
1297                  { filter: CanvasKit.FilterMode.Linear },
1298                  null
1299                )
1300                threw = false;
1301            } catch (e) {
1302                threw = true;
1303            }
1304            expect(threw).toBeTrue();
1305        });
1306    }); // end describe('DOMMatrix support')
1307
1308    it('can call subarray on a Malloced object', () => {
1309        const mThings = CanvasKit.Malloc(Float32Array, 6);
1310        mThings.toTypedArray().set([4, 5, 6, 7, 8, 9]);
1311        expectTypedArraysToEqual(Float32Array.of(4, 5, 6, 7, 8, 9), mThings.toTypedArray());
1312        expectTypedArraysToEqual(Float32Array.of(4, 5, 6, 7, 8, 9), mThings.subarray(0));
1313        expectTypedArraysToEqual(Float32Array.of(7, 8, 9), mThings.subarray(3));
1314        expectTypedArraysToEqual(Float32Array.of(7), mThings.subarray(3, 4));
1315        expectTypedArraysToEqual(Float32Array.of(7, 8), mThings.subarray(3, 5));
1316
1317        // mutations on the subarray affect the entire array (because they are backed by the
1318        // same memory)
1319        mThings.subarray(3)[0] = 100.5;
1320        expectTypedArraysToEqual(Float32Array.of(4, 5, 6, 100.5, 8, 9), mThings.toTypedArray());
1321        CanvasKit.Free(mThings);
1322    });
1323
1324    function expectTypedArraysToEqual(expected, actual) {
1325        expect(expected.constructor.name).toEqual(actual.constructor.name);
1326        expect(expected.length).toEqual(actual.length);
1327        for (let i = 0; i < expected.length; i++) {
1328            expect(expected[i]).toBeCloseTo(actual[i], 5, `element ${i}`);
1329        }
1330    }
1331
1332    it('can create a RasterDirectSurface', () => {
1333        // Make enough space for a 5x5 8888 surface (4 bytes for R, G, B, A)
1334        const rdsData = CanvasKit.Malloc(Uint8Array, 5 * 5 * 4);
1335        const surface = CanvasKit.MakeRasterDirectSurface({
1336            'width': 5,
1337            'height': 5,
1338            'colorType': CanvasKit.ColorType.RGBA_8888,
1339            'alphaType': CanvasKit.AlphaType.Premul,
1340            'colorSpace': CanvasKit.ColorSpace.SRGB,
1341        }, rdsData, 5 * 4);
1342
1343        surface.getCanvas().clear(CanvasKit.Color(200, 100, 0, 0.8));
1344        const pixels = rdsData.toTypedArray();
1345        // Check that the first pixels colors are right.
1346        expect(pixels[0]).toEqual(160); // red (premul, 0.8 * 200)
1347        expect(pixels[1]).toEqual(80); // green (premul, 0.8 * 100)
1348        expect(pixels[2]).toEqual(0); // blue (premul, not that it matters)
1349        expect(pixels[3]).toEqual(204); // alpha (0.8 * 255)
1350        surface.delete();
1351        CanvasKit.Free(rdsData);
1352    });
1353
1354    gm('makeImageFromTextureSource_TypedArray', (canvas, _, surface) => {
1355        if (!CanvasKit.gpu) {
1356            return SHOULD_SKIP;
1357        }
1358        // This creates and draws an Unpremul Image that is 1 pixel wide, 4 pixels tall with
1359        // the colors listed below.
1360        const pixels = Uint8Array.from([
1361            255,   0,   0, 255, // opaque red
1362              0, 255,   0, 255, // opaque green
1363              0,   0, 255, 255, // opaque blue
1364            255,   0, 255, 100, // transparent purple
1365        ]);
1366        const img = surface.makeImageFromTextureSource(pixels, {
1367              'width': 1,
1368              'height': 4,
1369              'alphaType': CanvasKit.AlphaType.Unpremul,
1370              'colorType': CanvasKit.ColorType.RGBA_8888,
1371            });
1372        canvas.drawImage(img, 1, 1, null);
1373
1374        const info = img.getImageInfo();
1375        expect(info).toEqual({
1376          'width': 1,
1377          'height': 4,
1378          'alphaType': CanvasKit.AlphaType.Unpremul,
1379          'colorType': CanvasKit.ColorType.RGBA_8888,
1380        });
1381        const cs = img.getColorSpace();
1382        expect(CanvasKit.ColorSpace.Equals(cs, CanvasKit.ColorSpace.SRGB)).toBeTruthy();
1383
1384        cs.delete();
1385        img.delete();
1386    });
1387
1388    gm('makeImageFromTextureSource_PremulTypedArray', (canvas, _, surface) => {
1389        if (!CanvasKit.gpu) {
1390            return SHOULD_SKIP;
1391        }
1392        // This creates and draws an Unpremul Image that is 1 pixel wide, 4 pixels tall with
1393        // the colors listed below.
1394        const pixels = Uint8Array.from([
1395            255,   0,   0, 255, // opaque red
1396              0, 255,   0, 255, // opaque green
1397              0,   0, 255, 255, // opaque blue
1398            100,   0, 100, 100, // transparent purple
1399        ]);
1400        const img = surface.makeImageFromTextureSource(pixels, {
1401              'width': 1,
1402              'height': 4,
1403              'alphaType': CanvasKit.AlphaType.Premul,
1404              'colorType': CanvasKit.ColorType.RGBA_8888,
1405            }, /*srcIsPremul = */true);
1406        canvas.drawImage(img, 1, 1, null);
1407
1408        const info = img.getImageInfo();
1409        expect(info).toEqual({
1410          'width': 1,
1411          'height': 4,
1412          'alphaType': CanvasKit.AlphaType.Premul,
1413          'colorType': CanvasKit.ColorType.RGBA_8888,
1414        });
1415        img.delete();
1416    });
1417
1418    gm('makeImageFromTextureSource_imgElement', (canvas, _, surface) => {
1419        if (!CanvasKit.gpu) {
1420            return SHOULD_SKIP;
1421        }
1422        // This makes an offscreen <img> with the provided source.
1423        const imageEle = new Image();
1424        imageEle.src = '/assets/mandrill_512.png';
1425
1426        // We need to wait until the image is loaded before the texture can use it. For good
1427        // measure, we also wait for it to be decoded.
1428        return imageEle.decode().then(() => {
1429            const img = surface.makeImageFromTextureSource(imageEle);
1430            // Make sure the texture is properly written to and Skia does not draw over it by
1431            // by accident.
1432            canvas.clear(CanvasKit.RED);
1433            surface.updateTextureFromSource(img, imageEle);
1434            canvas.drawImage(img, 0, 0, null);
1435
1436            const info = img.getImageInfo();
1437            expect(info).toEqual({
1438              'width': 512, // width and height should be derived from the image.
1439              'height': 512,
1440              'alphaType': CanvasKit.AlphaType.Unpremul,
1441              'colorType': CanvasKit.ColorType.RGBA_8888,
1442            });
1443            img.delete();
1444        });
1445    });
1446
1447    gm('MakeLazyImageFromTextureSource_imgElement', (canvas) => {
1448        if (!CanvasKit.gpu) {
1449            return SHOULD_SKIP;
1450        }
1451        // This makes an offscreen <img> with the provided source.
1452        const imageEle = new Image();
1453        imageEle.src = '/assets/mandrill_512.png';
1454
1455        // We need to wait until the image is loaded before the texture can use it. For good
1456        // measure, we also wait for it to be decoded.
1457        return imageEle.decode().then(() => {
1458            const img = CanvasKit.MakeLazyImageFromTextureSource(imageEle);
1459            canvas.drawImage(img, 5, 5, null);
1460
1461            const info = img.getImageInfo();
1462            expect(info).toEqual({
1463              'width': 512, // width and height should be derived from the image.
1464              'height': 512,
1465              'alphaType': CanvasKit.AlphaType.Unpremul,
1466              'colorType': CanvasKit.ColorType.RGBA_8888,
1467            });
1468            img.delete();
1469        });
1470    });
1471
1472    gm('MakeLazyImageFromTextureSource_imageInfo', (canvas) => {
1473        if (!CanvasKit.gpu) {
1474            return SHOULD_SKIP;
1475        }
1476        // This makes an offscreen <img> with the provided source.
1477        // flutter_106433.png has transparent pixels, which is required to test the Premul
1478        // behavior. https://github.com/flutter/flutter/issues/106433
1479        const imageEle = new Image();
1480        imageEle.src = '/assets/flutter_106433.png';
1481
1482        // We need to wait until the image is loaded before the texture can use it. For good
1483        // measure, we also wait for it to be decoded.
1484        return imageEle.decode().then(() => {
1485            const img = CanvasKit.MakeLazyImageFromTextureSource(imageEle, {
1486              'width': 183,
1487              'height': 180,
1488              'alphaType': CanvasKit.AlphaType.Premul,
1489              'colorType': CanvasKit.ColorType.RGBA_8888,
1490            });
1491            canvas.clear(CanvasKit.RED);
1492            canvas.drawImage(img, 20, 20, null);
1493
1494            img.delete();
1495        });
1496    });
1497
1498    gm('MakeLazyImageFromTextureSource_readPixels', (canvas) => {
1499        if (!CanvasKit.gpu) {
1500            return SHOULD_SKIP;
1501        }
1502
1503        // This makes an offscreen <img> with the provided source.
1504        const imageEle = new Image();
1505        imageEle.src = '/assets/mandrill_512.png';
1506
1507        // We need to wait until the image is loaded before the texture can use it. For good
1508        // measure, we also wait for it to be decoded.
1509        return imageEle.decode().then(() => {
1510            const img = CanvasKit.MakeLazyImageFromTextureSource(imageEle);
1511            const imgInfo = {
1512              'width': 512,
1513              'height': 512,
1514              'alphaType': CanvasKit.AlphaType.Unpremul,
1515              'colorType': CanvasKit.ColorType.RGBA_8888,
1516              'colorSpace': CanvasKit.ColorSpace.SRGB
1517            };
1518            const src = CanvasKit.XYWHRect(0, 0, 512, 512);
1519            const pixels = img.readPixels(0, 0, imgInfo);
1520            expect(pixels).toBeTruthy();
1521            // Make a new image from reading the pixels of the texture-backed image,
1522            // then draw that new image to a canvas and verify it works.
1523            const newImg = CanvasKit.MakeImage(imgInfo, pixels, 512 * 4);
1524            canvas.drawImageRectCubic(newImg, src, CanvasKit.XYWHRect(256, 0, 256, 256), 1/3, 1/3);
1525            canvas.drawImageRectCubic(img, src, CanvasKit.XYWHRect(0, 0, 256, 256), 1/3, 1/3);
1526
1527            const font = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 20);
1528            const paint = new CanvasKit.Paint();
1529            paint.setColor(CanvasKit.BLACK);
1530            canvas.drawText('original', 100, 280, paint, font);
1531            canvas.drawText('readPixels', 356, 280, paint, font);
1532
1533            img.delete();
1534            newImg.delete();
1535            font.delete();
1536            paint.delete();
1537        });
1538    });
1539
1540    // This tests the process of turning a SkPicture that contains texture-backed images into
1541    // an SkImage that can be drawn on a different surface. It does so by reading the pixels
1542    // back and creating a new SkImage from them.
1543    gm('MakeLazyImageFromTextureSource_makeImageSnapshot', (canvas) => {
1544        if (!CanvasKit.gpu) {
1545            return SHOULD_SKIP;
1546        }
1547
1548        // This makes an offscreen <img> with the provided source.
1549        const imageEle = new Image();
1550        imageEle.src = '/assets/mandrill_512.png';
1551
1552        // We need to wait until the image is loaded before the texture can use it. For good
1553        // measure, we also wait for it to be decoded.
1554        return imageEle.decode().then(() => {
1555            const img = CanvasKit.MakeLazyImageFromTextureSource(imageEle);
1556
1557            const recorder = new CanvasKit.PictureRecorder();
1558            const recorderCanvas = recorder.beginRecording();
1559            const src = CanvasKit.XYWHRect(0, 0, 512, 512);
1560            recorderCanvas.drawImageRectCubic(img, src, src, 1/3, 1/3);
1561            const picture = recorder.finishRecordingAsPicture();
1562
1563            // Draw the picture to an off-screen canvas
1564            const glCanvas = document.createElement("canvas");
1565            glCanvas.width = 512;
1566            glCanvas.height = 512;
1567            const surface = CanvasKit.MakeWebGLCanvasSurface(glCanvas);
1568            const surfaceCanvas = surface.getCanvas();
1569            surfaceCanvas.drawPicture(picture);
1570            const font = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 20);
1571            const paint = new CanvasKit.Paint();
1572            paint.setColor(CanvasKit.WHITE);
1573            // Put some text onto this surface, just to verify the readback works.
1574            surfaceCanvas.drawText('This is on the picture', 10, 50, paint, font);
1575            // Then read the surface as an image and read the pixels from there.
1576            const imgFromPicture = surface.makeImageSnapshot();
1577            const imgInfo = {
1578              'width': 512,
1579              'height': 512,
1580              'alphaType': CanvasKit.AlphaType.Unpremul,
1581              'colorType': CanvasKit.ColorType.RGBA_8888,
1582              'colorSpace': CanvasKit.ColorSpace.SRGB
1583            };
1584            const pixels = imgFromPicture.readPixels(0, 0, imgInfo);
1585            expect(pixels).toBeTruthy();
1586            // Create a new image with those pixels, which can be drawn on the test surface.
1587            const bitmapImg = CanvasKit.MakeImage(imgInfo, pixels, 512 * 4);
1588
1589            canvas.drawImageRectCubic(bitmapImg, src, CanvasKit.XYWHRect(256, 0, 256, 256), 1/3, 1/3);
1590            canvas.drawImageRectCubic(img, src, CanvasKit.XYWHRect(0, 0, 256, 256), 1/3, 1/3);
1591
1592            paint.setColor(CanvasKit.BLACK);
1593            canvas.drawText('original', 100, 280, paint, font);
1594            canvas.drawText('makeImageSnapshot', 290, 280, paint, font);
1595
1596            img.delete();
1597            imgFromPicture.delete();
1598            bitmapImg.delete();
1599            picture.delete();
1600            surface.delete();
1601            font.delete();
1602            paint.delete();
1603            recorder.delete();
1604        });
1605    });
1606
1607
1608    it('encodes images in three different ways', () => {
1609        // This creates and draws an Image that is 1 pixel wide, 4 pixels tall with
1610        // the colors listed below.
1611        const pixels = Uint8Array.from([
1612            255,   0,   0, 255, // opaque red
1613              0, 255,   0, 255, // opaque green
1614              0,   0, 255, 255, // opaque blue
1615            255,   0, 255, 100, // transparent purple
1616        ]);
1617        const img = CanvasKit.MakeImage({
1618          'width': 1,
1619          'height': 4,
1620          'alphaType': CanvasKit.AlphaType.Unpremul,
1621          'colorType': CanvasKit.ColorType.RGBA_8888,
1622          'colorSpace': CanvasKit.ColorSpace.SRGB
1623        }, pixels, 4);
1624
1625        let bytes = img.encodeToBytes(CanvasKit.ImageFormat.PNG, 100);
1626        assertBytesDecodeToImage(bytes, 'png');
1627        bytes = img.encodeToBytes(CanvasKit.ImageFormat.JPEG, 90);
1628        assertBytesDecodeToImage(bytes, 'jpeg');
1629        bytes = img.encodeToBytes(CanvasKit.ImageFormat.WEBP, 100);
1630        assertBytesDecodeToImage(bytes, 'webp');
1631
1632        img.delete();
1633    });
1634
1635    function assertBytesDecodeToImage(bytes, format) {
1636        expect(bytes).toBeTruthy('null output for ' + format);
1637        const img = CanvasKit.MakeImageFromEncoded(bytes);
1638        expect(img).toBeTruthy('Could not decode result from '+ format);
1639        img && img.delete();
1640    }
1641
1642    it('can make a render target', () => {
1643        if (!CanvasKit.gpu) {
1644            return;
1645        }
1646        const canvas = document.getElementById('test');
1647        const context = CanvasKit.GetWebGLContext(canvas);
1648        const grContext = CanvasKit.MakeGrContext(context);
1649        expect(grContext).toBeTruthy();
1650        const target = CanvasKit.MakeRenderTarget(grContext, 100, 100);
1651        expect(target).toBeTruthy();
1652        target.delete();
1653        grContext.delete();
1654    });
1655
1656    gm('PathEffect_MakePath1D', (canvas) => {
1657        // Based off //docs/examples/skpaint_path_1d_path_effect.cpp
1658
1659        const path = new CanvasKit.Path();
1660        path.addOval(CanvasKit.XYWHRect(0, 0, 16, 6));
1661
1662        const paint = new CanvasKit.Paint();
1663        const effect = CanvasKit.PathEffect.MakePath1D(
1664           path, 32, 0, CanvasKit.Path1DEffect.Rotate,
1665        );
1666        paint.setColor(CanvasKit.Color(94, 53, 88, 1)); // deep purple
1667        paint.setPathEffect(effect);
1668        paint.setAntiAlias(true);
1669        canvas.drawCircle(128, 128, 122, paint);
1670
1671        path.delete();
1672        effect.delete();
1673        paint.delete();
1674    });
1675
1676    gm('Can_Interpolate_Path', (canvas) => {
1677        const paint = new CanvasKit.Paint();
1678        paint.setAntiAlias(true);
1679        paint.setStyle(CanvasKit.PaintStyle.Stroke);
1680        paint.setStrokeWidth(2);
1681        const path = new CanvasKit.Path()
1682        const path2 = new CanvasKit.Path();
1683        const path3 = new CanvasKit.Path();
1684        path3.addCircle(30, 30, 10);
1685        path.moveTo(20, 20);
1686        path.lineTo(40, 40);
1687        path.lineTo(20, 40);
1688        path.lineTo(40, 20);
1689        path.close();
1690        path2.addRect([20, 20, 40, 40]);
1691        path2.transform(CanvasKit.Matrix.translated(40, 0));
1692        const canInterpolate1 = CanvasKit.Path.CanInterpolate(path, path2);
1693        expect(canInterpolate1).toBe(true);
1694        const canInterpolate2 = CanvasKit.Path.CanInterpolate(path, path3);
1695        expect(canInterpolate2).toBe(false);
1696        canvas.drawPath(path, paint);
1697        canvas.drawPath(path2, paint);
1698        path3.transform(CanvasKit.Matrix.translated(80, 0));
1699        canvas.drawPath(path3, paint);
1700        path.delete();
1701        path2.delete();
1702        path3.delete();
1703        paint.delete();
1704    });
1705
1706    gm('Interpolate_Paths', (canvas) => {
1707        const paint = new CanvasKit.Paint();
1708        paint.setAntiAlias(true);
1709        paint.setStyle(CanvasKit.PaintStyle.Stroke);
1710        paint.setStrokeWidth(2);
1711        const path = new CanvasKit.Path()
1712        const path2 = new CanvasKit.Path();
1713        path.moveTo(20, 20);
1714        path.lineTo(40, 40);
1715        path.lineTo(20, 40);
1716        path.lineTo(40, 20);
1717        path.close();
1718        path2.addRect([20, 20, 40, 40]);
1719        for (let i = 0; i <= 1; i += 1.0 / 6) {
1720          const interp = CanvasKit.Path.MakeFromPathInterpolation(path, path2, i);
1721          canvas.drawPath(interp, paint);
1722          interp.delete();
1723          canvas.translate(30, 0);
1724        }
1725        path.delete();
1726        path2.delete();
1727        paint.delete();
1728    });
1729
1730    gm('Draw_Circle', (canvas) => {
1731        const paint = new CanvasKit.Paint();
1732        paint.setColor(CanvasKit.Color(59, 53, 94, 1));
1733        const path = new CanvasKit.Path();
1734        path.moveTo(256, 256);
1735        path.addCircle(256, 256, 256);
1736        canvas.drawPath(path, paint);
1737        path.delete();
1738        paint.delete();
1739    });
1740
1741    gm('PathEffect_MakePath2D', (canvas) => {
1742        // Based off //docs/examples/skpaint_path_2d_path_effect.cpp
1743
1744        const path = new CanvasKit.Path();
1745        path.moveTo(20, 30);
1746        const points = [20, 20, 10, 30, 0, 30, 20, 10, 30, 10, 40, 0, 40, 10,
1747                        50, 10, 40, 20, 40, 30, 20, 50, 20, 40, 30, 30, 20, 30];
1748        for (let i = 0; i < points.length; i += 2) {
1749            path.lineTo(points[i], points[i+1]);
1750        }
1751
1752        const paint = new CanvasKit.Paint();
1753        const effect = CanvasKit.PathEffect.MakePath2D(
1754          CanvasKit.Matrix.scaled(40, 40), path
1755        );
1756        paint.setColor(CanvasKit.Color(53, 94, 59, 1)); // hunter green
1757        paint.setPathEffect(effect);
1758        paint.setAntiAlias(true);
1759        canvas.drawRect(CanvasKit.LTRBRect(-20, -20, 300, 300), paint);
1760
1761        path.delete();
1762        effect.delete();
1763        paint.delete();
1764    });
1765
1766    gm('PathEffect_MakeLine2D', (canvas) => {
1767        // Based off //docs/examples/skpaint_line_2d_path_effect.cpp
1768
1769        const lattice = CanvasKit.Matrix.multiply(
1770            CanvasKit.Matrix.scaled(8, 8),
1771            CanvasKit.Matrix.rotated(Math.PI / 6),
1772        );
1773
1774        const paint = new CanvasKit.Paint();
1775        const effect = CanvasKit.PathEffect.MakeLine2D(
1776          2, lattice,
1777        );
1778        paint.setColor(CanvasKit.Color(59, 53, 94, 1)); // dark blue
1779        paint.setPathEffect(effect);
1780        paint.setAntiAlias(true);
1781        canvas.drawRect(CanvasKit.LTRBRect(20, 20, 300, 300), paint);
1782
1783        effect.delete();
1784        paint.delete();
1785    });
1786
1787    gm('ImageFilter_MakeBlend', (canvas) => {
1788        const redCF = CanvasKit.ColorFilter.MakeBlend(
1789                CanvasKit.Color(255, 0, 0, 0.4), CanvasKit.BlendMode.SrcOver);
1790        const redIF = CanvasKit.ImageFilter.MakeColorFilter(redCF, null);
1791        const blueCF = CanvasKit.ColorFilter.MakeBlend(
1792                CanvasKit.Color(0, 0, 255, 0.7), CanvasKit.BlendMode.SrcOver);
1793        const blueIF = CanvasKit.ImageFilter.MakeColorFilter(blueCF, null);
1794
1795        const BOX_SIZE = 100;
1796        const SWATCH_SIZE = 80;
1797        const MARGIN = (BOX_SIZE - SWATCH_SIZE) / 2;
1798        const COLS_PER_ROW = CANVAS_WIDTH / BOX_SIZE;
1799        const blends = ['Clear', 'Src', 'Dst', 'SrcOver', 'DstOver', 'SrcIn', 'DstIn', 'SrcOut',
1800                        'DstOut', 'SrcATop', 'DstATop', 'Xor', 'Plus', 'Modulate', 'Screen',
1801                        'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight',
1802                        'SoftLight', 'Difference', 'Exclusion', 'Multiply', 'Hue', 'Saturation',
1803                        'Color', 'Luminosity'];
1804        const paint = new CanvasKit.Paint();
1805        // Put a dark green on the paint itself.
1806        paint.setColor(CanvasKit.Color(0, 255, 0, 0.2));
1807
1808        const font = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 10);
1809        const textPaint = new CanvasKit.Paint();
1810        textPaint.setColor(CanvasKit.BLACK);
1811
1812        for (let i = 0; i < blends.length; i++) {
1813            const filter = CanvasKit.ImageFilter.MakeBlend(CanvasKit.BlendMode[blends[i]],
1814                                                           redIF, blueIF);
1815            const col = i % COLS_PER_ROW, row = Math.floor(i / COLS_PER_ROW);
1816
1817            paint.setImageFilter(filter);
1818            canvas.save();
1819
1820            canvas.clipRect(CanvasKit.XYWHRect(col * BOX_SIZE + MARGIN, row * BOX_SIZE + MARGIN, SWATCH_SIZE, SWATCH_SIZE),
1821                            CanvasKit.ClipOp.Intersect);
1822            canvas.drawPaint(paint);
1823            canvas.restore();
1824
1825            canvas.drawText(blends[i], col * BOX_SIZE + 30, row * BOX_SIZE + BOX_SIZE, textPaint, font);
1826            filter.delete();
1827        }
1828        redCF.delete();
1829        redIF.delete();
1830        blueCF.delete();
1831        blueIF.delete();
1832        paint.delete();
1833    });
1834
1835    gm('ImageFilter_MakeDilate', (canvas, fetchedByteBuffers) => {
1836
1837        const paint = new CanvasKit.Paint();
1838        const dilate = CanvasKit.ImageFilter.MakeDilate(2, 10, null);
1839        paint.setImageFilter(dilate);
1840
1841        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
1842        expect(img).toBeTruthy();
1843        canvas.drawImage(img, 10, 20, paint);
1844
1845        img.delete();
1846        paint.delete();
1847        dilate.delete();
1848    }, '/assets/mandrill_512.png');
1849
1850    gm('ImageFilter_MakeErode', (canvas, fetchedByteBuffers) => {
1851
1852        const paint = new CanvasKit.Paint();
1853        const erode = CanvasKit.ImageFilter.MakeErode(2, 10, null);
1854        paint.setImageFilter(erode);
1855
1856        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
1857        expect(img).toBeTruthy();
1858        canvas.drawImage(img, 10, 20, paint);
1859
1860        img.delete();
1861        paint.delete();
1862        erode.delete();
1863    }, '/assets/mandrill_512.png');
1864
1865    gm('ImageFilter_MakeDisplacementMap', (canvas, fetchedByteBuffers) => {
1866        // See https://www.smashingmagazine.com/2021/09/deep-dive-wonderful-world-svg-displacement-filtering/
1867        // for a good writeup of displacement filters.
1868        // https://jsfiddle.skia.org/canvaskit/27ba8450861fd4ec9632276dcdb2edd0d967070c2bb44e60f803597ff78ccda2
1869        // is a way to play with how the color and scale interact.
1870
1871        // As implemented, if the displacement map is smaller than the image * scale, things can
1872        // look strange, with a copy of the image in the background. Making it the size
1873        // of the canvas will at least mask the "ghost" image that shows up in the background.
1874        const DISPLACEMENT_SIZE = CANVAS_HEIGHT;
1875        const IMAGE_SIZE = 512;
1876        const SCALE = 30;
1877        const pixels = [];
1878        // Create a soft, oblong grid shape. This sort of makes it look like there is some warbly
1879        // glass in front of the mandrill image.
1880        for (let y = 0; y < DISPLACEMENT_SIZE; y++) {
1881            for (let x = 0; x < DISPLACEMENT_SIZE; x++) {
1882                if (x < SCALE/2 || y < SCALE/2 || x >= IMAGE_SIZE - SCALE/2 || y >= IMAGE_SIZE - SCALE/2) {
1883                    // grey means no displacement. If we displace off the edge of the image, we'll
1884                    // see strange transparent pixels showing up around the edges.
1885                    pixels.push(127, 127, 127, 255);
1886                } else {
1887                    // Scale our sine wave from [-1, 1] to [0, 255] (which will be scaled by the
1888                    // DisplacementMap back to [-1, 1].
1889                    // Setting the alpha to be 255 doesn't impact the translation, but does
1890                    // let us draw the image if we like.
1891                    pixels.push(Math.sin(x/5)*255+127, Math.sin(y/3)*255+127, 0, 255);
1892                }
1893            }
1894        }
1895        const mapImg = CanvasKit.MakeImage({
1896            width: DISPLACEMENT_SIZE,
1897            height: DISPLACEMENT_SIZE,
1898            // Premul is important - we do not want further division of our channels.
1899            alphaType: CanvasKit.AlphaType.Premul,
1900            colorType: CanvasKit.ColorType.RGBA_8888,
1901            colorSpace: CanvasKit.ColorSpace.SRGB,
1902        }, Uint8ClampedArray.from(pixels), 4 * DISPLACEMENT_SIZE);
1903        // To see just the displacement map, uncomment the lines below
1904        // canvas.drawImage(mapImg, 0, 0, null);
1905        // return;
1906        const map = CanvasKit.ImageFilter.MakeImage(mapImg, {C: 1/3, B:1/3});
1907
1908        const displaced = CanvasKit.ImageFilter.MakeDisplacementMap(CanvasKit.ColorChannel.Red,
1909                                CanvasKit.ColorChannel.Green, SCALE, map, null);
1910        const paint = new CanvasKit.Paint();
1911        paint.setImageFilter(displaced);
1912        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
1913        expect(img).toBeTruthy();
1914        canvas.drawImage(img, 0, 0, paint);
1915
1916        mapImg.delete();
1917        img.delete();
1918        map.delete();
1919        paint.delete();
1920        displaced.delete();
1921    }, '/assets/mandrill_512.png');
1922
1923    gm('ImageFilter_MakeDropShadow', (canvas, fetchedByteBuffers) => {
1924
1925        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
1926        expect(img).toBeTruthy();
1927
1928        const drop = CanvasKit.ImageFilter.MakeDropShadow(10, -30, 4.0, 2.0, CanvasKit.MAGENTA, null);
1929        const paint = new CanvasKit.Paint();
1930        paint.setImageFilter(drop)
1931        canvas.drawImage(img, 50, 50, paint);
1932
1933        img.delete();
1934        paint.delete();
1935        drop.delete();
1936    }, '/assets/mandrill_512.png');
1937
1938    gm('ImageFilter_MakeDropShadowOnly', (canvas, fetchedByteBuffers) => {
1939
1940        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
1941        expect(img).toBeTruthy();
1942
1943        const drop = CanvasKit.ImageFilter.MakeDropShadowOnly(10, -30, 4.0, 2.0, CanvasKit.MAGENTA, null);
1944        const paint = new CanvasKit.Paint();
1945        paint.setImageFilter(drop)
1946        canvas.drawImage(img, 50, 50, paint);
1947        img.delete();
1948        paint.delete();
1949        drop.delete();
1950    }, '/assets/mandrill_512.png');
1951
1952    gm('ImageFilter_MakeOffset', (canvas, fetchedByteBuffers) => {
1953
1954        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
1955        expect(img).toBeTruthy();
1956
1957        const offset = CanvasKit.ImageFilter.MakeOffset(30, -130, null);
1958        const paint = new CanvasKit.Paint();
1959        paint.setImageFilter(offset);
1960        canvas.drawImage(img, 50, 50, paint);
1961        img.delete();
1962        paint.delete();
1963        offset.delete();
1964    }, '/assets/mandrill_512.png');
1965
1966    gm('ImageFilter_MakeShader', (canvas) => {
1967
1968        const rt = CanvasKit.RuntimeEffect.Make(`
1969uniform float4 color;
1970half4 main(vec2 fragcoord) {
1971    return vec4(color);
1972}
1973`);
1974        const shader = rt.makeShader([0.0, 0.0, 1.0, 0.5]);
1975        const filter = CanvasKit.ImageFilter.MakeShader(shader);
1976        const paint = new CanvasKit.Paint();
1977        paint.setImageFilter(filter);
1978        canvas.drawPaint(paint);
1979        paint.delete();
1980        filter.delete();
1981        shader.delete();
1982        rt.delete();
1983    });
1984
1985    it('can create, delete WebGL contexts', () => {
1986        if (!CanvasKit.webgl) {
1987            return SHOULD_SKIP;
1988        }
1989
1990        const newCanvas = document.createElement('canvas');
1991        expect(newCanvas).toBeTruthy();
1992        const ctx = CanvasKit.GetWebGLContext(newCanvas);
1993        expect(ctx).toBeGreaterThan(0);
1994
1995        const grContext = CanvasKit.MakeWebGLContext(ctx);
1996        expect(grContext).toBeTruthy();
1997
1998        grContext.delete();
1999        expect(grContext.isDeleted()).toBeTrue();
2000    });
2001
2002    it('can create, release, and delete WebGL contexts', () => {
2003        if (!CanvasKit.webgl) {
2004            return SHOULD_SKIP;
2005        }
2006
2007        const newCanvas = document.createElement('canvas');
2008        expect(newCanvas).toBeTruthy();
2009        const ctx = CanvasKit.GetWebGLContext(newCanvas);
2010        expect(ctx).toBeGreaterThan(0);
2011
2012        const grContext = CanvasKit.MakeWebGLContext(ctx);
2013        expect(grContext).toBeTruthy();
2014
2015        grContext.releaseResourcesAndAbandonContext();
2016
2017        grContext.delete();
2018        expect(grContext.isDeleted()).toBeTrue();
2019    });
2020
2021    it('can provide sample count and stencil parameters to onscreen surface', () => {
2022        if (!CanvasKit.webgl) {
2023            return SHOULD_SKIP;
2024        }
2025        const paramCanvas = document.createElement('canvas');
2026        const gl = paramCanvas.getContext('webgl');
2027        var sample = gl.getParameter(gl.SAMPLES);
2028        var stencil = gl.getParameter(gl.STENCIL_BITS);
2029
2030        const newCanvas = document.createElement('canvas');
2031        const ctx = CanvasKit.GetWebGLContext(newCanvas);
2032        const grContext = CanvasKit.MakeWebGLContext(ctx);
2033        expect(grContext).toBeTruthy();
2034
2035        var surface =  CanvasKit.MakeOnScreenGLSurface(grContext, 100, 100, CanvasKit.ColorSpace.SRGB, sample, stencil);
2036        expect(surface).toBeTruthy();
2037    });
2038});
2039