xref: /aosp_15_r20/external/skia/modules/canvaskit/tests/util.js (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker// The size of the golden images (DMs)
2*c8dee2aaSAndroid Build Coastguard Workerconst CANVAS_WIDTH = 600;
3*c8dee2aaSAndroid Build Coastguard Workerconst CANVAS_HEIGHT = 600;
4*c8dee2aaSAndroid Build Coastguard Worker
5*c8dee2aaSAndroid Build Coastguard Workerconst SHOULD_SKIP = 'should_skip';
6*c8dee2aaSAndroid Build Coastguard Worker
7*c8dee2aaSAndroid Build Coastguard Workerconst _commonGM = (it, pause, name, callback, assetsToFetchOrPromisesToWaitOn) => {
8*c8dee2aaSAndroid Build Coastguard Worker    if (name.includes(' ')) {
9*c8dee2aaSAndroid Build Coastguard Worker        throw name + " cannot contain spaces";
10*c8dee2aaSAndroid Build Coastguard Worker    }
11*c8dee2aaSAndroid Build Coastguard Worker    const fetchPromises = [];
12*c8dee2aaSAndroid Build Coastguard Worker    for (const assetOrPromise of assetsToFetchOrPromisesToWaitOn) {
13*c8dee2aaSAndroid Build Coastguard Worker        // https://stackoverflow.com/a/9436948
14*c8dee2aaSAndroid Build Coastguard Worker        if (typeof assetOrPromise === 'string' || assetOrPromise instanceof String) {
15*c8dee2aaSAndroid Build Coastguard Worker            const newPromise = fetchWithRetries(assetOrPromise)
16*c8dee2aaSAndroid Build Coastguard Worker                .then((response) => response.arrayBuffer())
17*c8dee2aaSAndroid Build Coastguard Worker                .catch((err) => {
18*c8dee2aaSAndroid Build Coastguard Worker                    console.error(err);
19*c8dee2aaSAndroid Build Coastguard Worker                    throw err;
20*c8dee2aaSAndroid Build Coastguard Worker                });
21*c8dee2aaSAndroid Build Coastguard Worker            fetchPromises.push(newPromise);
22*c8dee2aaSAndroid Build Coastguard Worker        } else if (typeof assetOrPromise.then === 'function') {
23*c8dee2aaSAndroid Build Coastguard Worker            fetchPromises.push(assetOrPromise);
24*c8dee2aaSAndroid Build Coastguard Worker        } else {
25*c8dee2aaSAndroid Build Coastguard Worker            throw 'Neither a string nor a promise ' + assetOrPromise;
26*c8dee2aaSAndroid Build Coastguard Worker        }
27*c8dee2aaSAndroid Build Coastguard Worker    }
28*c8dee2aaSAndroid Build Coastguard Worker    it('draws gm '+name, (done) => {
29*c8dee2aaSAndroid Build Coastguard Worker        const surface = CanvasKit.MakeCanvasSurface('test');
30*c8dee2aaSAndroid Build Coastguard Worker        expect(surface).toBeTruthy('Could not make surface');
31*c8dee2aaSAndroid Build Coastguard Worker        if (!surface) {
32*c8dee2aaSAndroid Build Coastguard Worker            done();
33*c8dee2aaSAndroid Build Coastguard Worker            return;
34*c8dee2aaSAndroid Build Coastguard Worker        }
35*c8dee2aaSAndroid Build Coastguard Worker        // if fetchPromises is empty, the returned promise will
36*c8dee2aaSAndroid Build Coastguard Worker        // resolve right away and just call the callback.
37*c8dee2aaSAndroid Build Coastguard Worker        Promise.all(fetchPromises).then((values) => {
38*c8dee2aaSAndroid Build Coastguard Worker            try {
39*c8dee2aaSAndroid Build Coastguard Worker                // If callback returns a promise, the chained .then
40*c8dee2aaSAndroid Build Coastguard Worker                // will wait for it. Otherwise, we'll pass the return value on,
41*c8dee2aaSAndroid Build Coastguard Worker                // which could indicate to skip this test and not report it to Gold.
42*c8dee2aaSAndroid Build Coastguard Worker                surface.getCanvas().clear(CanvasKit.WHITE);
43*c8dee2aaSAndroid Build Coastguard Worker                return callback(surface.getCanvas(), values, surface);
44*c8dee2aaSAndroid Build Coastguard Worker            } catch (e) {
45*c8dee2aaSAndroid Build Coastguard Worker                console.log(`gm ${name} failed with error`, e);
46*c8dee2aaSAndroid Build Coastguard Worker                expect(e).toBeFalsy();
47*c8dee2aaSAndroid Build Coastguard Worker                debugger;
48*c8dee2aaSAndroid Build Coastguard Worker                done();
49*c8dee2aaSAndroid Build Coastguard Worker            }
50*c8dee2aaSAndroid Build Coastguard Worker        }).then((shouldSkip) => {
51*c8dee2aaSAndroid Build Coastguard Worker            surface.flush();
52*c8dee2aaSAndroid Build Coastguard Worker            if (shouldSkip === SHOULD_SKIP) {
53*c8dee2aaSAndroid Build Coastguard Worker                surface.delete();
54*c8dee2aaSAndroid Build Coastguard Worker                done();
55*c8dee2aaSAndroid Build Coastguard Worker                console.log(`skipped gm ${name}`);
56*c8dee2aaSAndroid Build Coastguard Worker                return;
57*c8dee2aaSAndroid Build Coastguard Worker            }
58*c8dee2aaSAndroid Build Coastguard Worker            if (pause) {
59*c8dee2aaSAndroid Build Coastguard Worker                reportSurface(surface, name, null);
60*c8dee2aaSAndroid Build Coastguard Worker                console.error('pausing due to pause_gm being invoked');
61*c8dee2aaSAndroid Build Coastguard Worker            } else {
62*c8dee2aaSAndroid Build Coastguard Worker                reportSurface(surface, name, done);
63*c8dee2aaSAndroid Build Coastguard Worker            }
64*c8dee2aaSAndroid Build Coastguard Worker        }).catch((e) => {
65*c8dee2aaSAndroid Build Coastguard Worker            console.log(`could not load assets for gm ${name}`, e);
66*c8dee2aaSAndroid Build Coastguard Worker            debugger;
67*c8dee2aaSAndroid Build Coastguard Worker            done();
68*c8dee2aaSAndroid Build Coastguard Worker        });
69*c8dee2aaSAndroid Build Coastguard Worker    })
70*c8dee2aaSAndroid Build Coastguard Worker};
71*c8dee2aaSAndroid Build Coastguard Worker
72*c8dee2aaSAndroid Build Coastguard Workerconst fetchWithRetries = (url) => {
73*c8dee2aaSAndroid Build Coastguard Worker    const MAX_ATTEMPTS = 3;
74*c8dee2aaSAndroid Build Coastguard Worker    const DELAY_AFTER_FAILURE = 1000;
75*c8dee2aaSAndroid Build Coastguard Worker
76*c8dee2aaSAndroid Build Coastguard Worker    return new Promise((resolve, reject) => {
77*c8dee2aaSAndroid Build Coastguard Worker        let attempts = 0;
78*c8dee2aaSAndroid Build Coastguard Worker        const attemptFetch = () => {
79*c8dee2aaSAndroid Build Coastguard Worker            attempts++;
80*c8dee2aaSAndroid Build Coastguard Worker            fetch(url).then((resp) => resolve(resp))
81*c8dee2aaSAndroid Build Coastguard Worker                .catch((err) => {
82*c8dee2aaSAndroid Build Coastguard Worker                    if (attempts < MAX_ATTEMPTS) {
83*c8dee2aaSAndroid Build Coastguard Worker                        console.warn(`got error in fetching ${url}, retrying`, err);
84*c8dee2aaSAndroid Build Coastguard Worker                        retryAfterDelay();
85*c8dee2aaSAndroid Build Coastguard Worker                    } else {
86*c8dee2aaSAndroid Build Coastguard Worker                        console.error(`got error in fetching ${url} even after ${attempts} attempts`, err);
87*c8dee2aaSAndroid Build Coastguard Worker                        reject(err);
88*c8dee2aaSAndroid Build Coastguard Worker                    }
89*c8dee2aaSAndroid Build Coastguard Worker                });
90*c8dee2aaSAndroid Build Coastguard Worker        };
91*c8dee2aaSAndroid Build Coastguard Worker        const retryAfterDelay = () => {
92*c8dee2aaSAndroid Build Coastguard Worker            setTimeout(() => {
93*c8dee2aaSAndroid Build Coastguard Worker                attemptFetch();
94*c8dee2aaSAndroid Build Coastguard Worker            }, DELAY_AFTER_FAILURE);
95*c8dee2aaSAndroid Build Coastguard Worker        }
96*c8dee2aaSAndroid Build Coastguard Worker        attemptFetch();
97*c8dee2aaSAndroid Build Coastguard Worker    });
98*c8dee2aaSAndroid Build Coastguard Worker
99*c8dee2aaSAndroid Build Coastguard Worker}
100*c8dee2aaSAndroid Build Coastguard Worker
101*c8dee2aaSAndroid Build Coastguard Worker/**
102*c8dee2aaSAndroid Build Coastguard Worker * Takes a name, a callback, and any number of assets or promises. It executes the
103*c8dee2aaSAndroid Build Coastguard Worker * callback (presumably, the test) and reports the resulting surface to Gold.
104*c8dee2aaSAndroid Build Coastguard Worker * @param name {string}
105*c8dee2aaSAndroid Build Coastguard Worker * @param callback {Function}, has two params, the first is a CanvasKit.Canvas
106*c8dee2aaSAndroid Build Coastguard Worker *    and the second is an array of results from the passed in assets or promises.
107*c8dee2aaSAndroid Build Coastguard Worker *    If a given assetOrPromise was a string, the result will be an ArrayBuffer.
108*c8dee2aaSAndroid Build Coastguard Worker * @param assetsToFetchOrPromisesToWaitOn {string|Promise}. If a string, it will
109*c8dee2aaSAndroid Build Coastguard Worker *    be treated as a url to fetch and return an ArrayBuffer with the contents as
110*c8dee2aaSAndroid Build Coastguard Worker *    a result in the callback. Otherwise, the promise will be waited on and its
111*c8dee2aaSAndroid Build Coastguard Worker *    result will be whatever the promise resolves to.
112*c8dee2aaSAndroid Build Coastguard Worker */
113*c8dee2aaSAndroid Build Coastguard Workerconst gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
114*c8dee2aaSAndroid Build Coastguard Worker    _commonGM(it, false, name, callback, assetsToFetchOrPromisesToWaitOn);
115*c8dee2aaSAndroid Build Coastguard Worker};
116*c8dee2aaSAndroid Build Coastguard Worker
117*c8dee2aaSAndroid Build Coastguard Worker/**
118*c8dee2aaSAndroid Build Coastguard Worker *  fgm is like gm, except only tests declared with fgm, force_gm, or fit will be
119*c8dee2aaSAndroid Build Coastguard Worker *  executed. This mimics the behavior of Jasmine.js.
120*c8dee2aaSAndroid Build Coastguard Worker */
121*c8dee2aaSAndroid Build Coastguard Workerconst fgm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
122*c8dee2aaSAndroid Build Coastguard Worker    _commonGM(fit, false, name, callback, assetsToFetchOrPromisesToWaitOn);
123*c8dee2aaSAndroid Build Coastguard Worker};
124*c8dee2aaSAndroid Build Coastguard Worker
125*c8dee2aaSAndroid Build Coastguard Worker/**
126*c8dee2aaSAndroid Build Coastguard Worker *  force_gm is like gm, except only tests declared with fgm, force_gm, or fit will be
127*c8dee2aaSAndroid Build Coastguard Worker *  executed. This mimics the behavior of Jasmine.js.
128*c8dee2aaSAndroid Build Coastguard Worker */
129*c8dee2aaSAndroid Build Coastguard Workerconst force_gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
130*c8dee2aaSAndroid Build Coastguard Worker    fgm(name, callback, assetsToFetchOrPromisesToWaitOn);
131*c8dee2aaSAndroid Build Coastguard Worker};
132*c8dee2aaSAndroid Build Coastguard Worker
133*c8dee2aaSAndroid Build Coastguard Worker/**
134*c8dee2aaSAndroid Build Coastguard Worker *  skip_gm does nothing. It is a convenient way to skip a test temporarily.
135*c8dee2aaSAndroid Build Coastguard Worker */
136*c8dee2aaSAndroid Build Coastguard Workerconst skip_gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
137*c8dee2aaSAndroid Build Coastguard Worker    console.log(`Skipping gm ${name}`);
138*c8dee2aaSAndroid Build Coastguard Worker    // do nothing, skip the test for now
139*c8dee2aaSAndroid Build Coastguard Worker};
140*c8dee2aaSAndroid Build Coastguard Worker
141*c8dee2aaSAndroid Build Coastguard Worker/**
142*c8dee2aaSAndroid Build Coastguard Worker *  pause_gm is like fgm, except the test will not finish right away and clear,
143*c8dee2aaSAndroid Build Coastguard Worker *  making it ideal for a human to manually inspect the results.
144*c8dee2aaSAndroid Build Coastguard Worker */
145*c8dee2aaSAndroid Build Coastguard Workerconst pause_gm = (name, callback, ...assetsToFetchOrPromisesToWaitOn) => {
146*c8dee2aaSAndroid Build Coastguard Worker    _commonGM(fit, true, name, callback, assetsToFetchOrPromisesToWaitOn);
147*c8dee2aaSAndroid Build Coastguard Worker};
148*c8dee2aaSAndroid Build Coastguard Worker
149*c8dee2aaSAndroid Build Coastguard Workerconst _commonMultipleCanvasGM = (it, pause, name, callback) => {
150*c8dee2aaSAndroid Build Coastguard Worker    if (name.includes(' ')) {
151*c8dee2aaSAndroid Build Coastguard Worker        throw name + " cannot contain spaces";
152*c8dee2aaSAndroid Build Coastguard Worker    }
153*c8dee2aaSAndroid Build Coastguard Worker    it(`draws gm ${name} on both CanvasKit and using Canvas2D`, (done) => {
154*c8dee2aaSAndroid Build Coastguard Worker        const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
155*c8dee2aaSAndroid Build Coastguard Worker        skcanvas._config = 'software_canvas';
156*c8dee2aaSAndroid Build Coastguard Worker        const realCanvas = document.getElementById('test');
157*c8dee2aaSAndroid Build Coastguard Worker        realCanvas._config = 'html_canvas';
158*c8dee2aaSAndroid Build Coastguard Worker        realCanvas.width = CANVAS_WIDTH;
159*c8dee2aaSAndroid Build Coastguard Worker        realCanvas.height = CANVAS_HEIGHT;
160*c8dee2aaSAndroid Build Coastguard Worker
161*c8dee2aaSAndroid Build Coastguard Worker        if (pause) {
162*c8dee2aaSAndroid Build Coastguard Worker            console.log('debugging canvaskit version');
163*c8dee2aaSAndroid Build Coastguard Worker            callback(realCanvas);
164*c8dee2aaSAndroid Build Coastguard Worker            callback(skcanvas);
165*c8dee2aaSAndroid Build Coastguard Worker            const png = skcanvas.toDataURL();
166*c8dee2aaSAndroid Build Coastguard Worker            const img = document.createElement('img');
167*c8dee2aaSAndroid Build Coastguard Worker            document.body.appendChild(img);
168*c8dee2aaSAndroid Build Coastguard Worker            img.src = png;
169*c8dee2aaSAndroid Build Coastguard Worker            debugger;
170*c8dee2aaSAndroid Build Coastguard Worker            return;
171*c8dee2aaSAndroid Build Coastguard Worker        }
172*c8dee2aaSAndroid Build Coastguard Worker
173*c8dee2aaSAndroid Build Coastguard Worker        const promises = [];
174*c8dee2aaSAndroid Build Coastguard Worker
175*c8dee2aaSAndroid Build Coastguard Worker        for (const canvas of [skcanvas, realCanvas]) {
176*c8dee2aaSAndroid Build Coastguard Worker            callback(canvas);
177*c8dee2aaSAndroid Build Coastguard Worker            // canvas has .toDataURL (even though skcanvas is not a real Canvas)
178*c8dee2aaSAndroid Build Coastguard Worker            // so this will work.
179*c8dee2aaSAndroid Build Coastguard Worker            promises.push(reportCanvas(canvas, name, canvas._config));
180*c8dee2aaSAndroid Build Coastguard Worker        }
181*c8dee2aaSAndroid Build Coastguard Worker        Promise.all(promises).then(() => {
182*c8dee2aaSAndroid Build Coastguard Worker            skcanvas.dispose();
183*c8dee2aaSAndroid Build Coastguard Worker            done();
184*c8dee2aaSAndroid Build Coastguard Worker        }).catch(reportError(done));
185*c8dee2aaSAndroid Build Coastguard Worker    });
186*c8dee2aaSAndroid Build Coastguard Worker};
187*c8dee2aaSAndroid Build Coastguard Worker
188*c8dee2aaSAndroid Build Coastguard Worker/**
189*c8dee2aaSAndroid Build Coastguard Worker * Takes a name and a callback. It executes the callback (presumably, the test)
190*c8dee2aaSAndroid Build Coastguard Worker * for both a CanvasKit.Canvas and a native Canvas2D. The result of both will be
191*c8dee2aaSAndroid Build Coastguard Worker * uploaded to Gold.
192*c8dee2aaSAndroid Build Coastguard Worker * @param name {string}
193*c8dee2aaSAndroid Build Coastguard Worker * @param callback {Function}, has one param, either a CanvasKit.Canvas or a native
194*c8dee2aaSAndroid Build Coastguard Worker *    Canvas2D object.
195*c8dee2aaSAndroid Build Coastguard Worker */
196*c8dee2aaSAndroid Build Coastguard Workerconst multipleCanvasGM = (name, callback) => {
197*c8dee2aaSAndroid Build Coastguard Worker    _commonMultipleCanvasGM(it, false, name, callback);
198*c8dee2aaSAndroid Build Coastguard Worker};
199*c8dee2aaSAndroid Build Coastguard Worker
200*c8dee2aaSAndroid Build Coastguard Worker/**
201*c8dee2aaSAndroid Build Coastguard Worker *  fmultipleCanvasGM is like multipleCanvasGM, except only tests declared with
202*c8dee2aaSAndroid Build Coastguard Worker *  fmultipleCanvasGM, force_multipleCanvasGM, or fit will be executed. This
203*c8dee2aaSAndroid Build Coastguard Worker *  mimics the behavior of Jasmine.js.
204*c8dee2aaSAndroid Build Coastguard Worker */
205*c8dee2aaSAndroid Build Coastguard Workerconst fmultipleCanvasGM = (name, callback) => {
206*c8dee2aaSAndroid Build Coastguard Worker    _commonMultipleCanvasGM(fit, false, name, callback);
207*c8dee2aaSAndroid Build Coastguard Worker};
208*c8dee2aaSAndroid Build Coastguard Worker
209*c8dee2aaSAndroid Build Coastguard Worker/**
210*c8dee2aaSAndroid Build Coastguard Worker *  force_multipleCanvasGM is like multipleCanvasGM, except only tests declared
211*c8dee2aaSAndroid Build Coastguard Worker *  with fmultipleCanvasGM, force_multipleCanvasGM, or fit will be executed. This
212*c8dee2aaSAndroid Build Coastguard Worker *  mimics the behavior of Jasmine.js.
213*c8dee2aaSAndroid Build Coastguard Worker */
214*c8dee2aaSAndroid Build Coastguard Workerconst force_multipleCanvasGM = (name, callback) => {
215*c8dee2aaSAndroid Build Coastguard Worker    fmultipleCanvasGM(name, callback);
216*c8dee2aaSAndroid Build Coastguard Worker};
217*c8dee2aaSAndroid Build Coastguard Worker
218*c8dee2aaSAndroid Build Coastguard Worker/**
219*c8dee2aaSAndroid Build Coastguard Worker *  pause_multipleCanvasGM is like fmultipleCanvasGM, except the test will not
220*c8dee2aaSAndroid Build Coastguard Worker *  finish right away and clear, making it ideal for a human to manually inspect the results.
221*c8dee2aaSAndroid Build Coastguard Worker */
222*c8dee2aaSAndroid Build Coastguard Workerconst pause_multipleCanvasGM = (name, callback) => {
223*c8dee2aaSAndroid Build Coastguard Worker    _commonMultipleCanvasGM(fit, true, name, callback);
224*c8dee2aaSAndroid Build Coastguard Worker};
225*c8dee2aaSAndroid Build Coastguard Worker
226*c8dee2aaSAndroid Build Coastguard Worker/**
227*c8dee2aaSAndroid Build Coastguard Worker *  skip_multipleCanvasGM does nothing. It is a convenient way to skip a test temporarily.
228*c8dee2aaSAndroid Build Coastguard Worker */
229*c8dee2aaSAndroid Build Coastguard Workerconst skip_multipleCanvasGM = (name, callback) => {
230*c8dee2aaSAndroid Build Coastguard Worker    console.log(`Skipping multiple canvas gm ${name}`);
231*c8dee2aaSAndroid Build Coastguard Worker};
232*c8dee2aaSAndroid Build Coastguard Worker
233*c8dee2aaSAndroid Build Coastguard Worker
234*c8dee2aaSAndroid Build Coastguard Workerfunction reportSurface(surface, testname, done) {
235*c8dee2aaSAndroid Build Coastguard Worker    // Sometimes, the webgl canvas is blank, but the surface has the pixel
236*c8dee2aaSAndroid Build Coastguard Worker    // data. So, we copy it out and draw it to a normal canvas to take a picture.
237*c8dee2aaSAndroid Build Coastguard Worker    // To be consistent across CPU and GPU, we just do it for all configurations
238*c8dee2aaSAndroid Build Coastguard Worker    // (even though the CPU canvas shows up after flush just fine).
239*c8dee2aaSAndroid Build Coastguard Worker    let pixels = surface.getCanvas().readPixels(0, 0, {
240*c8dee2aaSAndroid Build Coastguard Worker        width: CANVAS_WIDTH,
241*c8dee2aaSAndroid Build Coastguard Worker        height: CANVAS_HEIGHT,
242*c8dee2aaSAndroid Build Coastguard Worker        colorType: CanvasKit.ColorType.RGBA_8888,
243*c8dee2aaSAndroid Build Coastguard Worker        alphaType: CanvasKit.AlphaType.Unpremul,
244*c8dee2aaSAndroid Build Coastguard Worker        colorSpace: CanvasKit.ColorSpace.SRGB,
245*c8dee2aaSAndroid Build Coastguard Worker    });
246*c8dee2aaSAndroid Build Coastguard Worker    if (!pixels) {
247*c8dee2aaSAndroid Build Coastguard Worker        throw 'Could not get pixels for test '+testname;
248*c8dee2aaSAndroid Build Coastguard Worker    }
249*c8dee2aaSAndroid Build Coastguard Worker    pixels = new Uint8ClampedArray(pixels.buffer);
250*c8dee2aaSAndroid Build Coastguard Worker    const imageData = new ImageData(pixels, CANVAS_WIDTH, CANVAS_HEIGHT);
251*c8dee2aaSAndroid Build Coastguard Worker
252*c8dee2aaSAndroid Build Coastguard Worker    const reportingCanvas = document.getElementById('report');
253*c8dee2aaSAndroid Build Coastguard Worker    if (!reportingCanvas) {
254*c8dee2aaSAndroid Build Coastguard Worker        throw 'Reporting canvas not found';
255*c8dee2aaSAndroid Build Coastguard Worker    }
256*c8dee2aaSAndroid Build Coastguard Worker    reportingCanvas.getContext('2d').putImageData(imageData, 0, 0);
257*c8dee2aaSAndroid Build Coastguard Worker    if (!done) {
258*c8dee2aaSAndroid Build Coastguard Worker        return;
259*c8dee2aaSAndroid Build Coastguard Worker    }
260*c8dee2aaSAndroid Build Coastguard Worker    reportCanvas(reportingCanvas, testname).then(() => {
261*c8dee2aaSAndroid Build Coastguard Worker        surface.delete();
262*c8dee2aaSAndroid Build Coastguard Worker        done();
263*c8dee2aaSAndroid Build Coastguard Worker    }).catch(reportError(done));
264*c8dee2aaSAndroid Build Coastguard Worker}
265*c8dee2aaSAndroid Build Coastguard Worker
266*c8dee2aaSAndroid Build Coastguard Worker
267*c8dee2aaSAndroid Build Coastguard Workerfunction starPath(CanvasKit, X=128, Y=128, R=116) {
268*c8dee2aaSAndroid Build Coastguard Worker    const p = new CanvasKit.Path();
269*c8dee2aaSAndroid Build Coastguard Worker    p.moveTo(X + R, Y);
270*c8dee2aaSAndroid Build Coastguard Worker    for (let i = 1; i < 8; i++) {
271*c8dee2aaSAndroid Build Coastguard Worker      let a = 2.6927937 * i;
272*c8dee2aaSAndroid Build Coastguard Worker      p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
273*c8dee2aaSAndroid Build Coastguard Worker    }
274*c8dee2aaSAndroid Build Coastguard Worker    p.close();
275*c8dee2aaSAndroid Build Coastguard Worker    return p;
276*c8dee2aaSAndroid Build Coastguard Worker}
277