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