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