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