xref: /aosp_15_r20/external/skia/modules/canvaskit/npm_build/example.html (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1<!DOCTYPE html>
2<title>CanvasKit (Skia via Web Assembly)</title>
3<meta charset="utf-8" />
4<meta http-equiv="X-UA-Compatible" content="IE=edge">
5<meta name="viewport" content="width=device-width, initial-scale=1.0">
6
7<style>
8  canvas, img {
9    border: 1px dashed #AAA;
10  }
11  #api5_c, #api6_c {
12      width: 300px;
13      height: 300px;
14  }
15
16</style>
17
18<h2> Drop in replacement for HTML Canvas (e.g. node.js)</h2>
19<img id=api1 width=300 height=300>
20<canvas id=api1_c width=300 height=300></canvas>
21<img id=api2 width=300 height=300>
22<canvas id=api2_c width=300 height=300></canvas>
23<img id=api3 width=300 height=300>
24<canvas id=api3_c width=300 height=300></canvas>
25<img id=api4 width=300 height=300>
26<canvas id=api4_c width=300 height=300></canvas>
27<img id=api5 width=300 height=300>
28<canvas id=api5_c width=300 height=300></canvas>
29<img id=api6 width=300 height=300>
30<canvas id=api6_c width=300 height=300></canvas>
31<img id=api7 width=300 height=300>
32<canvas id=api7_c width=300 height=300></canvas>
33<img id=api8 width=300 height=300>
34<canvas id=api8_c width=300 height=300></canvas>
35
36<h2 id=title1> CanvasKit expands the functionality of a stock HTML canvas</h2>
37<canvas id=vertex1 width=300 height=300></canvas>
38<canvas id=gradient1 width=300 height=300></canvas>
39<canvas id=patheffect width=300 height=300></canvas>
40<canvas id=paths width=300 height=300></canvas>
41<canvas id=pathperson width=300 height=300></canvas>
42<canvas id=ink width=300 height=300></canvas>
43<canvas id=surfaces width=300 height=300></canvas>
44<canvas id=atlas width=300 height=300></canvas>
45<canvas id=decode width=300 height=300></canvas>
46
47<h2 id=title2> CanvasKit can allow for text measurement/placement (e.g. breaking, kerning)</h2>
48<canvas id=textonpath width=300 height=300></canvas>
49<canvas id=drawGlyphs width=300 height=300></canvas>
50
51<h2 id=title3> Interactive drawPatch</h2>
52<canvas id=interdrawpatch width=512 height=512></canvas>
53
54<script type="text/javascript" src="/build/canvaskit.js"></script>
55
56<script type="text/javascript" charset="utf-8" async>
57
58  var CanvasKit = null;
59  var cdn = 'https://storage.googleapis.com/skia-cdn/misc/';
60
61  const ckLoaded = CanvasKitInit({locateFile: (file) => '/build/'+file});
62
63  const loadRoboto = fetch(cdn + 'Roboto-Regular.ttf').then((response) => response.arrayBuffer());
64  const loadNotoSerif = fetch(cdn + 'NotoSerif-Regular.ttf').then((response) => response.arrayBuffer());
65  const loadTestImage = fetch(cdn + 'test.png').then((response) => response.arrayBuffer());
66
67  async function initWebGpu(CK) {
68    if (navigator.gpu && CK.webgpu) {
69      const adapter = await navigator.gpu.requestAdapter();
70      const device = await adapter.requestDevice();
71      var gpu = CK.MakeGPUDeviceContext(device);
72      if (!gpu) {
73        console.error('Failed to initialize WebGPU device context');
74      }
75      return gpu;
76    }
77    return null;
78  }
79
80  const ready = async function() {
81    let CK = await ckLoaded;
82    let gpu = await initWebGpu(CK);
83    return [CK, gpu];
84  }();
85
86  // Examples which only require canvaskit
87  ready.then((initData) => {
88    const [CK, gpu] = initData;
89    if (gpu) {
90      let titles = ['title1', 'title2', 'title3'];
91      titles.forEach((title) => document.getElementById(title).innerText += " (using WebGPU)");
92    }
93    CanvasKit = CK;
94    PathExample(CanvasKit);
95    InkExample(CanvasKit, gpu);
96    PathPersonExample(CanvasKit, gpu);
97    VertexAPI1(CanvasKit, gpu);
98    GradientAPI1(CanvasKit, gpu);
99    TextOnPathAPI1(CanvasKit, gpu);
100    DrawGlyphsAPI1(CanvasKit, gpu);
101    SurfaceAPI1(CanvasKit, gpu);
102    if (CanvasKit.MakeCanvas){
103      CanvasAPI1(CanvasKit);
104      CanvasAPI2(CanvasKit);
105      CanvasAPI3(CanvasKit);
106      CanvasAPI4(CanvasKit);
107      CanvasAPI5(CanvasKit);
108      CanvasAPI6(CanvasKit);
109      CanvasAPI7(CanvasKit);
110      CanvasAPI8(CanvasKit);
111    } else {
112      console.log("Skipping CanvasAPI1 because it's not compiled in");
113    }
114    InteractivePatch(CanvasKit, gpu);
115  });
116
117  // Examples requiring external resources
118  Promise.all([ready, loadRoboto]).then((results) => {DrawingExample(...results.flat())});
119  Promise.all([ready, loadTestImage]).then((results) => {AtlasAPI1(...results.flat())});
120  Promise.all([ckLoaded, loadTestImage]).then((results) => {DecodeAPI(...results)});
121
122  // Helper function to create an optional WebGPU canvas surface, if WebGPU is supported. Falls back
123  // to CanvasKit.MakeCanvasSurface for SW/WebGL otherwise.
124  function MakeCanvasSurface(CanvasKit, gpu, canvasId) {
125    if (gpu) {
126      const canvasContext = CanvasKit.MakeGPUCanvasContext(
127          gpu, document.getElementById(canvasId));
128      if (!canvasContext) {
129        console.error('Failed to configure WebGPU canvas context');
130        return;
131      }
132      const surface = CanvasKit.MakeGPUCanvasSurface(canvasContext);
133      if (!surface) {
134        console.error('Failed to initialize current swapchain Surface');
135      }
136      return surface;
137    }
138    return CanvasKit.MakeCanvasSurface(canvasId);
139  }
140
141  function DrawingExample(CanvasKit, gpu, robotoData) {
142    if (!robotoData || !CanvasKit) {
143      return;
144    }
145    const surface = MakeCanvasSurface(CanvasKit, gpu, 'patheffect');
146    if (!surface) {
147      console.error('Could not make surface');
148      return;
149    }
150
151    const paint = new CanvasKit.Paint();
152    const roboto = CanvasKit.Typeface.MakeTypefaceFromData(robotoData);
153
154    const textPaint = new CanvasKit.Paint();
155    textPaint.setColor(CanvasKit.RED);
156    textPaint.setAntiAlias(true);
157
158    const textFont = new CanvasKit.Font(roboto, 30);
159
160    let i = 0;
161
162    let X = 128;
163    let Y = 128;
164
165    function drawFrame(canvas) {
166      const path = starPath(CanvasKit, X, Y);
167      // Some animations see performance improvements by marking their
168      // paths as volatile.
169      path.setIsVolatile(true);
170      const dpe = CanvasKit.PathEffect.MakeDash([15, 5, 5, 10], i/5);
171      i++;
172
173      paint.setPathEffect(dpe);
174      paint.setStyle(CanvasKit.PaintStyle.Stroke);
175      paint.setStrokeWidth(5.0 + -3 * Math.cos(i/30));
176      paint.setAntiAlias(true);
177      paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
178
179      canvas.clear(CanvasKit.TRANSPARENT);
180
181      canvas.drawPath(path, paint);
182      canvas.drawText('Try Clicking!', 10, 280, textPaint, textFont);
183
184      dpe.delete();
185      path.delete();
186      surface.requestAnimationFrame(drawFrame);
187    }
188    surface.requestAnimationFrame(drawFrame);
189
190    // Make animation interactive
191    let interact = (e) => {
192      if (!e.pressure) {
193        return;
194      }
195      X = e.offsetX;
196      Y = e.offsetY;
197    };
198    document.getElementById('patheffect').addEventListener('pointermove', interact);
199    document.getElementById('patheffect').addEventListener('pointerdown', interact);
200    preventScrolling(document.getElementById('patheffect'));
201    // A client would need to delete this if it didn't go on for ever.
202    // paint.delete();
203    // textPaint.delete();
204    // textFont.delete();
205  }
206
207   function InteractivePatch(CanvasKit, gpu) {
208     const ELEM = 'interdrawpatch';
209     const surface = MakeCanvasSurface(CanvasKit, gpu, ELEM);
210     if (!surface) {
211       console.error('Could not make surface');
212       return;
213     }
214
215     let live_corner, live_index;
216
217     const paint = new CanvasKit.Paint();
218     const pts_paint = new CanvasKit.Paint();
219     pts_paint.setStyle(CanvasKit.PaintStyle.Stroke);
220     pts_paint.setStrokeWidth(9);
221     pts_paint.setStrokeCap(CanvasKit.StrokeCap.Round);
222
223     const line_paint = new CanvasKit.Paint();
224     line_paint.setStyle(CanvasKit.PaintStyle.Stroke);
225     line_paint.setStrokeWidth(2);
226
227     const colors = [CanvasKit.RED, CanvasKit.BLUE, CanvasKit.YELLOW, CanvasKit.CYAN];
228
229     const patch = [
230          [ 10,170,   10, 10,  170, 10],  // prev_vector, point, next_vector
231          [340, 10,  500, 10,  500,170],
232          [500,340,  500,500,  340,500],
233          [170,500,   10,500,   10,340],
234      ];
235
236      function get_corner(corner, index) {
237          return [corner[index*2+0], corner[index*2+1]];
238      }
239
240      function push_xy(array, xy) {
241          array.push(xy[0], xy[1]);
242      }
243
244      function patch_to_cubics(patch) {
245          const array = [];
246          push_xy(array, get_corner(patch[0],1));
247          push_xy(array, get_corner(patch[0],2));
248          for (let i = 1; i < 4; ++i) {
249              push_xy(array, get_corner(patch[i],0));
250              push_xy(array, get_corner(patch[i],1));
251              push_xy(array, get_corner(patch[i],2));
252          }
253          push_xy(array, get_corner(patch[0],0));
254
255          return array;
256      }
257
258     function drawFrame(canvas) {
259         const cubics = patch_to_cubics(patch);
260
261         canvas.drawColor(CanvasKit.WHITE);
262         canvas.drawPatch(cubics, colors, null, CanvasKit.BlendMode.Dst, paint);
263         if (live_corner) {
264             canvas.drawPoints(CanvasKit.PointMode.Polygon, live_corner, line_paint);
265         }
266         canvas.drawPoints(CanvasKit.PointMode.Points, cubics, pts_paint);
267         surface.requestAnimationFrame(drawFrame);
268     }
269     surface.requestAnimationFrame(drawFrame);
270
271     function length2(x, y) {
272         return x*x + y*y;
273     }
274     function hit_test(x,y, x1,y1) {
275         return length2(x-x1, y-y1) <= 10*10;
276     }
277     function pointer_up(e) {
278         live_corner = null;
279         live_index = null;
280      }
281
282     function pointer_down(e) {
283         live_corner = null;
284         live_index = null;
285         for (p of patch) {
286             for (let i = 0; i < 6; i += 2) {
287                 if (hit_test(p[i], p[i+1], e.offsetX, e.offsetY)) {
288                     live_corner = p;
289                     live_index = i;
290                 }
291             }
292         }
293      }
294
295     function pointer_move(e) {
296       if (e.pressure && live_corner) {
297           if (live_index == 2) {
298               // corner
299               const dx = e.offsetX - live_corner[2];
300               const dy = e.offsetY - live_corner[3];
301               for  (let i = 0; i < 3; ++i) {
302                   live_corner[i*2+0] += dx;
303                   live_corner[i*2+1] += dy;
304               }
305           } else {
306               // control-point
307               live_corner[live_index+0] = e.offsetX;
308               live_corner[live_index+1] = e.offsetY;
309            }
310        }
311     }
312     document.getElementById(ELEM).addEventListener('pointermove', pointer_move);
313     document.getElementById(ELEM).addEventListener('pointerdown', pointer_down);
314     document.getElementById(ELEM).addEventListener('pointerup', pointer_up);
315     preventScrolling(document.getElementById(ELEM));
316   }
317
318  function PathPersonExample(CanvasKit, gpu) {
319    const surface = MakeCanvasSurface(CanvasKit, gpu, 'pathperson');
320    if (!surface) {
321      console.error('Could not make surface');
322      return;
323    }
324
325    function drawFrame(canvas) {
326      const paint = new CanvasKit.Paint();
327      paint.setStrokeWidth(1.0);
328      paint.setAntiAlias(true);
329      paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
330      paint.setStyle(CanvasKit.PaintStyle.Stroke);
331
332      const path = new CanvasKit.Path();
333      path.moveTo(10, 10);
334      path.lineTo(100, 10);
335      path.moveTo(10, 10);
336      path.lineTo(10, 200);
337      path.moveTo(10, 100);
338      path.lineTo(100,100);
339      path.moveTo(10, 200);
340      path.lineTo(100, 200);
341
342      canvas.drawPath(path, paint);
343      path.delete();
344      paint.delete();
345    }
346    // Intentionally just draw frame once
347    surface.drawOnce(drawFrame);
348  }
349
350  function PathExample(CanvasKit) {
351    const surface = CanvasKit.MakeSWCanvasSurface('paths');
352    if (!surface) {
353      console.error('Could not make surface');
354      return;
355    }
356
357    function drawFrame(canvas) {
358      const paint = new CanvasKit.Paint();
359      paint.setStrokeWidth(1.0);
360      paint.setAntiAlias(true);
361      paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
362      paint.setStyle(CanvasKit.PaintStyle.Stroke);
363
364      const path = new CanvasKit.Path();
365      path.moveTo(20, 5);
366      path.lineTo(30, 20);
367      path.lineTo(40, 10);
368      path.lineTo(50, 20);
369      path.lineTo(60, 0);
370      path.lineTo(20, 5);
371
372      path.moveTo(20, 80);
373      path.cubicTo(90, 10, 160, 150, 190, 10);
374
375      path.moveTo(36, 148);
376      path.quadTo(66, 188, 120, 136);
377      path.lineTo(36, 148);
378
379      path.moveTo(150, 180);
380      path.arcToTangent(150, 100, 50, 200, 20);
381      path.lineTo(160, 160);
382
383      path.moveTo(20, 120);
384      path.lineTo(20, 120);
385
386      canvas.drawPath(path, paint);
387
388      const rrect = CanvasKit.RRectXY([100, 10, 140, 62], 10, 4);
389
390      const rrectPath = new CanvasKit.Path().addRRect(rrect, true);
391
392      canvas.drawPath(rrectPath, paint);
393
394      rrectPath.delete();
395      path.delete();
396      paint.delete();
397    }
398    // Intentionally just draw frame once
399    surface.drawOnce(drawFrame);
400  }
401
402  function preventScrolling(canvas) {
403    canvas.addEventListener('touchmove', (e) => {
404      // Prevents touch events in the canvas from scrolling the canvas.
405      e.preventDefault();
406      e.stopPropagation();
407    });
408  }
409
410  function InkExample(CanvasKit, gpu) {
411    const surface = MakeCanvasSurface(CanvasKit, gpu, 'ink');
412    if (!surface) {
413      console.error('Could not make surface');
414      return;
415    }
416
417    let paint = new CanvasKit.Paint();
418    paint.setAntiAlias(true);
419    paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
420    paint.setStyle(CanvasKit.PaintStyle.Stroke);
421    paint.setStrokeWidth(4.0);
422    paint.setPathEffect(CanvasKit.PathEffect.MakeCorner(50));
423
424    // Draw I N K
425    let path = new CanvasKit.Path();
426    path.moveTo(80, 30);
427    path.lineTo(80, 80);
428
429    path.moveTo(100, 80);
430    path.lineTo(100, 15);
431    path.lineTo(130, 95);
432    path.lineTo(130, 30);
433
434    path.moveTo(150, 30);
435    path.lineTo(150, 80);
436    path.moveTo(170, 30);
437    path.lineTo(150, 55);
438    path.lineTo(170, 80);
439
440    let paths = [path];
441    let paints = [paint];
442
443    function drawFrame(canvas) {
444      canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
445
446      for (let i = 0; i < paints.length && i < paths.length; i++) {
447        canvas.drawPath(paths[i], paints[i]);
448      }
449
450      surface.requestAnimationFrame(drawFrame);
451    }
452
453    let hold = false;
454    let interact = (e) => {
455      let type = e.type;
456      if (type === 'lostpointercapture' || type === 'pointerup' || !e.pressure ) {
457        hold = false;
458        return;
459      }
460      if (hold) {
461        path.lineTo(e.offsetX, e.offsetY);
462      } else {
463        paint = paint.copy();
464        paint.setColor(CanvasKit.Color(Math.random() * 255, Math.random() * 255, Math.random() * 255, Math.random() + .2));
465        paints.push(paint);
466        path = new CanvasKit.Path();
467        paths.push(path);
468        path.moveTo(e.offsetX, e.offsetY);
469      }
470      hold = true;
471    };
472    document.getElementById('ink').addEventListener('pointermove', interact);
473    document.getElementById('ink').addEventListener('pointerdown', interact);
474    document.getElementById('ink').addEventListener('lostpointercapture', interact);
475    document.getElementById('ink').addEventListener('pointerup', interact);
476    preventScrolling(document.getElementById('ink'));
477    surface.requestAnimationFrame(drawFrame);
478  }
479
480  function starPath(CanvasKit, X=128, Y=128, R=116) {
481    let p = new CanvasKit.Path();
482    p.moveTo(X + R, Y);
483    for (let i = 1; i < 8; i++) {
484      let a = 2.6927937 * i;
485      p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
486    }
487    return p;
488  }
489
490  function CanvasAPI1(CanvasKit) {
491    let skcanvas = CanvasKit.MakeCanvas(300, 300);
492    let realCanvas = document.getElementById('api1_c');
493
494    let skPromise   = fetch(cdn + 'test.png')
495                        // if clients want to use a Blob, they are responsible
496                        // for reading it themselves.
497                        .then((response) => response.arrayBuffer())
498                        .then((buffer) => {
499                          skcanvas._img = skcanvas.decodeImage(buffer);
500                        });
501    let realPromise = fetch(cdn + 'test.png')
502                        .then((response) => response.blob())
503                        .then((blob) => createImageBitmap(blob))
504                        .then((bitmap) => {
505                          realCanvas._img = bitmap;
506                        });
507
508    let realFontLoaded = new FontFace('Bungee', 'url(/tests/assets/Bungee-Regular.ttf)', {
509      'family': 'Bungee',
510      'style': 'normal',
511      'weight': '400',
512    }).load().then((font) => {
513      document.fonts.add(font);
514    });
515
516    let skFontLoaded = fetch('/tests/assets/Bungee-Regular.ttf').then(
517                             (response) => response.arrayBuffer()).then(
518                             (buffer) => {
519                                // loadFont is synchronous
520                                skcanvas.loadFont(buffer, {
521                                  'family': 'Bungee',
522                                  'style': 'normal',
523                                  'weight': '400',
524                                });
525                              });
526
527    Promise.all([realPromise, skPromise, realFontLoaded, skFontLoaded]).then(() => {
528      for (let canvas of [skcanvas, realCanvas]) {
529        let ctx = canvas.getContext('2d');
530        ctx.fillStyle = '#EEE';
531        ctx.fillRect(0, 0, 300, 300);
532        ctx.fillStyle = 'black';
533        ctx.font = '26px Bungee';
534        ctx.rotate(.1);
535        let text = ctx.measureText('Awesome');
536        ctx.fillText('Awesome ', 25, 100);
537        ctx.strokeText('Groovy!', 35 + text.width, 100);
538
539        // Draw line under Awesome
540        ctx.strokeStyle = 'rgba(125,0,0,0.5)';
541        ctx.beginPath();
542        ctx.lineWidth = 6;
543        ctx.moveTo(25, 105);
544        ctx.lineTo(200, 105);
545        ctx.stroke();
546
547        // squished vertically
548        ctx.globalAlpha = 0.7;
549        ctx.imageSmoothingQuality = 'medium';
550        ctx.drawImage(canvas._img, 150, 150, 150, 100);
551        ctx.rotate(-.2);
552        ctx.imageSmoothingEnabled = false;
553        ctx.drawImage(canvas._img, 100, 150, 400, 350, 10, 200, 150, 100);
554
555        let idata = ctx.getImageData(80, 220, 40, 45);
556        ctx.putImageData(idata, 250, 10);
557        ctx.putImageData(idata, 200, 10, 20, 10, 20, 30);
558        ctx.resetTransform();
559        ctx.strokeStyle = 'black';
560        ctx.lineWidth = 1;
561        ctx.strokeRect(200, 10, 40, 45);
562
563        idata = ctx.createImageData(10, 20);
564        ctx.putImageData(idata, 10, 10);
565      }
566
567      document.getElementById('api1').src = skcanvas.toDataURL();
568      skcanvas.dispose();
569    });
570
571  }
572
573  function CanvasAPI2(CanvasKit) {
574    let skcanvas = CanvasKit.MakeCanvas(300, 300);
575    let realCanvas = document.getElementById('api2_c');
576    realCanvas.width = 300;
577    realCanvas.height = 300;
578
579    // svg data for a clock
580    skcanvas._path = skcanvas.makePath2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
581    realCanvas._path = new Path2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
582
583    for (let canvas of [skcanvas, realCanvas]) {
584      let ctx = canvas.getContext('2d');
585      ctx.scale(1.5, 1.5);
586      ctx.moveTo(20, 5);
587      ctx.lineTo(30, 20);
588      ctx.lineTo(40, 10);
589      ctx.lineTo(50, 20);
590      ctx.lineTo(60, 0);
591      ctx.lineTo(20, 5);
592
593      ctx.moveTo(20, 80);
594      ctx.bezierCurveTo(90, 10, 160, 150, 190, 10);
595
596      ctx.moveTo(36, 148);
597      ctx.quadraticCurveTo(66, 188, 120, 136);
598      ctx.lineTo(36, 148);
599
600      ctx.rect(5, 170, 20, 25);
601
602      ctx.moveTo(150, 180);
603      ctx.arcTo(150, 100, 50, 200, 20);
604      ctx.lineTo(160, 160);
605
606      ctx.moveTo(20, 120);
607      ctx.arc(20, 120, 18, 0, 1.75 * Math.PI);
608      ctx.lineTo(20, 120);
609
610      ctx.moveTo(150, 5);
611      ctx.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI);
612
613      ctx.lineWidth = 4/3;
614      ctx.stroke();
615
616      // make a clock
617      ctx.stroke(canvas._path);
618
619      // Test edgecases and draw direction
620      ctx.beginPath();
621      ctx.arc(50, 100, 10, Math.PI, -Math.PI/2);
622      ctx.stroke();
623      ctx.beginPath();
624      ctx.arc(75, 100, 10, Math.PI, -Math.PI/2, true);
625      ctx.stroke();
626      ctx.beginPath();
627      ctx.arc(100, 100, 10, Math.PI, 100.1 * Math.PI, true);
628      ctx.stroke();
629      ctx.beginPath();
630      ctx.arc(125, 100, 10, Math.PI, 100.1 * Math.PI, false);
631      ctx.stroke();
632      ctx.beginPath();
633      ctx.ellipse(155, 100, 10, 15, Math.PI/8, 100.1 * Math.PI, Math.PI, true);
634      ctx.stroke();
635      ctx.beginPath();
636      ctx.ellipse(180, 100, 10, 15, Math.PI/8, Math.PI, 100.1 * Math.PI, true);
637      ctx.stroke();
638    }
639    document.getElementById('api2').src = skcanvas.toDataURL();
640    skcanvas.dispose();
641  }
642
643  function CanvasAPI3(CanvasKit) {
644    let skcanvas = CanvasKit.MakeCanvas(300, 300);
645    let realCanvas = document.getElementById('api3_c');
646    realCanvas.width = 300;
647    realCanvas.height = 300;
648
649    for (let canvas of [skcanvas, realCanvas]) {
650      let ctx = canvas.getContext('2d');
651      ctx.rect(10, 10, 20, 20);
652
653      ctx.scale(2.0, 4.0);
654      ctx.rect(30, 10, 20, 20);
655      ctx.resetTransform();
656
657      ctx.rotate(Math.PI / 3);
658      ctx.rect(50, 10, 20, 20);
659      ctx.resetTransform();
660
661      ctx.translate(30, -2);
662      ctx.rect(70, 10, 20, 20);
663      ctx.resetTransform();
664
665      ctx.translate(60, 0);
666      ctx.rotate(Math.PI / 6);
667      ctx.transform(1.5, 0, 0, 0.5, 0, 0); // effectively scale
668      ctx.rect(90, 10, 20, 20);
669      ctx.resetTransform();
670
671      ctx.save();
672      ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
673      ctx.rect(110, 10, 20, 20);
674      ctx.lineTo(110, 0);
675      ctx.restore();
676      ctx.lineTo(220, 120);
677
678      ctx.scale(3.0, 3.0);
679      ctx.font = '6pt Noto Mono';
680      ctx.fillText('This text should be huge', 10, 80);
681      ctx.resetTransform();
682
683      ctx.strokeStyle = 'black';
684      ctx.lineWidth = 2;
685      ctx.stroke();
686
687      ctx.beginPath();
688      ctx.moveTo(250, 30);
689      ctx.lineTo(250, 80);
690      ctx.scale(3.0, 3.0);
691      ctx.lineTo(280/3, 90/3);
692      ctx.closePath();
693      ctx.strokeStyle = 'black';
694      ctx.lineWidth = 5;
695      ctx.stroke();
696
697    }
698    document.getElementById('api3').src = skcanvas.toDataURL();
699    skcanvas.dispose();
700  }
701
702  function CanvasAPI4(CanvasKit) {
703    let skcanvas = CanvasKit.MakeCanvas(300, 300);
704    let realCanvas = document.getElementById('api4_c');
705    realCanvas.width = 300;
706    realCanvas.height = 300;
707
708    for (let canvas of [skcanvas, realCanvas]) {
709      let ctx = canvas.getContext('2d');
710
711      ctx.strokeStyle = '#000';
712      ctx.fillStyle = '#CCC';
713      ctx.shadowColor = 'rebeccapurple';
714      ctx.shadowBlur = 1;
715      ctx.shadowOffsetX = 3;
716      ctx.shadowOffsetY = -8;
717      ctx.rect(10, 10, 30, 30);
718
719      ctx.save();
720      ctx.strokeStyle = '#C00';
721      ctx.fillStyle = '#00C';
722      ctx.shadowBlur = 0;
723      ctx.shadowColor = 'transparent';
724
725      ctx.stroke();
726
727      ctx.restore();
728      ctx.fill();
729
730      ctx.beginPath();
731      ctx.moveTo(36, 148);
732      ctx.quadraticCurveTo(66, 188, 120, 136);
733      ctx.closePath();
734      ctx.stroke();
735
736      ctx.beginPath();
737      ctx.shadowColor = '#993366AA';
738      ctx.shadowOffsetX = 8;
739      ctx.shadowBlur = 5;
740      ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
741      ctx.rect(110, 10, 20, 20);
742      ctx.lineTo(110, 0);
743      ctx.resetTransform();
744      ctx.lineTo(220, 120);
745      ctx.stroke();
746
747      ctx.fillStyle = 'green';
748      ctx.font = '16pt Noto Mono';
749      ctx.fillText('This should be shadowed', 20, 80);
750
751      ctx.beginPath();
752      ctx.lineWidth = 6;
753      ctx.ellipse(10, 290, 30, 30, 0, 0, Math.PI * 2);
754      ctx.scale(2, 1);
755      ctx.moveTo(10, 290);
756      ctx.ellipse(10, 290, 30, 60, 0, 0, Math.PI * 2);
757      ctx.resetTransform();
758      ctx.scale(3, 1);
759      ctx.moveTo(10, 290);
760      ctx.ellipse(10, 290, 30, 90, 0, 0, Math.PI * 2);
761      ctx.stroke();
762    }
763    document.getElementById('api4').src = skcanvas.toDataURL();
764    skcanvas.dispose();
765  }
766
767  function CanvasAPI5(CanvasKit) {
768    let skcanvas = CanvasKit.MakeCanvas(600, 600);
769    let realCanvas = document.getElementById('api5_c');
770    realCanvas.width = 600;
771    realCanvas.height = 600;
772
773    for (let canvas of [skcanvas, realCanvas]) {
774      let ctx = canvas.getContext('2d');
775      ctx.scale(1.1, 1.1);
776      ctx.translate(10, 10);
777      // Shouldn't impact the fillRect calls
778      ctx.setLineDash([5, 3]);
779
780      ctx.fillStyle = 'rgba(200, 0, 100, 0.81)';
781      ctx.fillRect(20, 30, 100, 100);
782
783      ctx.globalAlpha = 0.81;
784      ctx.fillStyle = 'rgba(200, 0, 100, 1.0)';
785      ctx.fillRect(120, 30, 100, 100);
786      // This shouldn't do anything
787      ctx.globalAlpha = 0.1;
788
789      ctx.fillStyle = 'rgba(200, 0, 100, 0.9)';
790      ctx.globalAlpha = 0.9;
791      // Intentional no-op to check ordering
792      ctx.clearRect(220, 30, 100, 100);
793      ctx.fillRect(220, 30, 100, 100);
794
795      ctx.fillRect(320, 30, 100, 100);
796      ctx.clearRect(330, 40, 80, 80);
797
798      ctx.strokeStyle = 'blue';
799      ctx.lineWidth = 3;
800      ctx.setLineDash([5, 3]);
801      ctx.strokeRect(20, 150, 100, 100);
802      ctx.setLineDash([50, 30]);
803      ctx.strokeRect(125, 150, 100, 100);
804      ctx.lineDashOffset = 25;
805      ctx.strokeRect(230, 150, 100, 100);
806      ctx.setLineDash([2, 5, 9]);
807      ctx.strokeRect(335, 150, 100, 100);
808
809      ctx.setLineDash([5, 2]);
810      ctx.moveTo(336, 400);
811      ctx.quadraticCurveTo(366, 488, 120, 450);
812      ctx.lineTo(300, 400);
813      ctx.stroke();
814
815      ctx.font = '36pt Noto Mono';
816      ctx.strokeText('Dashed', 20, 350);
817      ctx.fillText('Not Dashed', 20, 400);
818
819    }
820    document.getElementById('api5').src = skcanvas.toDataURL();
821    skcanvas.dispose();
822  }
823
824  function CanvasAPI6(CanvasKit) {
825    let skcanvas = CanvasKit.MakeCanvas(600, 600);
826    let realCanvas = document.getElementById('api6_c');
827    realCanvas.width = 600;
828    realCanvas.height = 600;
829
830    for (let canvas of [skcanvas, realCanvas]) {
831      let ctx = canvas.getContext('2d');
832
833      let rgradient = ctx.createRadialGradient(200, 300, 10, 100, 100, 300);
834
835      // Add three color stops
836      rgradient.addColorStop(0, 'red');
837      rgradient.addColorStop(0.7, 'white');
838      rgradient.addColorStop(1, 'blue');
839
840      ctx.fillStyle = rgradient;
841      ctx.globalAlpha = 0.7;
842      ctx.fillRect(0, 0, 600, 600);
843      ctx.globalAlpha = 0.95;
844
845      ctx.beginPath();
846      ctx.arc(300, 100, 90, 0, Math.PI*1.66);
847      ctx.closePath();
848      ctx.strokeStyle = 'yellow';
849      ctx.lineWidth = 5;
850      ctx.stroke();
851      ctx.save();
852      ctx.clip();
853
854      let lgradient = ctx.createLinearGradient(200, 20, 420, 40);
855
856      // Add three color stops
857      lgradient.addColorStop(0, 'green');
858      lgradient.addColorStop(0.5, 'cyan');
859      lgradient.addColorStop(1, 'orange');
860
861      ctx.fillStyle = lgradient;
862
863      ctx.fillRect(200, 30, 200, 300);
864
865      ctx.restore();
866      ctx.fillRect(550, 550, 40, 40);
867
868    }
869    document.getElementById('api6').src = skcanvas.toDataURL();
870    skcanvas.dispose();
871  }
872
873  function CanvasAPI7(CanvasKit) {
874    let skcanvas = CanvasKit.MakeCanvas(300, 300);
875    let realCanvas = document.getElementById('api7_c');
876
877    let skPromise   = fetch(cdn + 'test.png')
878                        // if clients want to use a Blob, they are responsible
879                        // for reading it themselves.
880                        .then((response) => response.arrayBuffer())
881                        .then((buffer) => {
882                          skcanvas._img = skcanvas.decodeImage(buffer);
883                        });
884    let realPromise = fetch(cdn + 'test.png')
885                        .then((response) => response.blob())
886                        .then((blob) => createImageBitmap(blob))
887                        .then((bitmap) => {
888                          realCanvas._img = bitmap;
889                        });
890
891
892    Promise.all([realPromise, skPromise]).then(() => {
893      for (let canvas of [skcanvas, realCanvas]) {
894        let ctx = canvas.getContext('2d');
895        ctx.fillStyle = '#EEE';
896        ctx.fillRect(0, 0, 300, 300);
897        ctx.lineWidth = 20;
898        ctx.scale(0.1, 0.2);
899
900        let pattern = ctx.createPattern(canvas._img, 'repeat');
901        ctx.fillStyle = pattern;
902        ctx.fillRect(0, 0, 1500, 750);
903
904        pattern = ctx.createPattern(canvas._img, 'repeat-x');
905        ctx.fillStyle = pattern;
906        ctx.fillRect(1500, 0, 3000, 750);
907
908        ctx.globalAlpha = 0.7;
909        pattern = ctx.createPattern(canvas._img, 'repeat-y');
910        ctx.fillStyle = pattern;
911        ctx.fillRect(0, 750, 1500, 1500);
912        ctx.strokeRect(0, 750, 1500, 1500);
913
914        pattern = ctx.createPattern(canvas._img, 'no-repeat');
915        ctx.fillStyle = pattern;
916        pattern.setTransform({a: 1, b: -.1, c:.1, d: 0.5, e: 1800, f:800});
917        ctx.fillRect(0, 0, 3000, 1500);
918      }
919
920      document.getElementById('api7').src = skcanvas.toDataURL();
921      skcanvas.dispose();
922    });
923  }
924
925  function CanvasAPI8(CanvasKit) {
926    let skcanvas = CanvasKit.MakeCanvas(300, 300);
927    let realCanvas = document.getElementById('api8_c');
928
929    function drawPoint(ctx, x, y, color) {
930      ctx.fillStyle = color;
931      ctx.fillRect(x, y, 1, 1);
932    }
933    const IN = 'purple';
934    const OUT = 'orange';
935    const SCALE = 4;
936
937    const pts = [[3, 3], [4, 4], [5, 5], [10, 10], [8, 10], [6, 10],
938                 [6.5, 9], [15, 10], [17, 10], [17, 11], [24, 24],
939                 [25, 25], [26, 26], [27, 27]];
940
941    const tests = [
942      {
943        xOffset: 0,
944        yOffset: 0,
945        fillType: 'nonzero',
946        strokeWidth: 0,
947        testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'nonzero'),
948      },
949      {
950        xOffset: 30,
951        yOffset: 0,
952        fillType: 'evenodd',
953        strokeWidth: 0,
954        testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'evenodd'),
955      },
956      {
957        xOffset: 0,
958        yOffset: 30,
959        fillType: null,
960        strokeWidth: 1,
961        testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
962      },
963      {
964        xOffset: 30,
965        yOffset: 30,
966        fillType: null,
967        strokeWidth: 2,
968        testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
969      },
970    ];
971
972    for (let canvas of [skcanvas, realCanvas]) {
973      let ctx = canvas.getContext('2d');
974      ctx.font = '11px Noto Mono';
975      // Draw some visual aids
976      ctx.fillText('path-nonzero', 30, 15);
977      ctx.fillText('path-evenodd', 150, 15);
978      ctx.fillText('stroke-1px-wide', 30, 130);
979      ctx.fillText('stroke-2px-wide', 150, 130);
980      ctx.fillText('purple is IN, orange is OUT', 10, 280);
981
982      // Scale up to make single pixels easier to see
983      ctx.scale(SCALE, SCALE);
984      for (let test of tests) {
985        ctx.beginPath();
986        let xOffset = test.xOffset;
987        let yOffset = test.yOffset;
988
989        ctx.fillStyle = '#AAA';
990        ctx.lineWidth = test.strokeWidth;
991        ctx.rect(5+xOffset, 5+yOffset, 20, 20);
992        ctx.arc(15+xOffset, 15+yOffset, 8, 0, Math.PI*2, false);
993        if (test.fillType) {
994          ctx.fill(test.fillType);
995        } else {
996          ctx.stroke();
997        }
998
999        for (let pt of pts) {
1000          let [x, y] = pt;
1001          x += xOffset;
1002          y += yOffset;
1003          // naively apply transform when querying because the points queried
1004          // ignore the CTM.
1005          if (test.testFn(ctx, x, y)) {
1006            drawPoint(ctx, x, y, IN);
1007          } else {
1008            drawPoint(ctx, x, y, OUT);
1009          }
1010        }
1011      }
1012    }
1013
1014    document.getElementById('api8').src = skcanvas.toDataURL();
1015    skcanvas.dispose();
1016  }
1017
1018  function VertexAPI1(CanvasKit, gpu) {
1019    const surface = MakeCanvasSurface(CanvasKit, gpu, 'vertex1');
1020    if (!surface) {
1021      console.error('Could not make surface');
1022      return;
1023    }
1024    const canvas = surface.getCanvas();
1025    let paint = new CanvasKit.Paint();
1026
1027    // See https://fiddle.skia.org/c/f48b22eaad1bb7adcc3faaa321754af6
1028    // for original c++ version.
1029    let points = [0, 0,  250, 0,  100, 100,  0, 250];
1030    let colors = [CanvasKit.RED, CanvasKit.BLUE,
1031                  CanvasKit.YELLOW, CanvasKit.CYAN];
1032    let vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TriangleFan,
1033                                            points, null, colors,
1034                                            false /*isVolatile*/);
1035
1036    canvas.drawVertices(vertices, CanvasKit.BlendMode.Dst, paint);
1037
1038    vertices.delete();
1039
1040    // See https://fiddle.skia.org/c/e8bdae9bea3227758989028424fcac3d
1041    // for original c++ version.
1042    points   = [300, 300,  50, 300,  200, 200,  300, 50 ];
1043    let texs = [  0,   0,   0, 250,  250, 250,  250,  0 ];
1044    vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TriangleFan,
1045                                            points, texs, colors);
1046
1047    let shader = CanvasKit.Shader.MakeLinearGradient([0, 0], [250, 0],
1048            colors, null, CanvasKit.TileMode.Clamp);
1049    paint.setShader(shader);
1050
1051    canvas.drawVertices(vertices, CanvasKit.BlendMode.Darken, paint);
1052    surface.flush();
1053
1054    shader.delete();
1055    paint.delete();
1056    surface.delete();
1057    vertices.delete();
1058  }
1059
1060  function GradientAPI1(CanvasKit, gpu) {
1061    const surface = MakeCanvasSurface(CanvasKit, gpu, 'gradient1');
1062    if (!surface) {
1063      console.error('Could not make surface');
1064      return;
1065    }
1066    const canvas = surface.getCanvas();
1067    let paint = new CanvasKit.Paint();
1068
1069    // See https://fiddle.skia.org/c/f48b22eaad1bb7adcc3faaa321754af6
1070    // for original c++ version.
1071    let colors = [CanvasKit.BLUE, CanvasKit.YELLOW, CanvasKit.RED];
1072    let pos =    [0, .7, 1.0];
1073    let transform = [2, 0, 0,
1074                     0, 2, 0,
1075                     0, 0, 1];
1076    let shader = CanvasKit.Shader.MakeRadialGradient([150, 150], 130, colors,
1077                              pos, CanvasKit.TileMode.Mirror, transform);
1078
1079    paint.setShader(shader);
1080    const textFont = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 75);
1081    const textBlob = CanvasKit.TextBlob.MakeFromText('Radial', textFont);
1082
1083    canvas.drawTextBlob(textBlob, 10, 200, paint);
1084    surface.flush();
1085    paint.delete();
1086    textFont.delete();
1087    textBlob.delete();
1088    shader.delete();
1089    surface.delete();
1090  }
1091
1092  function TextOnPathAPI1(CanvasKit, gpu) {
1093    const surface = MakeCanvasSurface(CanvasKit, gpu, 'textonpath');
1094    if (!surface) {
1095      console.error('Could not make surface');
1096      return;
1097    }
1098    const canvas = surface.getCanvas();
1099    const paint = new CanvasKit.Paint();
1100    paint.setStyle(CanvasKit.PaintStyle.Stroke);
1101    paint.setAntiAlias(true);
1102
1103    const font = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 24);
1104    const fontPaint = new CanvasKit.Paint();
1105    fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
1106    fontPaint.setAntiAlias(true);
1107
1108    const arc = new CanvasKit.Path();
1109    arc.arcToOval(CanvasKit.LTRBRect(20, 40, 280, 300), -160, 140, true);
1110    arc.lineTo(210, 140);
1111    arc.arcToOval(CanvasKit.LTRBRect(20, 0, 280, 260), 160, -140, true);
1112
1113    const str = 'This téxt should follow the curve across contours...';
1114    const textBlob = CanvasKit.TextBlob.MakeOnPath(str, arc, font);
1115
1116    canvas.drawPath(arc, paint);
1117    canvas.drawTextBlob(textBlob, 0, 0, fontPaint);
1118
1119    surface.flush();
1120
1121    surface.delete();
1122    textBlob.delete();
1123    arc.delete();
1124    paint.delete();
1125    font.delete();
1126    fontPaint.delete();
1127  }
1128
1129  function DrawGlyphsAPI1(CanvasKit, gpu) {
1130    const surface = MakeCanvasSurface(CanvasKit, gpu, 'drawGlyphs');
1131    if (!surface) {
1132      console.error('Could not make surface');
1133      return;
1134    }
1135    const canvas = surface.getCanvas();
1136    const paint = new CanvasKit.Paint();
1137    const font = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 16);
1138    paint.setAntiAlias(true);
1139
1140    let glyphs = [];
1141    let positions = [];
1142    for (let i = 0; i < 256; ++i) {
1143      glyphs.push(i);
1144      positions.push((i % 16) * 16);
1145      positions.push(Math.round(i/16) * 16);
1146    }
1147    canvas.drawGlyphs(glyphs, positions, 16, 20, font, paint);
1148
1149    surface.flush();
1150
1151    surface.delete();
1152    paint.delete();
1153    font.delete();
1154  }
1155
1156  function SurfaceAPI1(CanvasKit, gpu) {
1157    const surface = MakeCanvasSurface(CanvasKit, gpu, 'surfaces');
1158    if (!surface) {
1159      console.error('Could not make surface');
1160      return;
1161    }
1162
1163    // create a subsurface as a temporary workspace.
1164    const subSurface = surface.makeSurface({
1165      width: 50,
1166      height: 50,
1167      alphaType: CanvasKit.AlphaType.Premul,
1168      colorType: CanvasKit.ColorType.RGBA_8888,
1169      colorSpace: CanvasKit.ColorSpace.SRGB,
1170    });
1171
1172    if (!subSurface) {
1173      console.error('Could not make subsurface');
1174      return;
1175    }
1176
1177    // draw a small "scene"
1178    const paint = new CanvasKit.Paint();
1179    paint.setColor(CanvasKit.Color(139, 228, 135, 0.95)); // greenish
1180    paint.setStyle(CanvasKit.PaintStyle.Fill);
1181    paint.setAntiAlias(true);
1182
1183    const subCanvas = subSurface.getCanvas();
1184    subCanvas.clear(CanvasKit.BLACK);
1185    subCanvas.drawRect(CanvasKit.LTRBRect(5, 15, 45, 40), paint);
1186
1187    paint.setColor(CanvasKit.Color(214, 93, 244)); // purplish
1188    for (let i = 0; i < 10; i++) {
1189      const x = Math.random() * 50;
1190      const y = Math.random() * 50;
1191
1192      subCanvas.drawOval(CanvasKit.XYWHRect(x, y, 6, 6), paint);
1193    }
1194
1195    // Snap it off as an Image - this image will be in the form the
1196    // parent surface prefers (e.g. Texture for GPU / Raster for CPU).
1197    const img = subSurface.makeImageSnapshot();
1198
1199    // clean up the temporary surface (which also cleans up subCanvas)
1200    subSurface.delete();
1201    paint.delete();
1202
1203    // Make it repeat a bunch with a shader
1204    const pattern = img.makeShaderCubic(CanvasKit.TileMode.Repeat, CanvasKit.TileMode.Mirror,
1205                                        1/3, 1/3);
1206    const patternPaint = new CanvasKit.Paint();
1207    patternPaint.setShader(pattern);
1208
1209    let i = 0;
1210    function drawFrame(canvas) {
1211      i++;
1212      canvas.clear(CanvasKit.WHITE);
1213      canvas.drawOval(CanvasKit.LTRBRect(i % 60, i % 60, 300 - (i% 60), 300 - (i % 60)), patternPaint);
1214      surface.requestAnimationFrame(drawFrame);
1215    }
1216    surface.requestAnimationFrame(drawFrame);
1217  }
1218
1219  function AtlasAPI1(CanvasKit, gpu, imgData) {
1220    if (!CanvasKit || !imgData) {
1221      return;
1222    }
1223
1224    const surface = MakeCanvasSurface(CanvasKit, gpu, 'atlas');
1225    if (!surface) {
1226      console.error('Could not make surface');
1227      return;
1228    }
1229
1230    const img = CanvasKit.MakeImageFromEncoded(imgData);
1231    const paint = new CanvasKit.Paint();
1232    paint.setColor(CanvasKit.Color(0, 0, 0, 0.8));
1233
1234    // Allocate space for 2 rectangles.
1235    const srcs = CanvasKit.Malloc(Float32Array, 8);
1236    srcs.toTypedArray().set([
1237      0, 0, 250, 250, // LTRB
1238      250, 0, 500, 250
1239    ]);
1240
1241    // Allocate space for 2 RSXForms
1242    const dsts = CanvasKit.Malloc(Float32Array, 8);
1243    dsts.toTypedArray().set([
1244      .5, 0, 0, 0,  // scos, ssin, tx, ty
1245      0, .8, 200, 100
1246    ]);
1247
1248   // Allocate space for 4 colors.
1249    const colors = new CanvasKit.Malloc(Uint32Array, 2);
1250    colors.toTypedArray().set([
1251      CanvasKit.ColorAsInt( 85, 170,  10, 128), // light green
1252      CanvasKit.ColorAsInt( 51,  51, 191, 128), // light blue
1253    ]);
1254
1255    let i = 0;
1256
1257    function drawFrame(canvas) {
1258      canvas.clear(CanvasKit.WHITE);
1259      i++;
1260      let scale = 0.5 + Math.sin(i/40)/4;
1261
1262      // update the coordinates of existing sprites - note that this
1263      // does not require a full re-copy of the full array; they are
1264      // updated in-place.
1265      dsts.toTypedArray().set([0.5, 0, (2*i)%200, (5*Math.round(i/200)) % 200], 0);
1266      dsts.toTypedArray().set([scale*Math.sin(i/20), scale*Math.cos(i/20), 200, 100], 4);
1267
1268      canvas.drawAtlas(img, srcs, dsts, paint, CanvasKit.BlendMode.Plus, colors,
1269                       {filter: CanvasKit.FilterMode.Nearest});
1270      surface.requestAnimationFrame(drawFrame);
1271    }
1272    surface.requestAnimationFrame(drawFrame);
1273  }
1274
1275  async function DecodeAPI(CanvasKit, imgData) {
1276    if (!CanvasKit || !imgData) {
1277      return;
1278    }
1279    const surface = CanvasKit.MakeCanvasSurface('decode');
1280    if (!surface) {
1281      console.error('Could not make surface');
1282      return;
1283    }
1284    const blob = new Blob([ imgData ]);
1285    // ImageBitmap is not supported in Safari
1286    const imageBitmap = await createImageBitmap(blob);
1287    const img = await CanvasKit.MakeImageFromCanvasImageSource(imageBitmap);
1288
1289    surface.drawOnce((canvas) => {
1290      canvas.drawImage(img, 0, 0, null);
1291    });
1292  }
1293</script>
1294