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