blob: a1945806171084f86bc4bdef453ba8397f5f2479 [file] [log] [blame]
Kaido Kertb1089432024-03-18 19:46:49 -07001<!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>