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