1<!DOCTYPE html> 2<title>CanvasKit Extra features (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 { 9 border: 1px dashed #AAA; 10 } 11 #sk_legos,#sk_drinks,#sk_party,#sk_onboarding, #sk_animated_gif { 12 width: 300px; 13 height: 300px; 14 } 15 16</style> 17 18<h2> Skottie </h2> 19<canvas id=sk_legos width=300 height=300></canvas> 20<canvas id=sk_drinks width=500 height=500></canvas> 21<canvas id=sk_party width=500 height=500></canvas> 22<canvas id=sk_onboarding width=500 height=500></canvas> 23<canvas id=sk_animated_gif width=500 height=500 24 title='This is an animated gif being animated in Skottie'></canvas> 25 26<h2> RT Shader </h2> 27<canvas id=rtshader width=300 height=300></canvas> 28<canvas id=rtshader2 width=300 height=300></canvas> 29 30<h2> Paragraph </h2> 31<canvas id=para1 width=600 height=600></canvas> 32<canvas id=para2 width=600 height=600 tabindex='-1'></canvas> 33<canvas id=para3 width=600 height=600 tabindex='-1'></canvas> 34<canvas id=para4 width=600 height=600 tabindex='-1'></canvas> 35 36`` 37<h2> CanvasKit can serialize/deserialize .skp files</h2> 38<canvas id=skp width=500 height=500></canvas> 39 40<h2> 3D perspective transformations </h2> 41<canvas id=glyphgame width=500 height=500></canvas> 42 43<h2> Support for extended color spaces </h2> 44<a href="chrome://flags/#force-color-profile">Force P3 profile</a> 45<canvas id=colorsupport width=300 height=300></canvas> 46 47<script type="text/javascript" src="/build/canvaskit.js"></script> 48 49<script type="text/javascript" src="textapi_utils.js"></script> 50 51<script type="text/javascript" charset="utf-8"> 52 53 var CanvasKit = null; 54 var cdn = 'https://storage.googleapis.com/skia-cdn/misc/'; 55 56 const ckLoaded = CanvasKitInit({locateFile: (file) => '/build/'+file}); 57 58 const loadLegoJSON = fetch(cdn + 'lego_loader.json').then((response) => response.text()); 59 const loadDrinksJSON = fetch(cdn + 'drinks.json').then((response) => response.text()); 60 const loadConfettiJSON = fetch(cdn + 'confetti.json').then((response) => response.text()); 61 const loadOnboardingJSON = fetch(cdn + 'onboarding.json').then((response) => response.text()); 62 const loadMultiframeJSON = fetch(cdn + 'skottie_sample_multiframe.json').then((response) => response.text()); 63 64 const loadFlightGif = fetch(cdn + 'flightAnim.gif').then((response) => response.arrayBuffer()); 65 const loadFont = fetch(cdn + 'Roboto-Regular.ttf').then((response) => response.arrayBuffer()); 66 const loadDog = fetch(cdn + 'dog.jpg').then((response) => response.arrayBuffer()); 67 const loadMandrill = fetch(cdn + 'mandrill_256.png').then((response) => response.arrayBuffer()); 68 const loadBrickTex = fetch(cdn + 'brickwork-texture.jpg').then((response) => response.arrayBuffer()); 69 const loadBrickBump = fetch(cdn + 'brickwork_normal-map.jpg').then((response) => response.arrayBuffer()); 70 71 // Examples which only require canvaskit 72 ckLoaded.then((CK) => { 73 CanvasKit = CK; 74 RTShaderAPI1(CanvasKit); 75 ColorSupport(CanvasKit); 76 SkpExample(CanvasKit); 77 }); 78 79 // Examples requiring external resources. 80 // Set bounds to fix the 4:3 resolution of the legos 81 Promise.all([ckLoaded, loadLegoJSON]).then(([ck, jsonstr]) => { 82 SkottieExample(ck, 'sk_legos', jsonstr, [-50, 0, 350, 300]); 83 }); 84 // Re-size to fit 85 let fullBounds = [0, 0, 500, 500]; 86 Promise.all([ckLoaded, loadDrinksJSON]).then(([ck, jsonstr]) => { 87 SkottieExample(ck, 'sk_drinks', jsonstr, fullBounds); 88 }); 89 Promise.all([ckLoaded, loadConfettiJSON]).then(([ck, jsonstr]) => { 90 SkottieExample(ck, 'sk_party', jsonstr, fullBounds); 91 }); 92 Promise.all([ckLoaded, loadOnboardingJSON]).then(([ck, jsonstr]) => { 93 SkottieExample(ck, 'sk_onboarding', jsonstr, fullBounds); 94 }); 95 Promise.all([ckLoaded, loadMultiframeJSON, loadFlightGif]).then(([ck, jsonstr, gif]) => { 96 SkottieExample(ck, 'sk_animated_gif', jsonstr, fullBounds, {'image_0.png': gif}); 97 }); 98 99 Promise.all([ckLoaded, loadFont]).then((results) => { 100 ParagraphExtended(...results); 101 ParagraphAPI1(...results); 102 ParagraphAPI2(...results); 103 ParagraphAPI3(...results); 104 GlyphGame(...results) 105 }); 106 107 const rectLeft = 0; 108 const rectTop = 1; 109 const rectRight = 2; 110 const rectBottom = 3; 111 112 function SkottieExample(CanvasKit, id, jsonStr, bounds, assets) { 113 if (!CanvasKit || !jsonStr) { 114 return; 115 } 116 const animation = CanvasKit.MakeManagedAnimation(jsonStr, assets); 117 const duration = animation.duration() * 1000; 118 const size = animation.size(); 119 let c = document.getElementById(id); 120 bounds = bounds || CanvasKit.LTRBRect(0, 0, size.w, size.h); 121 122 // Basic managed animation test. 123 if (id === 'sk_drinks') { 124 animation.setColor('BACKGROUND_FILL', CanvasKit.Color(0, 163, 199, 1.0)); 125 } 126 127 const surface = CanvasKit.MakeCanvasSurface(id); 128 if (!surface) { 129 console.error('Could not make surface'); 130 return; 131 } 132 133 let firstFrame = Date.now(); 134 135 function drawFrame(canvas) { 136 let seek = ((Date.now() - firstFrame) / duration) % 1.0; 137 let damage = animation.seek(seek); 138 139 if (damage[rectRight] > damage[rectLeft] && damage[rectBottom] > damage[rectTop]) { 140 canvas.clear(CanvasKit.WHITE); 141 animation.render(canvas, bounds); 142 } 143 surface.requestAnimationFrame(drawFrame); 144 } 145 surface.requestAnimationFrame(drawFrame); 146 147 return surface; 148 } 149 150 function ParagraphAPI1(CanvasKit, fontData) { 151 if (!CanvasKit || !fontData) { 152 return; 153 } 154 155 const surface = CanvasKit.MakeCanvasSurface('para1'); 156 if (!surface) { 157 console.error('Could not make surface'); 158 return; 159 } 160 161 const canvas = surface.getCanvas(); 162 const fontMgr = CanvasKit.FontMgr.FromData([fontData]); 163 164 const paraStyle = new CanvasKit.ParagraphStyle({ 165 textStyle: { 166 color: CanvasKit.BLACK, 167 fontFamilies: ['Roboto'], 168 fontSize: 50, 169 }, 170 textAlign: CanvasKit.TextAlign.Left, 171 maxLines: 5, 172 }); 173 174 const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr); 175 builder.addText('The quick brown fox ate a hamburgerfons and got sick.'); 176 const paragraph = builder.build(); 177 178 let wrapTo = 0; 179 180 let X = 100; 181 let Y = 100; 182 183 const fontPaint = new CanvasKit.Paint(); 184 fontPaint.setStyle(CanvasKit.PaintStyle.Fill); 185 fontPaint.setAntiAlias(true); 186 187 function drawFrame(canvas) { 188 canvas.clear(CanvasKit.WHITE); 189 wrapTo = 350 + 150 * Math.sin(Date.now() / 2000); 190 paragraph.layout(wrapTo); 191 canvas.drawParagraph(paragraph, 0, 0); 192 193 canvas.drawLine(wrapTo, 0, wrapTo, 400, fontPaint); 194 195 surface.requestAnimationFrame(drawFrame); 196 } 197 surface.requestAnimationFrame(drawFrame); 198 199 let interact = (e) => { 200 X = e.offsetX*2; // multiply by 2 because the canvas is 300 css pixels wide, 201 Y = e.offsetY*2; // but the canvas itself is 600px wide 202 }; 203 204 document.getElementById('para1').addEventListener('pointermove', interact); 205 return surface; 206 } 207 208 function ParagraphAPI2(CanvasKit, fontData) { 209 if (!CanvasKit || !fontData) { 210 return; 211 } 212 213 const surface = CanvasKit.MakeCanvasSurface('para2'); 214 if (!surface) { 215 console.error('Could not make surface'); 216 return; 217 } 218 219 const mouse = MakeMouse(); 220 const cursor = MakeCursor(CanvasKit); 221 const canvas = surface.getCanvas(); 222 223 const text0 = "In a hole in the ground there lived a hobbit. Not a nasty, dirty, " + 224 "wet hole full of worms and oozy smells. This was a hobbit-hole and " + 225 "that means good food, a warm hearth, and all the comforts of home."; 226 const LOC_X = 20, 227 LOC_Y = 20; 228 229 const bgPaint = new CanvasKit.Paint(); 230 bgPaint.setColor([0.965, 0.965, 0.965, 1]); 231 232 const editor = MakeEditor(text0, {typeface:null, size:24}, cursor, 400); 233 234 editor.applyStyleToRange({size:100}, 0, 1); 235 editor.applyStyleToRange({italic:true}, 38, 38+6); 236 editor.applyStyleToRange({color:[1,0,0,1]}, 5, 5+4); 237 238 editor.setXY(LOC_X, LOC_Y); 239 240 function drawFrame(canvas) { 241 const lines = editor.getLines(); 242 243 canvas.clear(CanvasKit.WHITE); 244 245 if (mouse.isActive()) { 246 const pos = mouse.getPos(-LOC_X, -LOC_Y); 247 const a = lines_pos_to_index(lines, pos[0], pos[1]); 248 const b = lines_pos_to_index(lines, pos[2], pos[3]); 249 if (a == b) { 250 editor.setIndex(a); 251 } else { 252 editor.setIndices(a, b); 253 } 254 } 255 256 canvas.drawRect(editor.bounds(), bgPaint); 257 editor.draw(canvas); 258 259 surface.requestAnimationFrame(drawFrame); 260 } 261 surface.requestAnimationFrame(drawFrame); 262 263 function interact(e) { 264 const type = e.type; 265 if (type === 'pointerup') { 266 mouse.setUp(e.offsetX, e.offsetY); 267 } else if (type === 'pointermove') { 268 mouse.setMove(e.offsetX, e.offsetY); 269 } else if (type === 'pointerdown') { 270 mouse.setDown(e.offsetX, e.offsetY); 271 } 272 }; 273 274 function keyhandler(e) { 275 switch (e.key) { 276 case 'ArrowLeft': editor.moveDX(-1); return; 277 case 'ArrowRight': editor.moveDX(1); return; 278 case 'ArrowUp': 279 e.preventDefault(); 280 editor.moveDY(-1); 281 return; 282 case 'ArrowDown': 283 e.preventDefault(); 284 editor.moveDY(1); 285 return; 286 case 'Backspace': 287 editor.deleteSelection(); 288 return; 289 case 'Shift': 290 return; 291 } 292 if (e.ctrlKey) { 293 switch (e.key) { 294 case 'r': editor.applyStyleToSelection({color:[1,0,0,1]}); return; 295 case 'g': editor.applyStyleToSelection({color:[0,0.6,0,1]}); return; 296 case 'u': editor.applyStyleToSelection({color:[0,0,1,1]}); return; 297 case 'k': editor.applyStyleToSelection({color:[0,0,0,1]}); return; 298 299 case 'i': editor.applyStyleToSelection({italic:'toggle'}); return; 300 case 'b': editor.applyStyleToSelection({bold:'toggle'}); return; 301 case 'l': editor.applyStyleToSelection({underline:'toggle'}); return; 302 303 case ']': editor.applyStyleToSelection({size_add:1}); return; 304 case '[': editor.applyStyleToSelection({size_add:-1}); return; 305 } 306 } 307 if (!e.ctrlKey && !e.metaKey) { 308 e.preventDefault(); // at least needed for 'space' 309 editor.insert(e.key); 310 } 311 } 312 313 document.getElementById('para2').addEventListener('pointermove', interact); 314 document.getElementById('para2').addEventListener('pointerdown', interact); 315 document.getElementById('para2').addEventListener('pointerup', interact); 316 document.getElementById('para2').addEventListener('keydown', keyhandler); 317 return surface; 318 } 319 320 function ParagraphAPI3(CanvasKit, fontData) { 321 if (!CanvasKit || !fontData) { 322 return; 323 } 324 325 const surface = CanvasKit.MakeCanvasSurface('para3'); 326 if (!surface) { 327 console.error('Could not make surface'); 328 return; 329 } 330 331 const fontMgr = CanvasKit.FontMgr.FromData([fontData]); 332 333 const paraStyle = new CanvasKit.ParagraphStyle({ 334 textStyle: { 335 color: CanvasKit.BLACK, 336 fontFamilies: ['Roboto'], 337 fontSize: 50, 338 }, 339 textAlign: CanvasKit.TextAlign.Left, 340 maxLines: 5, 341 }); 342 343 const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr); 344 builder.addText('The quick brown fox ate a hamburgerfons and got sick.'); 345 346 // The code below works for English-only text assuming 347 // that each character is a glyph (cluster), spaces are breaks between words, 348 // new lines are breaks between lines and the entire text is LTR. 349 350 // In case the paragraph text was constructed in a set of calls we need the text 351 const text = builder.getText(); 352 353 // Pass the entire text as one word. It's only used for the method 354 // getWords 355 const mallocedWords = CanvasKit.Malloc(Uint32Array, 2); 356 mallocedWords.toTypedArray().set([0, text.length]); 357 358 // Pass each character as a separate grapheme 359 const mallocedGraphemes = CanvasKit.Malloc(Uint32Array, text.length + 1); 360 const graphemesArr = mallocedGraphemes.toTypedArray(); 361 for (let i = 0; i <= text.length; i++) { 362 graphemesArr[i] = i; 363 } 364 365 // Pass each space as a "soft" break and each new line as a "hard" break. 366 const SOFT = 0; 367 const HARD = 1; 368 const lineBreaks = [0, SOFT]; 369 for (let i = 0; i < text.length; ++i) { 370 if (text[i] === ' ') { 371 lineBreaks.push(i + 1, SOFT); 372 } 373 if (text[i] === '\n') { 374 lineBreaks.push(i + 1, HARD); 375 } 376 } 377 lineBreaks.push(text.length, SOFT); 378 const mallocedLineBreaks = CanvasKit.Malloc(Uint32Array, lineBreaks.length); 379 mallocedLineBreaks.toTypedArray().set(lineBreaks); 380 381 builder.setWordsUtf16(mallocedWords); 382 builder.setGraphemeBreaksUtf16(mallocedGraphemes); 383 builder.setLineBreaksUtf16(mallocedLineBreaks); 384 const paragraph = builder.build(); 385 386 paragraph.layout(600); 387 388 let wrapTo = 0; 389 390 let X = 100; 391 let Y = 100; 392 393 const fontPaint = new CanvasKit.Paint(); 394 fontPaint.setStyle(CanvasKit.PaintStyle.Fill); 395 fontPaint.setAntiAlias(true); 396 397 function drawFrame(canvas) { 398 canvas.clear(CanvasKit.WHITE); 399 wrapTo = 350 + 150 * Math.sin(Date.now() / 2000); 400 paragraph.layout(wrapTo); 401 canvas.drawParagraph(paragraph, 0, 0); 402 403 canvas.drawLine(wrapTo, 0, wrapTo, 400, fontPaint); 404 405 surface.requestAnimationFrame(drawFrame); 406 } 407 surface.requestAnimationFrame(drawFrame); 408 409 let interact = (e) => { 410 X = e.offsetX*2; // multiply by 2 because the canvas is 300 css pixels wide, 411 Y = e.offsetY*2; // but the canvas itself is 600px wide 412 }; 413 414 document.getElementById('para1').addEventListener('pointermove', interact); 415 416 return surface; 417 } 418 419 function ParagraphExtended(CanvasKit, fontData) { 420 if (!CanvasKit || !fontData) { 421 return; 422 } 423 424 const surface = CanvasKit.MakeCanvasSurface('para4'); 425 if (!surface) { 426 console.error('Could not make surface'); 427 return; 428 } 429 430 const fontMgr = CanvasKit.FontMgr.FromData([fontData]); 431 432 const paraStyle = new CanvasKit.ParagraphStyle({ 433 textStyle: { 434 color: CanvasKit.BLACK, 435 fontFamilies: ['Roboto'], 436 fontSize: 50, 437 }, 438 textAlign: CanvasKit.TextAlign.Left, 439 maxLines: 5, 440 }); 441 442 const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr); 443 builder.addText('The quick brown fox ate a hamburgerfons and got sick.'); 444 445 // The code below works for English-only text assuming 446 // that each character is a glyph (cluster), spaces are breaks between words, 447 // new lines are breaks between lines and the entire text is LTR. 448 449 // In case the paragraph text was constructed in a set of calls we need the text 450 const text = builder.getText(); 451 452 // Pass the entire text as one word. It's only used for the method getWords 453 const words = [0, text.length]; 454 455 // Pass each character as a separate grapheme 456 const graphemes = []; 457 for (let i = 0; i <= text.length; i++) { 458 graphemes.push(i); 459 } 460 461 // Pass each space as a "soft" break and each new line as a "hard" break 462 const SOFT = 0; 463 const HARD = 1; 464 const lineBreaks = [0, SOFT]; 465 for (let i = 0; i < text.length; ++i) { 466 if (text[i] === ' ') { 467 lineBreaks.push(i + 1, SOFT); 468 } 469 if (text[i] === '\n') { 470 lineBreaks.push(i + 1, HARD); 471 } 472 } 473 lineBreaks.push(text.length, SOFT); 474 475 builder.setWordsUtf16(words); 476 builder.setGraphemeBreaksUtf16(graphemes); 477 builder.setLineBreaksUtf16(lineBreaks); 478 const paragraph = builder.build(); 479 480 paragraph.layout(600); 481 482 let wrapTo = 0; 483 484 let X = 100; 485 let Y = 100; 486 487 const roboto = CanvasKit.Typeface.MakeTypefaceFromData(fontData); 488 const textFont = new CanvasKit.Font(roboto, 20); 489 const fontPaint = new CanvasKit.Paint(); 490 fontPaint.setStyle(CanvasKit.PaintStyle.Fill); 491 fontPaint.setAntiAlias(true); 492 493 function drawFrame(canvas) { 494 canvas.clear(CanvasKit.WHITE); 495 wrapTo = 350 + 150 * Math.sin(Date.now() / 2000); 496 paragraph.layout(wrapTo); 497 canvas.drawParagraph(paragraph, 0, 0); 498 canvas.drawLine(wrapTo, 0, wrapTo, 400, fontPaint); 499 500 const lines = paragraph.getShapedLines(); 501 var fonts = ''; 502 var sep = ''; 503 for (const line of lines) { 504 for (const run of line.runs) { 505 fonts += sep; 506 fonts += run.typeface.getFamilyName(); 507 sep = ', '; 508 } 509 } 510 canvas.drawText(fonts, wrapTo + 10, 200, fontPaint, textFont); 511 512 surface.requestAnimationFrame(drawFrame); 513 } 514 surface.requestAnimationFrame(drawFrame); 515 516 let interact = (e) => { 517 X = e.offsetX*2; // multiply by 2 because the canvas is 300 css pixels wide, 518 Y = e.offsetY*2; // but the canvas itself is 600px wide 519 }; 520 521 document.getElementById('para1').addEventListener('pointermove', interact); 522 523 return surface; 524 } 525 526 function RTShaderAPI1(CanvasKit) { 527 if (!CanvasKit) { 528 return; 529 } 530 531 const surface = CanvasKit.MakeCanvasSurface('rtshader'); 532 if (!surface) { 533 console.error('Could not make surface'); 534 return; 535 } 536 537 const canvas = surface.getCanvas(); 538 539 const effect = CanvasKit.RuntimeEffect.Make(spiralSkSL); 540 const shader = effect.makeShader([ 541 0.5, 542 150, 150, 543 0, 1, 0, 1, 544 1, 0, 0, 1]); 545 const paint = new CanvasKit.Paint(); 546 paint.setShader(shader); 547 canvas.drawRect(CanvasKit.LTRBRect(0, 0, 300, 300), paint); 548 549 surface.flush(); 550 shader.delete(); 551 paint.delete(); 552 effect.delete(); 553 } 554 555 // RTShader2 demo 556 Promise.all([ckLoaded, loadDog, loadMandrill]).then((values) => { 557 const [CanvasKit, dogData, mandrillData] = values; 558 const dogImg = CanvasKit.MakeImageFromEncoded(dogData); 559 if (!dogImg) { 560 console.error('could not decode dog'); 561 return; 562 } 563 const mandrillImg = CanvasKit.MakeImageFromEncoded(mandrillData); 564 if (!mandrillImg) { 565 console.error('could not decode mandrill'); 566 return; 567 } 568 const quadrantSize = 150; 569 570 const dogShader = dogImg.makeShaderCubic( 571 CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp, 572 1/3, 1/3, 573 CanvasKit.Matrix.scaled(quadrantSize/dogImg.width(), 574 quadrantSize/dogImg.height())); 575 const mandrillShader = mandrillImg.makeShaderCubic( 576 CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp, 577 1/3, 1/3, 578 CanvasKit.Matrix.scaled( 579 quadrantSize/mandrillImg.width(), 580 quadrantSize/mandrillImg.height())); 581 582 const surface = CanvasKit.MakeCanvasSurface('rtshader2'); 583 if (!surface) { 584 console.error('Could not make surface'); 585 return; 586 } 587 588 const prog = ` 589 uniform shader before_map; 590 uniform shader after_map; 591 uniform shader threshold_map; 592 593 uniform float cutoff; 594 uniform float slope; 595 596 float smooth_cutoff(float x) { 597 x = x * slope + (0.5 - slope * cutoff); 598 return clamp(x, 0, 1); 599 } 600 601 half4 main(float2 xy) { 602 half4 before = before_map.eval(xy); 603 half4 after = after_map.eval(xy); 604 605 float m = smooth_cutoff(threshold_map.eval(xy).r); 606 return mix(before, after, half(m)); 607 }`; 608 609 const canvas = surface.getCanvas(); 610 611 const thresholdEffect = CanvasKit.RuntimeEffect.Make(prog); 612 const spiralEffect = CanvasKit.RuntimeEffect.Make(spiralSkSL); 613 614 const draw = (x, y, shader) => { 615 const paint = new CanvasKit.Paint(); 616 paint.setShader(shader); 617 canvas.save(); 618 canvas.translate(x, y); 619 canvas.drawRect(CanvasKit.LTRBRect(0, 0, quadrantSize, quadrantSize), paint); 620 canvas.restore(); 621 paint.delete(); 622 }; 623 624 const offscreenSurface = CanvasKit.MakeSurface(quadrantSize, quadrantSize); 625 const getBlurrySpiralShader = (rad_scale) => { 626 const oCanvas = offscreenSurface.getCanvas(); 627 628 const spiralShader = spiralEffect.makeShader([ 629 rad_scale, 630 quadrantSize/2, quadrantSize/2, 631 1, 1, 1, 1, 632 0, 0, 0, 1]); 633 634 const blur = CanvasKit.ImageFilter.MakeBlur(0.1, 0.1, CanvasKit.TileMode.Clamp, null); 635 636 const paint = new CanvasKit.Paint(); 637 paint.setShader(spiralShader); 638 paint.setImageFilter(blur); 639 oCanvas.drawRect(CanvasKit.LTRBRect(0, 0, quadrantSize, quadrantSize), paint); 640 641 paint.delete(); 642 blur.delete(); 643 spiralShader.delete(); 644 return offscreenSurface.makeImageSnapshot() 645 .makeShaderCubic(CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp, 646 1/3, 1/3); 647 648 }; 649 650 const drawFrame = () => { 651 surface.requestAnimationFrame(drawFrame); 652 const thresholdShader = getBlurrySpiralShader(Math.sin(Date.now() / 5000) / 2); 653 654 const blendShader = thresholdEffect.makeShaderWithChildren( 655 [0.5, 10], 656 [dogShader, mandrillShader, thresholdShader]); 657 draw(0, 0, blendShader); 658 draw(quadrantSize, 0, thresholdShader); 659 draw(0, quadrantSize, dogShader); 660 draw(quadrantSize, quadrantSize, mandrillShader); 661 662 blendShader.delete(); 663 }; 664 665 surface.requestAnimationFrame(drawFrame); 666 }); 667 668 function SkpExample(CanvasKit) { 669 if (!CanvasKit) { 670 return; 671 } 672 673 const surface = CanvasKit.MakeSWCanvasSurface('skp'); 674 if (!surface) { 675 console.error('Could not make surface'); 676 return; 677 } 678 679 const paint = new CanvasKit.Paint(); 680 paint.setColor(CanvasKit.RED); 681 682 const textPaint = new CanvasKit.Paint(); 683 const textFont = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 20); 684 const pr = new CanvasKit.PictureRecorder(); 685 const skpCanvas = pr.beginRecording(CanvasKit.LTRBRect(0, 0, 200, 200)); 686 skpCanvas.drawRect(CanvasKit.LTRBRect(10, 10, 50, 50), paint); 687 skpCanvas.drawText('If you see this, CanvasKit loaded!!', 5, 100, textPaint, textFont); 688 689 const pic = pr.finishRecordingAsPicture(); 690 const skpData = pic.serialize(); 691 692 paint.delete(); 693 pr.delete(); 694 695 const deserialized = CanvasKit.MakePicture(skpData); 696 697 function drawFrame(canvas) { 698 if (deserialized) { 699 canvas.drawPicture(deserialized); 700 } else { 701 canvas.drawText('SKP did not deserialize', 5, 100, textPaint, textFont); 702 } 703 } 704 surface.drawOnce(drawFrame); 705 textPaint.delete(); 706 textFont.delete(); 707 } 708 709 // Shows a hidden message by rotating all the characters in a kind of way that makes you 710 // search with your mouse. 711 function GlyphGame(canvas, robotoData) { 712 const surface = CanvasKit.MakeCanvasSurface('glyphgame'); 713 if (!surface) { 714 console.error('Could not make surface'); 715 return; 716 } 717 const sizeX = document.getElementById('glyphgame').width; 718 const sizeY = document.getElementById('glyphgame').height; 719 const halfDim = Math.min(sizeX, sizeY) / 2; 720 const margin = 50; 721 const marginTop = 25; 722 let rotX = 0; // expected to be updated in interact() 723 let rotY = 0; 724 let pointer = [500, 450]; 725 const radPerPixel = 0.005; // radians of subject rotation per pixel distance moved by mouse. 726 727 const camAngle = Math.PI / 12; 728 const cam = { 729 'eye' : [0, 0, 1 / Math.tan(camAngle/2) - 1], 730 'coa' : [0, 0, 0], 731 'up' : [0, 1, 0], 732 'near' : 0.02, 733 'far' : 4, 734 'angle': camAngle, 735 }; 736 737 let lastImage = null; 738 739 const fontMgr = CanvasKit.FontMgr.FromData([robotoData]); 740 741 const paraStyle = new CanvasKit.ParagraphStyle({ 742 textStyle: { 743 color: CanvasKit.Color(105, 56, 16), // brown 744 fontFamilies: ['Roboto'], 745 fontSize: 28, 746 }, 747 textAlign: CanvasKit.TextAlign.Left, 748 }); 749 const hStyle = CanvasKit.RectHeightStyle.Max; 750 const wStyle = CanvasKit.RectWidthStyle.Tight; 751 752 const quotes = [ 753 'Some activities superficially familiar to you are merely stupid and should be avoided for your safety, although they are not illegal as such. These include: giving your bank account details to the son of the Nigerian Minister of Finance; buying title to bridges, skyscrapers, spacecraft, planets, or other real assets; murder; selling your identity; and entering into financial contracts with entities running Economics 2.0 or higher.', 754 // Charles Stross - Accelerando 755 'If only there were evil people somewhere insidiously committing evil deeds, and it were necessary only to separate them from the rest of us and destroy them. But the line dividing good and evil cuts through the heart of every human being. And who is willing to destroy a piece of his own heart?', 756 // Aleksandr Solzhenitsyn - The Gulag Archipelago 757 'There is one metaphor of which the moderns are very fond; they are always saying, “You can’t put the clock back.” The simple and obvious answer is “You can.” A clock, being a piece of human construction, can be restored by the human finger to any figure or hour. In the same way society, being a piece of human construction, can be reconstructed upon any plan that has ever existed.', 758 // G. K. Chesterton - What's Wrong With The World? 759 ]; 760 761 // pick one at random 762 const text = quotes[Math.floor(Math.random()*3)]; 763 const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr); 764 builder.addText(text); 765 const paragraph = builder.build(); 766 const font = new CanvasKit.Font(CanvasKit.Typeface.GetDefault(), 18); 767 // wrap the text to a given width. 768 paragraph.layout(sizeX - margin*2); 769 770 // to rotate every glyph individually, calculate the bounding rect of each one, 771 // construct an array of rects and paragraphs that would draw each glyph individually. 772 const letters = Array(text.length); 773 for (let i = 0; i < text.length; i++) { 774 const r = paragraph.getRectsForRange(i, i+1, hStyle, wStyle)[0]; 775 // The character is drawn with drawParagraph so we can pass the paraStyle, 776 // and have our character be the exact size and shape the paragraph expected 777 // when it wrapped the text. canvas.drawText wouldn't cut it. 778 const tmpbuilder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr); 779 tmpbuilder.addText(text[i]); 780 const para = tmpbuilder.build(); 781 para.layout(100); 782 letters[i] = { 783 'r': r, 784 'para': para, 785 }; 786 } 787 788 function drawFrame(canvas) { 789 // persistence of vision effect is done by drawing the past frame as an image, 790 // then covering with semitransparent background color. 791 if (lastImage) { 792 canvas.drawImage(lastImage, 0, 0, null); 793 canvas.drawColor(CanvasKit.Color(171, 244, 255, 0.1)); // sky blue, almost transparent 794 } else { 795 canvas.clear(CanvasKit.Color(171, 244, 255)); // sky blue, opaque 796 } 797 canvas.save(); 798 // Set up 3D view enviroment 799 canvas.concat(CanvasKit.M44.setupCamera( 800 CanvasKit.LTRBRect(0, 0, sizeX, sizeY), halfDim, cam)); 801 802 // Rotate the whole paragraph as a unit. 803 const paraRotPoint = [halfDim, halfDim, 1]; 804 canvas.concat(CanvasKit.M44.multiply( 805 CanvasKit.M44.translated(paraRotPoint), 806 CanvasKit.M44.rotated([0,1,0], rotX), 807 CanvasKit.M44.rotated([1,0,0], rotY * 0.2), 808 CanvasKit.M44.translated(CanvasKit.Vector.mulScalar(paraRotPoint, -1)), 809 )); 810 811 // Rotate every glyph in the paragraph individually. 812 let i = 0; 813 for (const letter of letters) { 814 canvas.save(); 815 let r = letter['r']; 816 // rotate about the center of the glyph's rect. 817 rotationPoint = [ 818 margin + r[rectLeft] + (r[rectRight] - r[rectLeft]) / 2, 819 marginTop + r[rectTop] + (r[rectBottom] - r[rectTop]) / 2, 820 0 821 ]; 822 distanceFromPointer = CanvasKit.Vector.dist(pointer, rotationPoint.slice(0, 2)); 823 // Rotate more around the Y-axis depending on the glyph's distance from the pointer. 824 canvas.concat(CanvasKit.M44.multiply( 825 CanvasKit.M44.translated(rotationPoint), 826 // note that I'm rotating around the x axis first, undoing some of the rotation done to the whole 827 // paragraph above, where x came second. If I rotated y first, a lot of letters would end up 828 // upside down, which is a bit too hard to unscramble. 829 CanvasKit.M44.rotated([1,0,0], rotY * -0.6), 830 CanvasKit.M44.rotated([0,1,0], distanceFromPointer * -0.035), 831 CanvasKit.M44.translated(CanvasKit.Vector.mulScalar(rotationPoint, -1)), 832 )); 833 canvas.drawParagraph(letter['para'], margin + r[rectLeft], marginTop + r[rectTop]); 834 i++; 835 canvas.restore(); 836 } 837 canvas.restore(); 838 lastImage = surface.makeImageSnapshot(); 839 } 840 841 function interact(e) { 842 pointer = [e.offsetX, e.offsetY] 843 rotX = (pointer[0] - halfDim) * radPerPixel; 844 rotY = (pointer[1] - halfDim) * radPerPixel * -1; 845 surface.requestAnimationFrame(drawFrame); 846 }; 847 848 document.getElementById('glyphgame').addEventListener('pointermove', interact); 849 surface.requestAnimationFrame(drawFrame); 850 } 851 852 function ColorSupport(CanvasKit) { 853 const surface = CanvasKit.MakeCanvasSurface('colorsupport', CanvasKit.ColorSpace.ADOBE_RGB); 854 if (!surface) { 855 console.error('Could not make surface'); 856 return; 857 } 858 const canvas = surface.getCanvas(); 859 860 // If the surface is correctly initialized with a higher bit depth color type, 861 // And chrome is compositing it into a buffer with the P3 color space, 862 // then the inner round rect should be distinct and less saturated than the full red background. 863 // Even if the monitor it is viewed on cannot accurately represent that color space. 864 865 let red = CanvasKit.Color4f(1, 0, 0, 1); 866 let paint = new CanvasKit.Paint(); 867 paint.setColor(red, CanvasKit.ColorSpace.ADOBE_RGB); 868 canvas.drawPaint(paint); 869 paint.setColor(red, CanvasKit.ColorSpace.DISPLAY_P3); 870 canvas.drawRRect(CanvasKit.RRectXY([50, 50, 250, 250], 30, 30), paint); 871 paint.setColor(red, CanvasKit.ColorSpace.SRGB); 872 canvas.drawRRect(CanvasKit.RRectXY([100, 100, 200, 200], 30, 30), paint); 873 874 surface.flush(); 875 surface.delete(); 876 } 877</script> 878