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