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