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