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