• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1describe('Core canvas behavior', () => {
2    let container;
3
4    beforeEach(async () => {
5        await LoadCanvasKit;
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 rcanvas = spr.beginRecording(
20                        CanvasKit.LTRBRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT));
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
37        const bytes = pic.serialize();
38        expect(bytes).toBeTruthy();
39
40        pic.delete();
41    });
42
43    const uIntColorToCanvasKitColor = (c) => {
44        return CanvasKit.Color(
45         (c >> 16) & 0xFF,
46         (c >>  8) & 0xFF,
47         (c >>  0) & 0xFF,
48        ((c >> 24) & 0xFF) / 255
49      );
50    };
51
52    it('can compute tonal colors', () => {
53        const input = {
54            ambient: CanvasKit.BLUE,
55            spot: CanvasKit.RED,
56        };
57        const out = CanvasKit.computeTonalColors(input);
58        expect(new Float32Array(out.ambient)).toEqual(CanvasKit.BLACK);
59        const expectedSpot = [0.173, 0, 0, 0.969];
60        expect(out.spot.length).toEqual(4);
61        expect(out.spot[0]).toBeCloseTo(expectedSpot[0], 3);
62        expect(out.spot[1]).toBeCloseTo(expectedSpot[1], 3);
63        expect(out.spot[2]).toBeCloseTo(expectedSpot[2], 3);
64        expect(out.spot[3]).toBeCloseTo(expectedSpot[3], 3);
65    });
66
67    it('can compute tonal colors with malloced values', () => {
68        const ambientColor = CanvasKit.Malloc(Float32Array, 4);
69        ambientColor.toTypedArray().set(CanvasKit.BLUE);
70        const spotColor = CanvasKit.Malloc(Float32Array, 4);
71        spotColor.toTypedArray().set(CanvasKit.RED);
72        const input = {
73            ambient: ambientColor,
74            spot: spotColor,
75        };
76        const out = CanvasKit.computeTonalColors(input);
77        expect(new Float32Array(out.ambient)).toEqual(CanvasKit.BLACK);
78        const expectedSpot = [0.173, 0, 0, 0.969];
79        expect(out.spot.length).toEqual(4);
80        expect(out.spot[0]).toBeCloseTo(expectedSpot[0], 3);
81        expect(out.spot[1]).toBeCloseTo(expectedSpot[1], 3);
82        expect(out.spot[2]).toBeCloseTo(expectedSpot[2], 3);
83        expect(out.spot[3]).toBeCloseTo(expectedSpot[3], 3);
84    });
85
86    // This helper is used for all the MakeImageFromEncoded tests.
87    // TODO(kjlubick): rewrite this and callers to use gm
88    function decodeAndDrawSingleFrameImage(imgName, goldName, done) {
89        const imgPromise = fetch(imgName)
90            .then((response) => response.arrayBuffer());
91        Promise.all([imgPromise, LoadCanvasKit]).then((values) => {
92            const imgData = values[0];
93            expect(imgData).toBeTruthy();
94            catchException(done, () => {
95                let img = CanvasKit.MakeImageFromEncoded(imgData);
96                expect(img).toBeTruthy();
97                const surface = CanvasKit.MakeCanvasSurface('test');
98                expect(surface).toBeTruthy('Could not make surface');
99                if (!surface) {
100                    done();
101                    return;
102                }
103                const canvas = surface.getCanvas();
104                let paint = new CanvasKit.Paint();
105                canvas.drawImage(img, 0, 0, paint);
106
107                paint.delete();
108                img.delete();
109
110                reportSurface(surface, goldName, done);
111            })();
112        });
113    }
114
115    it('can decode and draw a png', (done) => {
116        decodeAndDrawSingleFrameImage('/assets/mandrill_512.png', 'drawImage_png', done);
117    });
118
119    it('can decode and draw a jpg', (done) => {
120        decodeAndDrawSingleFrameImage('/assets/mandrill_h1v1.jpg', 'drawImage_jpg', done);
121    });
122
123    it('can decode and draw a (still) gif', (done) => {
124        decodeAndDrawSingleFrameImage('/assets/flightAnim.gif', 'drawImage_gif', done);
125    });
126
127    it('can decode and draw a still webp', (done) => {
128        decodeAndDrawSingleFrameImage('/assets/color_wheel.webp', 'drawImage_webp', done);
129    });
130
131   it('can readPixels from an Image', (done) => {
132        const imgPromise = fetch('/assets/mandrill_512.png')
133            .then((response) => response.arrayBuffer());
134        Promise.all([imgPromise, LoadCanvasKit]).then((values) => {
135            const imgData = values[0];
136            expect(imgData).toBeTruthy();
137            catchException(done, () => {
138                let img = CanvasKit.MakeImageFromEncoded(imgData);
139                expect(img).toBeTruthy();
140                const imageInfo = {
141                    alphaType: CanvasKit.AlphaType.Unpremul,
142                    colorType: CanvasKit.ColorType.RGBA_8888,
143                    colorSpace: CanvasKit.ColorSpace.SRGB,
144                    width: img.width(),
145                    height: img.height(),
146                };
147                const rowBytes = 4 * img.width();
148
149                const pixels = img.readPixels(0, 0, imageInfo);
150                // We know the image is 512 by 512 pixels in size, each pixel
151                // requires 4 bytes (R, G, B, A).
152                expect(pixels.length).toEqual(512 * 512 * 4);
153
154                // Make enough space for a 5x5 8888 surface (4 bytes for R, G, B, A)
155                const rdsData = CanvasKit.Malloc(Uint8Array, 512 * 5*512 * 4);
156                const pixels2 = rdsData.toTypedArray();
157                pixels2[0] = 127;  // sentinel value, should be overwritten by readPixels.
158                img.readPixels(0, 0, imageInfo, rdsData, rowBytes);
159                expect(rdsData.toTypedArray()[0]).toEqual(pixels[0]);
160
161                img.delete();
162                CanvasKit.Free(rdsData);
163                done();
164            })();
165        });
166    });
167
168    gm('drawDrawable_animated_gif', (canvas, fetchedByteBuffers) => {
169        let aImg = CanvasKit.MakeAnimatedImageFromEncoded(fetchedByteBuffers[0]);
170        expect(aImg).toBeTruthy();
171        expect(aImg.getRepetitionCount()).toEqual(-1); // infinite loop
172        expect(aImg.width()).toEqual(320);
173        expect(aImg.height()).toEqual(240);
174        expect(aImg.getFrameCount()).toEqual(60);
175
176        const drawCurrentFrame = function(x, y) {
177            let img = aImg.makeImageAtCurrentFrame();
178            canvas.drawImage(img, x, y, null);
179            img.delete();
180        }
181
182        drawCurrentFrame(0, 0);
183
184        let c = aImg.decodeNextFrame();
185        expect(c).not.toEqual(-1);
186        drawCurrentFrame(300, 0);
187        for(let i = 0; i < 10; i++) {
188            c = aImg.decodeNextFrame();
189            expect(c).not.toEqual(-1);
190        }
191        drawCurrentFrame(0, 300);
192        for(let i = 0; i < 10; i++) {
193            c = aImg.decodeNextFrame();
194            expect(c).not.toEqual(-1);
195        }
196        drawCurrentFrame(300, 300);
197
198        aImg.delete();
199    }, '/assets/flightAnim.gif');
200
201    gm('exif_orientation', (canvas, fetchedByteBuffers) => {
202        canvas.clear(CanvasKit.WHITE);
203        const paint = new CanvasKit.Paint();
204        const font = new CanvasKit.Font(null, 14);
205        canvas.drawText('The following heart should be rotated 90 CCW due to exif.',
206            5, 25, paint, font);
207
208        // TODO(kjlubick) it would be nice to also to test MakeAnimatedImageFromEncoded but
209        //   I could not create a sample animated image that worked.
210        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
211        expect(img).toBeTruthy();
212        canvas.drawImage(img, 5, 35, null);
213
214        img.delete();
215        paint.delete();
216        font.delete();
217    }, '/assets/exif_rotated_heart.jpg');
218
219    gm('1x4_from_scratch', (canvas) => {
220        canvas.clear(CanvasKit.WHITE);
221        const paint = new CanvasKit.Paint();
222
223        // This creates and draws an Image that is 1 pixel wide, 4 pixels tall with
224        // the colors listed below.
225        const pixels = Uint8Array.from([
226            255,   0,   0, 255, // opaque red
227              0, 255,   0, 255, // opaque green
228              0,   0, 255, 255, // opaque blue
229            255,   0, 255, 100, // transparent purple
230        ]);
231        const img = CanvasKit.MakeImage({
232          'width': 1,
233          'height': 4,
234          'alphaType': CanvasKit.AlphaType.Unpremul,
235          'colorType': CanvasKit.ColorType.RGBA_8888,
236          'colorSpace': CanvasKit.ColorSpace.SRGB
237        }, pixels, 4);
238        canvas.drawImage(img, 1, 1, paint);
239
240        const info = img.getImageInfo();
241        expect(info).toEqual({
242          'width': 1,
243          'height': 4,
244          'alphaType': CanvasKit.AlphaType.Unpremul,
245          'colorType': CanvasKit.ColorType.RGBA_8888,
246        });
247        const cs = img.getColorSpace();
248        expect(CanvasKit.ColorSpace.Equals(cs, CanvasKit.ColorSpace.SRGB)).toBeTruthy();
249
250        cs.delete();
251        img.delete();
252        paint.delete();
253    });
254
255    gm('draw_atlas_with_builders', (canvas, fetchedByteBuffers) => {
256        const atlas = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
257        expect(atlas).toBeTruthy();
258        canvas.clear(CanvasKit.WHITE);
259
260        const paint = new CanvasKit.Paint();
261        paint.setColor(CanvasKit.Color(0, 0, 0, 0.8));
262
263        // Allocate space for 4 rectangles.
264        const srcs = CanvasKit.Malloc(Float32Array, 16);
265        srcs.toTypedArray().set([
266            0,   0, 256, 256, // LTRB
267          256,   0, 512, 256,
268            0, 256, 256, 512,
269          256, 256, 512, 512
270        ]);
271
272        // Allocate space for 4 RSXForms.
273        const dsts = CanvasKit.Malloc(Float32Array, 16);
274        dsts.toTypedArray().set([
275            0.5, 0,  20,  20, // scos, ssin, tx, ty
276            0.5, 0, 300,  20,
277            0.5, 0,  20, 300,
278            0.5, 0, 300, 300
279        ]);
280
281        // Allocate space for 4 colors.
282        const colors = new CanvasKit.Malloc(Uint32Array, 4);
283        colors.toTypedArray().set([
284          CanvasKit.ColorAsInt( 85, 170,  10, 128), // light green
285          CanvasKit.ColorAsInt( 51,  51, 191, 128), // light blue
286          CanvasKit.ColorAsInt(  0,   0,   0, 128),
287          CanvasKit.ColorAsInt(256, 256, 256, 128),
288        ]);
289
290        canvas.drawAtlas(atlas, srcs, dsts, paint, CanvasKit.BlendMode.Modulate, colors);
291
292        atlas.delete();
293        CanvasKit.Free(srcs);
294        CanvasKit.Free(dsts);
295        CanvasKit.Free(colors);
296        paint.delete();
297    }, '/assets/mandrill_512.png');
298
299    gm('draw_atlas_with_arrays', (canvas, fetchedByteBuffers) => {
300        const atlas = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
301        expect(atlas).toBeTruthy();
302        canvas.clear(CanvasKit.WHITE);
303
304        const paint = new CanvasKit.Paint();
305        paint.setColor(CanvasKit.Color(0, 0, 0, 0.8));
306
307        const srcs = [
308            0, 0,  8,  8,
309            8, 0, 16,  8,
310            0, 8,  8, 16,
311            8, 8, 16, 16,
312        ];
313
314        const dsts = [
315            10, 0,   0,   0,
316            10, 0, 100,   0,
317            10, 0,   0, 100,
318            10, 0, 100, 100,
319        ];
320
321        const colors = Uint32Array.of(
322            CanvasKit.ColorAsInt( 85, 170,  10, 128), // light green
323            CanvasKit.ColorAsInt( 51,  51, 191, 128), // light blue
324            CanvasKit.ColorAsInt(  0,   0,   0, 128),
325            CanvasKit.ColorAsInt(255, 255, 255, 128),
326        );
327
328        // sampling for each of the 4 instances
329        const sampling = [
330            null,
331            {B: 0, C: 0.5},
332            {filter: CanvasKit.FilterMode.Nearest, mipmap: CanvasKit.MipmapMode.None},
333            {filter: CanvasKit.FilterMode.Linear,  mipmap: CanvasKit.MipmapMode.Nearest},
334        ];
335
336        // positioning for each of the 4 instances
337        const offset = [
338            [0, 0], [256, 0], [0, 256], [256, 256]
339        ];
340
341        canvas.translate(20, 20);
342        for (let i = 0; i < 4; ++i) {
343            canvas.save();
344            canvas.translate(offset[i][0], offset[i][1]);
345            canvas.drawAtlas(atlas, srcs, dsts, paint, CanvasKit.BlendMode.SrcOver, colors,
346                             sampling[i]);
347            canvas.restore();
348        }
349
350        atlas.delete();
351        paint.delete();
352    }, '/assets/mandrill_16.png');
353
354    gm('draw_patch', (canvas, fetchedByteBuffers) => {
355        const image = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
356        expect(image).toBeTruthy();
357        canvas.clear(CanvasKit.WHITE);
358
359        const paint = new CanvasKit.Paint();
360        const shader = image.makeShaderOptions(CanvasKit.TileMode.Clamp,
361                                               CanvasKit.TileMode.Clamp,
362                                               CanvasKit.FilterMode.Linear,
363                                               CanvasKit.MipmapMode.None);
364        const cubics = [0,0, 80,50, 160,50,
365                        240,0, 200,80, 200,160,
366                        240,240, 160,160, 80,240,
367                        0,240, 50,160, 0,80];
368         const colors = [CanvasKit.RED, CanvasKit.BLUE, CanvasKit.YELLOW, CanvasKit.CYAN];
369         const texs = [0,0, 16,0, 16,16, 0,16];
370
371         const params = [
372             [  0,   0, colors, null, null,   null],
373             [256,   0, null,   texs, shader, null],
374             [  0, 256, colors, texs, shader, null],
375             [256, 256, colors, texs, shader, CanvasKit.BlendMode.Screen],
376         ];
377         for (const p of params) {
378             paint.setShader(p[4]);
379             canvas.save();
380             canvas.translate(p[0], p[1]);
381             canvas.drawPatch(cubics, p[2], p[3], p[5], paint);
382             canvas.restore();
383         }
384        paint.delete();
385    }, '/assets/mandrill_16.png');
386
387    gm('draw_glyphs', (canvas, fetchedByteBuffers) => {
388        canvas.clear(CanvasKit.WHITE);
389
390        const paint = new CanvasKit.Paint();
391        const font = new CanvasKit.Font(null, 24);
392        paint.setAntiAlias(true);
393
394        const DIM = 16; // row/col count for the grid
395        const GAP = 32; // spacing between each glyph
396        const glyphs = new Uint16Array(256);
397        const positions = new Float32Array(256*2);
398        for (let i = 0; i < 256; ++i) {
399            glyphs[i] = i;
400            positions[2*i+0] = (i%DIM) * GAP;
401            positions[2*i+1] = Math.round(i/DIM) * GAP;
402        }
403        canvas.drawGlyphs(glyphs, positions, 16, 20, font, paint);
404
405        font.delete();
406        paint.delete();
407    });
408
409    gm('image_decoding_methods', async (canvas) => {
410        canvas.clear(CanvasKit.WHITE);
411
412        const IMAGE_FILE_PATHS = [
413            '/assets/brickwork-texture.jpg',
414            '/assets/mandrill_512.png',
415            '/assets/color_wheel.gif'
416        ];
417
418        let row = 1;
419        // Test 4 different methods of decoding an image for each of the three images in
420        // IMAGE_FILE_PATHS.
421        // Resulting Images are drawn to visually show that all methods decode correctly.
422        for (const imageFilePath of IMAGE_FILE_PATHS) {
423            const response = await fetch(imageFilePath);
424            const arrayBuffer = await response.arrayBuffer();
425            // response.blob() is preferable when you don't need both a Blob *and* an ArrayBuffer.
426            const blob = new Blob([ arrayBuffer ]);
427
428            // Method 1 - decode TypedArray using wasm codecs:
429            const skImage1 = CanvasKit.MakeImageFromEncoded(arrayBuffer);
430
431            // Method 2 (slower and does not work in Safari) decode using ImageBitmap:
432            const imageBitmap = await createImageBitmap(blob);
433            // Testing showed that transferring an ImageBitmap to a canvas using the 'bitmaprenderer'
434            // context and passing that canvas to CanvasKit.MakeImageFromCanvasImageSource() is
435            // marginally faster than passing ImageBitmap to
436            // CanvasKit.MakeImageFromCanvasImageSource() directly.
437            const canvasBitmapElement = document.createElement('canvas');
438            canvasBitmapElement.width = imageBitmap.width;
439            canvasBitmapElement.height = imageBitmap.height;
440            const ctxBitmap = canvasBitmapElement.getContext('bitmaprenderer');
441            ctxBitmap.transferFromImageBitmap(imageBitmap);
442            const skImage2 = CanvasKit.MakeImageFromCanvasImageSource(canvasBitmapElement);
443
444            // Method 3 (slowest) decode using HTMLImageElement directly:
445            const image = new Image();
446            // Testing showed that waiting for a load event is faster than waiting on image.decode()
447            // HTMLImageElement.decode() reference: https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode
448            const promise1 = new Promise((resolve) => image.addEventListener('load', resolve));
449            image.src = imageFilePath;
450            await promise1;
451            const skImage3 = CanvasKit.MakeImageFromCanvasImageSource(image);
452
453            // Method 4 (roundabout, but works if all you have is a Blob) decode from Blob using
454            // HTMLImageElement:
455            const imageObjectUrl = URL.createObjectURL( blob );
456            const image2 = new Image();
457            const promise2 = new Promise((resolve) => image2.addEventListener('load', resolve));
458            image2.src = imageObjectUrl;
459            await promise2;
460            const skImage4 = CanvasKit.MakeImageFromCanvasImageSource(image2);
461
462            // Draw decoded images
463            const sourceRect = CanvasKit.XYWHRect(0, 0, 150, 150);
464            canvas.drawImageRect(skImage1, sourceRect, CanvasKit.XYWHRect(0, row * 100, 90, 90), null, false);
465            canvas.drawImageRect(skImage2, sourceRect, CanvasKit.XYWHRect(100, row * 100, 90, 90), null, false);
466            canvas.drawImageRect(skImage3, sourceRect, CanvasKit.XYWHRect(200, row * 100, 90, 90), null, false);
467            canvas.drawImageRect(skImage4, sourceRect, CanvasKit.XYWHRect(300, row * 100, 90, 90), null, false);
468
469            row++;
470        }
471        // Label images with the method used to decode them
472        const paint = new CanvasKit.Paint();
473        const textFont = new CanvasKit.Font(null, 7);
474        canvas.drawText('WASM Decoding', 0, 90, paint, textFont);
475        canvas.drawText('ImageBitmap Decoding', 100, 90, paint, textFont);
476        canvas.drawText('HTMLImageEl Decoding', 200, 90, paint, textFont);
477        canvas.drawText('Blob Decoding', 300, 90, paint, textFont);
478    });
479
480    gm('sweep_gradient', (canvas) => {
481        const paint = new CanvasKit.Paint();
482        const shader = CanvasKit.Shader.MakeSweepGradient(
483            100, 100, // X, Y coordinates
484            [CanvasKit.GREEN, CanvasKit.BLUE],
485            [0.0, 1.0],
486            CanvasKit.TileMode.Clamp,
487        );
488        expect(shader).toBeTruthy('Could not make shader');
489
490        paint.setShader(shader);
491        canvas.drawPaint(paint);
492
493        paint.delete();
494        shader.delete();
495    });
496
497    // TODO(kjlubick): There's a lot of shared code between the gradient gms
498    // It would be best to deduplicate that in a nice DAMP way.
499    // Inspired by https://fiddle.skia.org/c/b29ce50a341510784ac7d5281586d076
500    gm('linear_gradients', (canvas) => {
501        canvas.clear(CanvasKit.WHITE);
502        canvas.scale(2, 2);
503        const strokePaint = new CanvasKit.Paint();
504        strokePaint.setStyle(CanvasKit.PaintStyle.Stroke);
505        strokePaint.setColor(CanvasKit.BLACK);
506
507        const paint = new CanvasKit.Paint();
508        paint.setStyle(CanvasKit.PaintStyle.Fill);
509        const transparentGreen = CanvasKit.Color(0, 255, 255, 0);
510
511        const lgs = CanvasKit.Shader.MakeLinearGradient(
512            [0, 0], [50, 100], // start and stop points
513            [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
514            [0, 0.65, 1.0],
515            CanvasKit.TileMode.Mirror
516        );
517        paint.setShader(lgs);
518        let r = CanvasKit.LTRBRect(0, 0, 100, 100);
519        canvas.drawRect(r, paint);
520        canvas.drawRect(r, strokePaint);
521
522        const lgsPremul = CanvasKit.Shader.MakeLinearGradient(
523            [100, 0], [150, 100], // start and stop points
524            Uint32Array.of(
525                CanvasKit.ColorAsInt(0, 255, 255, 0),
526                CanvasKit.ColorAsInt(0, 0, 255, 255),
527                CanvasKit.ColorAsInt(255, 0, 0, 255)),
528            [0, 0.65, 1.0],
529            CanvasKit.TileMode.Mirror,
530            null, // no local matrix
531            1 // interpolate colors in premul
532        );
533        paint.setShader(lgsPremul);
534        r = CanvasKit.LTRBRect(100, 0, 200, 100);
535        canvas.drawRect(r, paint);
536        canvas.drawRect(r, strokePaint);
537
538        const lgs45 = CanvasKit.Shader.MakeLinearGradient(
539            [0, 100], [50, 200], // start and stop points
540            Float32Array.of(...transparentGreen, ...CanvasKit.BLUE, ...CanvasKit.RED),
541            [0, 0.65, 1.0],
542            CanvasKit.TileMode.Mirror,
543            CanvasKit.Matrix.rotated(Math.PI/4, 0, 100),
544        );
545        paint.setShader(lgs45);
546        r = CanvasKit.LTRBRect(0, 100, 100, 200);
547        canvas.drawRect(r, paint);
548        canvas.drawRect(r, strokePaint);
549
550        // malloc'd color array
551        const colors = CanvasKit.Malloc(Float32Array, 12);
552        const typedColorsArray = colors.toTypedArray();
553        typedColorsArray.set(transparentGreen, 0);
554        typedColorsArray.set(CanvasKit.BLUE, 4);
555        typedColorsArray.set(CanvasKit.RED, 8);
556        const lgs45Premul = CanvasKit.Shader.MakeLinearGradient(
557            [100, 100], [150, 200], // start and stop points
558            typedColorsArray,
559            [0, 0.65, 1.0],
560            CanvasKit.TileMode.Mirror,
561            CanvasKit.Matrix.rotated(Math.PI/4, 100, 100),
562            1 // interpolate colors in premul
563        );
564        CanvasKit.Free(colors);
565        paint.setShader(lgs45Premul);
566        r = CanvasKit.LTRBRect(100, 100, 200, 200);
567        canvas.drawRect(r, paint);
568        canvas.drawRect(r, strokePaint);
569
570        lgs.delete();
571        lgs45.delete();
572        lgsPremul.delete();
573        lgs45Premul.delete();
574        strokePaint.delete();
575        paint.delete();
576    });
577
578    gm('radial_gradients', (canvas) => {
579        canvas.clear(CanvasKit.WHITE);
580        canvas.scale(2, 2);
581        const strokePaint = new CanvasKit.Paint();
582        strokePaint.setStyle(CanvasKit.PaintStyle.Stroke);
583        strokePaint.setColor(CanvasKit.BLACK);
584
585        const paint = new CanvasKit.Paint();
586        paint.setStyle(CanvasKit.PaintStyle.Fill);
587        const transparentGreen = CanvasKit.Color(0, 255, 255, 0);
588
589        const rgs = CanvasKit.Shader.MakeRadialGradient(
590            [50, 50], 50, // center, radius
591            [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
592            [0, 0.65, 1.0],
593            CanvasKit.TileMode.Mirror
594        );
595        paint.setShader(rgs);
596        let r = CanvasKit.LTRBRect(0, 0, 100, 100);
597        canvas.drawRect(r, paint);
598        canvas.drawRect(r, strokePaint);
599
600        const rgsPremul = CanvasKit.Shader.MakeRadialGradient(
601            [150, 50], 50, // center, radius
602            [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
603            [0, 0.65, 1.0],
604            CanvasKit.TileMode.Mirror,
605            null, // no local matrix
606            1 // interpolate colors in premul
607        );
608        paint.setShader(rgsPremul);
609        r = CanvasKit.LTRBRect(100, 0, 200, 100);
610        canvas.drawRect(r, paint);
611        canvas.drawRect(r, strokePaint);
612
613        const rgsSkew = CanvasKit.Shader.MakeRadialGradient(
614            [50, 150], 50, // center, radius
615            [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
616            [0, 0.65, 1.0],
617            CanvasKit.TileMode.Mirror,
618            CanvasKit.Matrix.skewed(0.5, 0, 100, 100),
619            null, // color space
620        );
621        paint.setShader(rgsSkew);
622        r = CanvasKit.LTRBRect(0, 100, 100, 200);
623        canvas.drawRect(r, paint);
624        canvas.drawRect(r, strokePaint);
625
626        const rgsSkewPremul = CanvasKit.Shader.MakeRadialGradient(
627            [150, 150], 50, // center, radius
628            [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
629            [0, 0.65, 1.0],
630            CanvasKit.TileMode.Mirror,
631            CanvasKit.Matrix.skewed(0.5, 0, 100, 100),
632            1, // interpolate colors in premul
633            null, // color space
634        );
635        paint.setShader(rgsSkewPremul);
636        r = CanvasKit.LTRBRect(100, 100, 200, 200);
637        canvas.drawRect(r, paint);
638        canvas.drawRect(r, strokePaint);
639
640        rgs.delete();
641        rgsPremul.delete();
642        rgsSkew.delete();
643        rgsSkewPremul.delete();
644        strokePaint.delete();
645        paint.delete();
646    });
647
648    gm('conical_gradients', (canvas) => {
649        canvas.clear(CanvasKit.WHITE);
650        canvas.scale(2, 2);
651        const strokePaint = new CanvasKit.Paint();
652        strokePaint.setStyle(CanvasKit.PaintStyle.Stroke);
653        strokePaint.setColor(CanvasKit.BLACK);
654
655        const paint = new CanvasKit.Paint();
656        paint.setStyle(CanvasKit.PaintStyle.Fill);
657        paint.setAntiAlias(true);
658        const transparentGreen = CanvasKit.Color(0, 255, 255, 0);
659
660        const cgs = CanvasKit.Shader.MakeTwoPointConicalGradient(
661            [80, 10], 15, // start, radius
662            [10, 110], 60, // end, radius
663            [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
664            [0, 0.65, 1.0],
665            CanvasKit.TileMode.Mirror,
666            null, // no local matrix
667        );
668        paint.setShader(cgs);
669        let r = CanvasKit.LTRBRect(0, 0, 100, 100);
670        canvas.drawRect(r, paint);
671        canvas.drawRect(r, strokePaint);
672
673        const cgsPremul = CanvasKit.Shader.MakeTwoPointConicalGradient(
674            [180, 10], 15, // start, radius
675            [110, 110], 60, // end, radius
676            [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
677            [0, 0.65, 1.0],
678            CanvasKit.TileMode.Mirror,
679            null, // no local matrix
680            1, // interpolate colors in premul
681            null, // color space
682        );
683        paint.setShader(cgsPremul);
684        r = CanvasKit.LTRBRect(100, 0, 200, 100);
685        canvas.drawRect(r, paint);
686        canvas.drawRect(r, strokePaint);
687
688        const cgs45 = CanvasKit.Shader.MakeTwoPointConicalGradient(
689            [80, 110], 15, // start, radius
690            [10, 210], 60, // end, radius
691            [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
692            [0, 0.65, 1.0],
693            CanvasKit.TileMode.Mirror,
694            CanvasKit.Matrix.rotated(Math.PI/4, 0, 100),
695            null, // color space
696        );
697        paint.setShader(cgs45);
698        r = CanvasKit.LTRBRect(0, 100, 100, 200);
699        canvas.drawRect(r, paint);
700        canvas.drawRect(r, strokePaint);
701
702        const cgs45Premul = CanvasKit.Shader.MakeTwoPointConicalGradient(
703            [180, 110], 15, // start, radius
704            [110, 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, 100, 100),
709            1, // interpolate colors in premul
710            null, // color space
711        );
712        paint.setShader(cgs45Premul);
713        r = CanvasKit.LTRBRect(100, 100, 200, 200);
714        canvas.drawRect(r, paint);
715        canvas.drawRect(r, strokePaint);
716
717        cgs.delete();
718        cgsPremul.delete();
719        cgs45.delete();
720        strokePaint.delete();
721        paint.delete();
722    });
723
724    gm('blur_filters', (canvas) => {
725        const pathUL = starPath(CanvasKit, 100, 100, 80);
726        const pathBR = starPath(CanvasKit, 400, 300, 80);
727        const paint = new CanvasKit.Paint();
728        const textFont = new CanvasKit.Font(null, 24);
729
730        canvas.drawText('Above: MaskFilter', 20, 220, paint, textFont);
731        canvas.drawText('Right: ImageFilter', 20, 260, paint, textFont);
732
733        paint.setColor(CanvasKit.BLUE);
734
735        const blurMask = CanvasKit.MaskFilter.MakeBlur(CanvasKit.BlurStyle.Normal, 5, true);
736        paint.setMaskFilter(blurMask);
737        canvas.drawPath(pathUL, paint);
738
739        const blurIF = CanvasKit.ImageFilter.MakeBlur(8, 1, CanvasKit.TileMode.Decal, null);
740        paint.setImageFilter(blurIF);
741        canvas.drawPath(pathBR, paint);
742
743        pathUL.delete();
744        pathBR.delete();
745        paint.delete();
746        blurMask.delete();
747        blurIF.delete();
748        textFont.delete();
749    });
750
751    gm('combined_filters', (canvas, fetchedByteBuffers) => {
752        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
753        expect(img).toBeTruthy();
754
755        canvas.clear(CanvasKit.WHITE);
756        const paint = new CanvasKit.Paint();
757        paint.setAntiAlias(true);
758        paint.setColor(CanvasKit.Color(0, 255, 0, 1.0));
759        const redCF =  CanvasKit.ColorFilter.MakeBlend(
760                CanvasKit.Color(255, 0, 0, 0.1), CanvasKit.BlendMode.SrcOver);
761        const redIF = CanvasKit.ImageFilter.MakeColorFilter(redCF, null);
762        const blurIF = CanvasKit.ImageFilter.MakeBlur(8, 0.2, CanvasKit.TileMode.Decal, null);
763        const combined = CanvasKit.ImageFilter.MakeCompose(redIF, blurIF);
764
765        // rotate 10 degrees centered on 200, 200
766        const m = CanvasKit.Matrix.rotated(Math.PI/18, 200, 200);
767        const filtering = { filter: CanvasKit.FilterMode.Linear };
768        const rotated = CanvasKit.ImageFilter.MakeMatrixTransform(m, filtering, combined);
769        paint.setImageFilter(rotated);
770
771        //canvas.rotate(10, 200, 200);
772        canvas.drawImage(img, 0, 0, paint);
773        canvas.drawRect(CanvasKit.LTRBRect(5, 35, 45, 80), paint);
774
775        paint.delete();
776        redIF.delete();
777        redCF.delete();
778        blurIF.delete();
779        combined.delete();
780        rotated.delete();
781        img.delete();
782    }, '/assets/mandrill_512.png');
783
784    gm('animated_filters', (canvas, fetchedByteBuffers) => {
785        const img = CanvasKit.MakeAnimatedImageFromEncoded(fetchedByteBuffers[0]);
786        expect(img).toBeTruthy();
787        img.decodeNextFrame();
788        img.decodeNextFrame();
789        canvas.clear(CanvasKit.WHITE);
790        const paint = new CanvasKit.Paint();
791        paint.setAntiAlias(true);
792        paint.setColor(CanvasKit.Color(0, 255, 0, 1.0));
793        const redCF =  CanvasKit.ColorFilter.MakeBlend(
794                CanvasKit.Color(255, 0, 0, 0.1), CanvasKit.BlendMode.SrcOver);
795        const redIF = CanvasKit.ImageFilter.MakeColorFilter(redCF, null);
796        const blurIF = CanvasKit.ImageFilter.MakeBlur(8, 0.2, CanvasKit.TileMode.Decal, null);
797        const combined = CanvasKit.ImageFilter.MakeCompose(redIF, blurIF);
798        paint.setImageFilter(combined);
799
800        const frame = img.makeImageAtCurrentFrame();
801        canvas.drawImage(frame, 100, 50, paint);
802
803        paint.delete();
804        redIF.delete();
805        redCF.delete();
806        blurIF.delete();
807        combined.delete();
808        frame.delete();
809        img.delete();
810    }, '/assets/flightAnim.gif');
811
812    gm('drawImageVariants', (canvas, fetchedByteBuffers) => {
813        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
814        expect(img).toBeTruthy();
815
816        canvas.clear(CanvasKit.WHITE);
817        canvas.scale(2, 2);
818        const paint = new CanvasKit.Paint();
819        const clipTo = (x, y) => {
820            canvas.save();
821            canvas.clipRect(CanvasKit.XYWHRect(x, y, 128, 128), CanvasKit.ClipOp.Intersect);
822        };
823
824        clipTo(0, 0);
825        canvas.drawImage(img, 0, 0, paint);
826        canvas.restore();
827
828        clipTo(128, 0);
829        canvas.drawImageCubic(img, 128, 0, 1/3, 1/3, null);
830        canvas.restore();
831
832        clipTo(0, 128);
833        canvas.drawImageOptions(img, 0, 128, CanvasKit.FilterMode.Linear, CanvasKit.MipmapMode.None, null);
834        canvas.restore();
835
836        const mipImg = img.makeCopyWithDefaultMipmaps();
837        clipTo(128, 128);
838        canvas.drawImageOptions(mipImg, 128, 128,
839                                CanvasKit.FilterMode.Nearest, CanvasKit.MipmapMode.Nearest, null);
840        canvas.restore();
841
842        paint.delete();
843        mipImg.delete();
844        img.delete();
845    }, '/assets/mandrill_512.png');
846
847    gm('drawImageRectVariants', (canvas, fetchedByteBuffers) => {
848        const img = CanvasKit.MakeImageFromEncoded(fetchedByteBuffers[0]);
849        expect(img).toBeTruthy();
850
851        canvas.clear(CanvasKit.WHITE);
852        const paint = new CanvasKit.Paint();
853        const src = CanvasKit.XYWHRect(100, 100, 128, 128);
854        canvas.drawImageRect(img, src, CanvasKit.XYWHRect(0, 0, 256, 256), paint);
855        canvas.drawImageRectCubic(img, src, CanvasKit.XYWHRect(256, 0, 256, 256), 1/3, 1/3);
856        canvas.drawImageRectOptions(img, src, CanvasKit.XYWHRect(0, 256, 256, 256),
857                                    CanvasKit.FilterMode.Linear, CanvasKit.MipmapMode.None);
858        const mipImg = img.makeCopyWithDefaultMipmaps();
859        canvas.drawImageRectOptions(mipImg, src, CanvasKit.XYWHRect(256, 256, 256, 256),
860                                CanvasKit.FilterMode.Nearest, CanvasKit.MipmapMode.Nearest);
861
862        paint.delete();
863        mipImg.delete();
864        img.delete();
865    }, '/assets/mandrill_512.png');
866
867    gm('drawImage_skp', (canvas, fetchedByteBuffers) => {
868        const pic = CanvasKit.MakePicture(fetchedByteBuffers[0]);
869        canvas.clear(CanvasKit.TRANSPARENT);
870        canvas.drawPicture(pic);
871        // The asset below can be re-downloaded from
872        // https://fiddle.skia.org/c/cbb8dee39e9f1576cd97c2d504db8eee
873    }, '/assets/red_line.skp');
874
875    it('can draw once using drawOnce utility method', (done) => {
876        const surface = CanvasKit.MakeCanvasSurface('test');
877        expect(surface).toBeTruthy('Could not make surface');
878        if (!surface) {
879            done();
880            return;
881        }
882
883        const drawFrame = (canvas) => {
884            const paint = new CanvasKit.Paint();
885            paint.setStrokeWidth(1.0);
886            paint.setAntiAlias(true);
887            paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
888            paint.setStyle(CanvasKit.PaintStyle.Stroke);
889            const path = new CanvasKit.Path();
890            path.moveTo(20, 5);
891            path.lineTo(30, 20);
892            path.lineTo(40, 10);
893            canvas.drawPath(path, paint);
894            path.delete();
895            paint.delete();
896            // surface hasn't been flushed yet (nor should we call flush
897            // ourselves), so reportSurface would likely be blank if we
898            // were to call it.
899            done();
900        };
901        surface.drawOnce(drawFrame);
902        // Reminder: drawOnce is async. In this test, we are just making
903        // sure the drawOnce function is there and doesn't crash, so we can
904        // just call done() when the frame is rendered.
905    });
906
907    it('can draw client-supplied dirty rects', (done) => {
908        // dirty rects are only honored by software (CPU) canvases today.
909        const surface = CanvasKit.MakeSWCanvasSurface('test');
910        expect(surface).toBeTruthy('Could not make surface');
911        if (!surface) {
912            done();
913            return;
914        }
915
916        const drawFrame = (canvas) => {
917            const paint = new CanvasKit.Paint();
918            paint.setStrokeWidth(1.0);
919            paint.setAntiAlias(true);
920            paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
921            paint.setStyle(CanvasKit.PaintStyle.Stroke);
922            const path = new CanvasKit.Path();
923            path.moveTo(20, 5);
924            path.lineTo(30, 20);
925            path.lineTo(40, 10);
926            canvas.drawPath(path, paint);
927            path.delete();
928            paint.delete();
929            done();
930        };
931        const dirtyRect = CanvasKit.XYWHRect(10, 10, 15, 15);
932        surface.drawOnce(drawFrame, dirtyRect);
933        // We simply ensure that passing a dirty rect doesn't crash.
934    });
935
936    it('can use DecodeCache APIs', () => {
937        const initialLimit = CanvasKit.getDecodeCacheLimitBytes();
938        expect(initialLimit).toBeGreaterThan(1024 * 1024);
939
940        const newLimit = 42 * 1024 * 1024;
941        CanvasKit.setDecodeCacheLimitBytes(newLimit);
942        expect(CanvasKit.getDecodeCacheLimitBytes()).toEqual(newLimit);
943
944        // We cannot make any assumptions about this value,
945        // so we just make sure it doesn't crash.
946        CanvasKit.getDecodeCacheUsedBytes();
947    });
948
949    gm('combined_shaders', (canvas) => {
950        const rShader = CanvasKit.Shader.Color(CanvasKit.Color(255, 0, 0, 1.0)); // deprecated
951        const gShader = CanvasKit.Shader.MakeColor(CanvasKit.Color(0, 255, 0, 0.6));
952
953        const rgShader = CanvasKit.Shader.MakeBlend(CanvasKit.BlendMode.SrcOver, rShader, gShader);
954
955        const p = new CanvasKit.Paint();
956        p.setShader(rgShader);
957        canvas.drawPaint(p);
958
959        rShader.delete();
960        gShader.delete();
961        rgShader.delete();
962        p.delete();
963    });
964
965    it('exports consts correctly', () => {
966        expect(CanvasKit.TRANSPARENT).toEqual(Float32Array.of(0, 0, 0, 0));
967        expect(CanvasKit.RED).toEqual(Float32Array.of(1, 0, 0, 1));
968
969        expect(CanvasKit.QUAD_VERB).toEqual(2);
970        expect(CanvasKit.CONIC_VERB).toEqual(3);
971
972        expect(CanvasKit.SaveLayerInitWithPrevious).toEqual(4);
973        expect(CanvasKit.SaveLayerF16ColorType).toEqual(16);
974    });
975
976    it('can set color on a paint and get it as four floats', () => {
977        const paint = new CanvasKit.Paint();
978        paint.setColor(CanvasKit.Color4f(3.3, 2.2, 1.1, 0.5));
979        expect(paint.getColor()).toEqual(Float32Array.of(3.3, 2.2, 1.1, 0.5));
980
981        paint.setColorComponents(0.5, 0.6, 0.7, 0.8);
982        expect(paint.getColor()).toEqual(Float32Array.of(0.5, 0.6, 0.7, 0.8));
983
984        paint.setColorInt(CanvasKit.ColorAsInt(50, 100, 150, 200));
985        let color = paint.getColor();
986        expect(color.length).toEqual(4);
987        expect(color[0]).toBeCloseTo(50/255, 5);  // Red
988        expect(color[1]).toBeCloseTo(100/255, 5); // Green
989        expect(color[2]).toBeCloseTo(150/255, 5); // Blue
990        expect(color[3]).toBeCloseTo(200/255, 5); // Alpha
991
992        paint.setColorInt(0xFF000000);
993        expect(paint.getColor()).toEqual(Float32Array.of(0, 0, 0, 1.0));
994    });
995
996    gm('draw shadow', (canvas) => {
997        const lightRadius = 20;
998        const lightPos = [500,500,20];
999        const zPlaneParams = [0,0,1];
1000        const path = starPath(CanvasKit);
1001        const textFont = new CanvasKit.Font(null, 24);
1002        const textPaint = new CanvasKit.Paint();
1003
1004        canvas.drawShadow(path, zPlaneParams, lightPos, lightRadius,
1005                          CanvasKit.BLACK, CanvasKit.MAGENTA, 0);
1006        canvas.drawText('Default Flags', 5, 250, textPaint, textFont);
1007
1008        let bounds = CanvasKit.getShadowLocalBounds(CanvasKit.Matrix.identity(),
1009            path, zPlaneParams, lightPos, lightRadius, 0);
1010        expectTypedArraysToEqual(bounds, Float32Array.of(-3.64462, -12.67541, 245.50, 242.59164));
1011
1012        bounds = CanvasKit.getShadowLocalBounds(CanvasKit.M44.identity(),
1013            path, zPlaneParams, lightPos, lightRadius, 0);
1014        expectTypedArraysToEqual(bounds, Float32Array.of(-3.64462, -12.67541, 245.50, 242.59164));
1015
1016        // Test that the APIs accept Malloc objs and the Malloced typearray
1017        const mZPlane = CanvasKit.Malloc(Float32Array, 3);
1018        mZPlane.toTypedArray().set(zPlaneParams);
1019        const mLight = CanvasKit.Malloc(Float32Array, 3);
1020        const lightTA = mLight.toTypedArray();
1021        lightTA.set(lightPos);
1022
1023        canvas.translate(250, 250);
1024        canvas.drawShadow(path, mZPlane, lightTA, lightRadius,
1025                          CanvasKit.BLACK, CanvasKit.MAGENTA,
1026                          CanvasKit.ShadowTransparentOccluder | CanvasKit.ShadowGeometricOnly | CanvasKit.ShadowDirectionalLight);
1027        canvas.drawText('All Flags', 5, 250, textPaint, textFont);
1028
1029        const outBounds = new Float32Array(4);
1030        CanvasKit.getShadowLocalBounds(CanvasKit.Matrix.rotated(Math.PI / 6),
1031            path, mZPlane, mLight, lightRadius,
1032            CanvasKit.ShadowTransparentOccluder | CanvasKit.ShadowGeometricOnly | CanvasKit.ShadowDirectionalLight,
1033            outBounds);
1034        expectTypedArraysToEqual(outBounds, Float32Array.of(-31.6630249, -15.24227, 245.5, 252.94101));
1035
1036        CanvasKit.Free(mZPlane);
1037        CanvasKit.Free(mLight);
1038
1039        path.delete();
1040        textFont.delete();
1041        textPaint.delete();
1042    });
1043
1044    gm('fractal_noise_shader', (canvas) => {
1045        const shader = CanvasKit.Shader.MakeFractalNoise(0.1, 0.05, 2, 0, 0, 0);
1046        const paint = new CanvasKit.Paint();
1047        paint.setColor(CanvasKit.BLACK);
1048        paint.setShader(shader);
1049        canvas.drawPaint(paint);
1050        paint.delete();
1051        shader.delete();
1052    });
1053
1054    gm('turbulance_shader', (canvas) => {
1055        const shader = CanvasKit.Shader.MakeTurbulence(0.1, 0.05, 2, 117, 0, 0);
1056        const paint = new CanvasKit.Paint();
1057        paint.setColor(CanvasKit.BLACK);
1058        paint.setShader(shader);
1059        canvas.drawPaint(paint);
1060        paint.delete();
1061        shader.delete();
1062    });
1063
1064    gm('fractal_noise_tiled_shader', (canvas) => {
1065        const shader = CanvasKit.Shader.MakeFractalNoise(0.1, 0.05, 2, 0, 80, 80);
1066        const paint = new CanvasKit.Paint();
1067        paint.setColor(CanvasKit.BLACK);
1068        paint.setShader(shader);
1069        canvas.drawPaint(paint);
1070        paint.delete();
1071        shader.delete();
1072    });
1073
1074    gm('turbulance_tiled_shader', (canvas) => {
1075        const shader = CanvasKit.Shader.MakeTurbulence(0.1, 0.05, 2, 117, 80, 80);
1076        const paint = new CanvasKit.Paint();
1077        paint.setColor(CanvasKit.BLACK);
1078        paint.setShader(shader);
1079        canvas.drawPaint(paint);
1080        paint.delete();
1081        shader.delete();
1082    });
1083
1084    describe('ColorSpace Support', () => {
1085        it('Can create an SRGB 8888 surface', () => {
1086            const colorSpace = CanvasKit.ColorSpace.SRGB;
1087            const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.ColorSpace.SRGB);
1088            expect(surface).toBeTruthy('Could not make surface');
1089            let info = surface.imageInfo();
1090            expect(info.alphaType).toEqual(CanvasKit.AlphaType.Unpremul);
1091            expect(info.colorType).toEqual(CanvasKit.ColorType.RGBA_8888);
1092            expect(CanvasKit.ColorSpace.Equals(info.colorSpace, colorSpace))
1093                .toBeTruthy("Surface not created with correct color space.");
1094
1095            const mObj = CanvasKit.Malloc(Uint8Array, CANVAS_WIDTH * CANVAS_HEIGHT * 4);
1096            mObj.toTypedArray()[0] = 127; // sentinel value. Should be overwritten by readPixels.
1097            const canvas = surface.getCanvas();
1098            canvas.clear(CanvasKit.TRANSPARENT);
1099            const pixels = canvas.readPixels(0, 0, {
1100                width: CANVAS_WIDTH,
1101                height: CANVAS_HEIGHT,
1102                colorType: CanvasKit.ColorType.RGBA_8888,
1103                alphaType: CanvasKit.AlphaType.Unpremul,
1104                colorSpace: colorSpace
1105            }, mObj, 4 * CANVAS_WIDTH);
1106            expect(pixels).toBeTruthy('Could not read pixels from surface');
1107            expect(pixels[0] !== 127).toBeTruthy();
1108            expect(pixels[0]).toEqual(mObj.toTypedArray()[0]);
1109            CanvasKit.Free(mObj);
1110            surface.delete();
1111        });
1112        it('Can create a Display P3 surface', () => {
1113            const colorSpace = CanvasKit.ColorSpace.DISPLAY_P3;
1114            const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.ColorSpace.DISPLAY_P3);
1115            expect(surface).toBeTruthy('Could not make surface');
1116            if (!surface.reportBackendTypeIsGPU()) {
1117                console.log('Not expecting color space support in cpu backed suface.');
1118                return;
1119            }
1120            let info = surface.imageInfo();
1121            expect(info.alphaType).toEqual(CanvasKit.AlphaType.Unpremul);
1122            expect(info.colorType).toEqual(CanvasKit.ColorType.RGBA_F16);
1123            expect(CanvasKit.ColorSpace.Equals(info.colorSpace, colorSpace))
1124                .toBeTruthy("Surface not created with correct color space.");
1125
1126            const pixels = surface.getCanvas().readPixels(0, 0, {
1127                width: CANVAS_WIDTH,
1128                height: CANVAS_HEIGHT,
1129                colorType: CanvasKit.ColorType.RGBA_F16,
1130                alphaType: CanvasKit.AlphaType.Unpremul,
1131                colorSpace: colorSpace
1132            });
1133            expect(pixels).toBeTruthy('Could not read pixels from surface');
1134        });
1135        it('Can create an Adobe RGB surface', () => {
1136            const colorSpace = CanvasKit.ColorSpace.ADOBE_RGB;
1137            const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.ColorSpace.ADOBE_RGB);
1138            expect(surface).toBeTruthy('Could not make surface');
1139            if (!surface.reportBackendTypeIsGPU()) {
1140                console.log('Not expecting color space support in cpu backed surface.');
1141                return;
1142            }
1143            let info = surface.imageInfo();
1144            expect(info.alphaType).toEqual(CanvasKit.AlphaType.Unpremul);
1145            expect(info.colorType).toEqual(CanvasKit.ColorType.RGBA_F16);
1146            expect(CanvasKit.ColorSpace.Equals(info.colorSpace, colorSpace))
1147                .toBeTruthy("Surface not created with correct color space.");
1148
1149            const pixels = surface.getCanvas().readPixels(0, 0, {
1150                width: CANVAS_WIDTH,
1151                height: CANVAS_HEIGHT,
1152                colorType: CanvasKit.ColorType.RGBA_F16,
1153                alphaType: CanvasKit.AlphaType.Unpremul,
1154                colorSpace: colorSpace
1155            });
1156            expect(pixels).toBeTruthy('Could not read pixels from surface');
1157        });
1158
1159        it('combine draws from several color spaces', () => {
1160            const surface = CanvasKit.MakeCanvasSurface('test', CanvasKit.ColorSpace.ADOBE_RGB);
1161            expect(surface).toBeTruthy('Could not make surface');
1162            if (!surface.reportBackendTypeIsGPU()) {
1163                console.log('Not expecting color space support in cpu backed suface.');
1164                return;
1165            }
1166            const canvas = surface.getCanvas();
1167
1168            let paint = new CanvasKit.Paint();
1169            paint.setColor(CanvasKit.RED, CanvasKit.ColorSpace.ADOBE_RGB);
1170            canvas.drawPaint(paint);
1171            paint.setColor(CanvasKit.RED, CanvasKit.ColorSpace.DISPLAY_P3); // 93.7 in adobeRGB
1172            canvas.drawRect(CanvasKit.LTRBRect(200, 0, 400, 600), paint);
1173            paint.setColor(CanvasKit.RED, CanvasKit.ColorSpace.SRGB); // 85.9 in adobeRGB
1174            canvas.drawRect(CanvasKit.LTRBRect(400, 0, 600, 600), paint);
1175
1176            // this test paints three bands of red, each the maximum red that a color space supports.
1177            // They are each represented by skia by some color in the Adobe RGB space of the surface,
1178            // as floats between 0 and 1.
1179
1180            // TODO(nifong) readpixels and verify correctness after f32 readpixels bug is fixed
1181        });
1182    }); // end describe('ColorSpace Support')
1183
1184    describe('DOMMatrix support', () => {
1185        gm('sweep_gradient_dommatrix', (canvas) => {
1186            const paint = new CanvasKit.Paint();
1187            const shader = CanvasKit.Shader.MakeSweepGradient(
1188                100, 100, // x y coordinates
1189                [CanvasKit.GREEN, CanvasKit.BLUE],
1190                [0.0, 1.0],
1191                CanvasKit.TileMode.Clamp,
1192                new DOMMatrix().translate(-10, 100),
1193            );
1194            expect(shader).toBeTruthy('Could not make shader');
1195            if (!shader) {
1196                return;
1197            }
1198
1199            paint.setShader(shader);
1200            canvas.drawPaint(paint);
1201
1202            paint.delete();
1203            shader.delete();
1204        });
1205
1206        const radiansToDegrees = (rad) => {
1207           return (rad / Math.PI) * 180;
1208        };
1209
1210        // this should draw the same as concat_with4x4_canvas
1211        gm('concat_dommatrix', (canvas) => {
1212            const path = starPath(CanvasKit, CANVAS_WIDTH/2, CANVAS_HEIGHT/2);
1213            const paint = new CanvasKit.Paint();
1214            paint.setAntiAlias(true);
1215            canvas.clear(CanvasKit.WHITE);
1216            canvas.concat(new DOMMatrix().translate(CANVAS_WIDTH/2, 0, 0));
1217            canvas.concat(new DOMMatrix().rotateAxisAngle(1, 0, 0, radiansToDegrees(Math.PI/3)));
1218            canvas.concat(new DOMMatrix().rotateAxisAngle(0, 1, 0, radiansToDegrees(Math.PI/4)));
1219            canvas.concat(new DOMMatrix().rotateAxisAngle(0, 0, 1, radiansToDegrees(Math.PI/16)));
1220            canvas.concat(new DOMMatrix().translate(-CANVAS_WIDTH/2, 0, 0));
1221
1222            const localMatrix = canvas.getLocalToDevice();
1223            expect4x4MatricesToMatch([
1224             0.693519, -0.137949,  0.707106,   91.944030,
1225             0.698150,  0.370924, -0.612372, -209.445297,
1226            -0.177806,  0.918359,  0.353553,   53.342029,
1227             0       ,  0       ,  0       ,    1       ], localMatrix);
1228
1229            // Draw some stripes to help the eye detect the turn
1230            const stripeWidth = 10;
1231            paint.setColor(CanvasKit.BLACK);
1232            for (let i = 0; i < CANVAS_WIDTH; i += 2*stripeWidth) {
1233                canvas.drawRect(CanvasKit.LTRBRect(i, 0, i + stripeWidth, CANVAS_HEIGHT), paint);
1234            }
1235
1236            paint.setColor(CanvasKit.YELLOW);
1237            canvas.drawPath(path, paint);
1238            paint.delete();
1239            path.delete();
1240        });
1241    }); // end describe('DOMMatrix support')
1242
1243    it('can call subarray on a Malloced object', () => {
1244        const mThings = CanvasKit.Malloc(Float32Array, 6);
1245        mThings.toTypedArray().set([4, 5, 6, 7, 8, 9]);
1246        expectTypedArraysToEqual(Float32Array.of(4, 5, 6, 7, 8, 9), mThings.toTypedArray());
1247        expectTypedArraysToEqual(Float32Array.of(4, 5, 6, 7, 8, 9), mThings.subarray(0));
1248        expectTypedArraysToEqual(Float32Array.of(7, 8, 9), mThings.subarray(3));
1249        expectTypedArraysToEqual(Float32Array.of(7), mThings.subarray(3, 4));
1250        expectTypedArraysToEqual(Float32Array.of(7, 8), mThings.subarray(3, 5));
1251
1252        // mutations on the subarray affect the entire array (because they are backed by the
1253        // same memory)
1254        mThings.subarray(3)[0] = 100.5;
1255        expectTypedArraysToEqual(Float32Array.of(4, 5, 6, 100.5, 8, 9), mThings.toTypedArray());
1256        CanvasKit.Free(mThings);
1257    });
1258
1259    function expectTypedArraysToEqual(expected, actual) {
1260        expect(expected.constructor.name).toEqual(actual.constructor.name);
1261        expect(expected.length).toEqual(actual.length);
1262        for (let i = 0; i < expected.length; i++) {
1263            expect(expected[i]).toBeCloseTo(actual[i], 5, `element ${i}`);
1264        }
1265    }
1266
1267    it('can create a RasterDirectSurface', () => {
1268        // Make enough space for a 5x5 8888 surface (4 bytes for R, G, B, A)
1269        const rdsData = CanvasKit.Malloc(Uint8Array, 5 * 5 * 4);
1270        const surface = CanvasKit.MakeRasterDirectSurface({
1271            'width': 5,
1272            'height': 5,
1273            'colorType': CanvasKit.ColorType.RGBA_8888,
1274            'alphaType': CanvasKit.AlphaType.Premul,
1275            'colorSpace': CanvasKit.ColorSpace.SRGB,
1276        }, rdsData, 5 * 4);
1277
1278        surface.getCanvas().clear(CanvasKit.Color(200, 100, 0, 0.8));
1279        const pixels = rdsData.toTypedArray();
1280        // Check that the first pixels colors are right.
1281        expect(pixels[0]).toEqual(160); // red (premul, 0.8 * 200)
1282        expect(pixels[1]).toEqual(80); // green (premul, 0.8 * 100)
1283        expect(pixels[2]).toEqual(0); // blue (premul, not that it matters)
1284        expect(pixels[3]).toEqual(204); // alpha (0.8 * 255)
1285        surface.delete();
1286        CanvasKit.Free(rdsData);
1287    });
1288
1289    gm('makeImageFromTextureSource_TypedArray', (canvas, _, surface) => {
1290        if (!CanvasKit.gpu) {
1291            return;
1292        }
1293        // This creates and draws an Unpremul Image that is 1 pixel wide, 4 pixels tall with
1294        // the colors listed below.
1295        const pixels = Uint8Array.from([
1296            255,   0,   0, 255, // opaque red
1297              0, 255,   0, 255, // opaque green
1298              0,   0, 255, 255, // opaque blue
1299            255,   0, 255, 100, // transparent purple
1300        ]);
1301        const img = surface.makeImageFromTextureSource(pixels, {
1302              'width': 1,
1303              'height': 4,
1304              'alphaType': CanvasKit.AlphaType.Unpremul,
1305              'colorType': CanvasKit.ColorType.RGBA_8888,
1306            });
1307        canvas.drawImage(img, 1, 1, null);
1308
1309        const info = img.getImageInfo();
1310        expect(info).toEqual({
1311          'width': 1,
1312          'height': 4,
1313          'alphaType': CanvasKit.AlphaType.Unpremul,
1314          'colorType': CanvasKit.ColorType.RGBA_8888,
1315        });
1316        const cs = img.getColorSpace();
1317        expect(CanvasKit.ColorSpace.Equals(cs, CanvasKit.ColorSpace.SRGB)).toBeTruthy();
1318
1319        cs.delete();
1320        img.delete();
1321    });
1322
1323    gm('makeImageFromTextureSource_PremulTypedArray', (canvas, _, surface) => {
1324        if (!CanvasKit.gpu) {
1325            return;
1326        }
1327        // This creates and draws an Unpremul Image that is 1 pixel wide, 4 pixels tall with
1328        // the colors listed below.
1329        const pixels = Uint8Array.from([
1330            255,   0,   0, 255, // opaque red
1331              0, 255,   0, 255, // opaque green
1332              0,   0, 255, 255, // opaque blue
1333            100,   0, 100, 100, // transparent purple
1334        ]);
1335        const img = surface.makeImageFromTextureSource(pixels, {
1336              'width': 1,
1337              'height': 4,
1338              'alphaType': CanvasKit.AlphaType.Premul,
1339              'colorType': CanvasKit.ColorType.RGBA_8888,
1340            });
1341        canvas.drawImage(img, 1, 1, null);
1342
1343        const info = img.getImageInfo();
1344        expect(info).toEqual({
1345          'width': 1,
1346          'height': 4,
1347          'alphaType': CanvasKit.AlphaType.Premul,
1348          'colorType': CanvasKit.ColorType.RGBA_8888,
1349        });
1350        img.delete();
1351    });
1352
1353    gm('makeImageFromTextureSource_imgElement', (canvas, _, surface) => {
1354        if (!CanvasKit.gpu) {
1355            return;
1356        }
1357        // This makes an offscreen <img> with the provided source.
1358        const imageEle = new Image();
1359        imageEle.src = '/assets/mandrill_512.png';
1360
1361        // We need to wait until the image is loaded before the texture can use it. For good
1362        // measure, we also wait for it to be decoded.
1363        return imageEle.decode().then(() => {
1364            const img = surface.makeImageFromTextureSource(imageEle);
1365            canvas.drawImage(img, 0, 0, null);
1366
1367            const info = img.getImageInfo();
1368            expect(info).toEqual({
1369              'width': 512, // width and height should be derived from the image.
1370              'height': 512,
1371              'alphaType': CanvasKit.AlphaType.Unpremul,
1372              'colorType': CanvasKit.ColorType.RGBA_8888,
1373            });
1374            img.delete();
1375        });
1376    });
1377
1378    gm('MakeLazyImageFromTextureSource_imgElement', (canvas) => {
1379        if (!CanvasKit.gpu) {
1380            return;
1381        }
1382        // This makes an offscreen <img> with the provided source.
1383        const imageEle = new Image();
1384        imageEle.src = '/assets/mandrill_512.png';
1385
1386        // We need to wait until the image is loaded before the texture can use it. For good
1387        // measure, we also wait for it to be decoded.
1388        return imageEle.decode().then(() => {
1389            const img = CanvasKit.MakeLazyImageFromTextureSource(imageEle);
1390            canvas.drawImage(img, 5, 5, null);
1391
1392            const info = img.getImageInfo();
1393            expect(info).toEqual({
1394              'width': 512, // width and height should be derived from the image.
1395              'height': 512,
1396              'alphaType': CanvasKit.AlphaType.Unpremul,
1397              'colorType': CanvasKit.ColorType.RGBA_8888,
1398            });
1399            img.delete();
1400        });
1401    });
1402
1403    gm('MakeLazyImageFromTextureSource_imageInfo', (canvas) => {
1404        if (!CanvasKit.gpu) {
1405            return;
1406        }
1407        // This makes an offscreen <img> with the provided source.
1408        const imageEle = new Image();
1409        imageEle.src = '/assets/mandrill_512.png';
1410
1411        // We need to wait until the image is loaded before the texture can use it. For good
1412        // measure, we also wait for it to be decoded.
1413        return imageEle.decode().then(() => {
1414            const img = CanvasKit.MakeLazyImageFromTextureSource(imageEle, {
1415              'width': 400,
1416              'height': 400,
1417              'alphaType': CanvasKit.AlphaType.Premul,
1418              'colorType': CanvasKit.ColorType.RGBA_8888,
1419            });
1420            canvas.drawImage(img, 20, 20, null);
1421
1422            img.delete();
1423        });
1424    });
1425});
1426