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