UNPKG

canvaskit-wasm

Version:

A WASM version of Skia's Canvas API

1,227 lines (1,023 loc) 38.5 kB
<!DOCTYPE html> <title>CanvasKit (Skia via Web Assembly)</title> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> canvas, img { border: 1px dashed #AAA; width: 300px; height: 300px; } </style> <h2>Drop in replacement for HTML Canvas (e.g. node.js)</h2> <img id=api1 width=300 height=300> <canvas id=api1_c width=300 height=300></canvas> <img id=api2 width=300 height=300> <canvas id=api2_c width=300 height=300></canvas> <img id=api3 width=300 height=300> <canvas id=api3_c width=300 height=300></canvas> <img id=api4 width=300 height=300> <canvas id=api4_c width=300 height=300></canvas> <img id=api5 width=300 height=300> <canvas id=api5_c width=300 height=300></canvas> <img id=api6 width=300 height=300> <canvas id=api6_c width=300 height=300></canvas> <img id=api7 width=300 height=300> <canvas id=api7_c width=300 height=300></canvas> <img id=api8 width=300 height=300> <canvas id=api8_c width=300 height=300></canvas> <h2> CanvasKit expands the functionality of a stock HTML canvas</h2> <canvas id=vertex1 width=300 height=300></canvas> <canvas id=gradient1 width=300 height=300></canvas> <canvas id=patheffect width=300 height=300></canvas> <canvas id=paths width=200 height=200></canvas> <canvas id=pathperson width=300 height=300></canvas> <canvas id=ink width=300 height=300></canvas> <canvas id=surfaces width=300 height=300></canvas> <canvas id=atlas width=300 height=300></canvas> <canvas id=decode width=300 height=300></canvas> <h2> CanvasKit can allow for text shaping (e.g. breaking, kerning)</h2> <canvas id=shape1 width=600 height=600></canvas> <canvas id=shape2 width=600 height=600></canvas> <canvas id=textonpath width=300 height=300></canvas> <script type="text/javascript" src="/node_modules/canvaskit/bin/canvaskit.js"></script> <script type="text/javascript" charset="utf-8"> var CanvasKit = null; var cdn = 'https://storage.googleapis.com/skia-cdn/misc/'; const ckLoaded = CanvasKitInit({locateFile: (file) => '/node_modules/canvaskit/bin/'+file}); const loadRoboto = fetch(cdn + 'Roboto-Regular.ttf').then((response) => response.arrayBuffer()); const loadNotoSerif = fetch(cdn + 'NotoSerif-Regular.ttf').then((response) => response.arrayBuffer()); const loadTestImage = fetch(cdn + 'test.png').then((response) => response.arrayBuffer()); // Examples which only require canvaskit ckLoaded.then((CK) => { CanvasKit = CK; PathExample(CanvasKit); InkExample(CanvasKit); PathPersonExample(CanvasKit); VertexAPI1(CanvasKit); GradiantAPI1(CanvasKit); TextOnPathAPI1(CanvasKit); SurfaceAPI1(CanvasKit); CanvasAPI1(CanvasKit); CanvasAPI2(CanvasKit); CanvasAPI3(CanvasKit); CanvasAPI4(CanvasKit); CanvasAPI5(CanvasKit); CanvasAPI6(CanvasKit); CanvasAPI7(CanvasKit); CanvasAPI8(CanvasKit); }); // Examples requiring external resources Promise.all([ckLoaded, loadRoboto]).then((results) => {DrawingExample(...results)}); Promise.all([ckLoaded, loadNotoSerif]).then((results) => { TextShapingAPI1(...results); TextShapingAPI2(...results); }); Promise.all([ckLoaded, loadTestImage]).then((results) => {AtlasAPI1(...results)}); Promise.all([ckLoaded, loadTestImage]).then((results) => {DecodeAPI(...results)}); function DrawingExample(CanvasKit, robotoData) { if (!robotoData || !CanvasKit) { return; } const surface = CanvasKit.MakeCanvasSurface('patheffect'); if (!surface) { console.error('Could not make surface'); return; } const paint = new CanvasKit.Paint(); const fontMgr = CanvasKit.FontMgr.RefDefault(); const roboto = fontMgr.MakeTypefaceFromData(robotoData); const textPaint = new CanvasKit.Paint(); textPaint.setColor(CanvasKit.RED); textPaint.setAntiAlias(true); const textFont = new CanvasKit.Font(roboto, 30); let i = 0; let X = 128; let Y = 128; function drawFrame(canvas) { const path = starPath(CanvasKit, X, Y); // Some animations see performance improvements by marking their // paths as volatile. path.setIsVolatile(true); const dpe = CanvasKit.PathEffect.MakeDash([15, 5, 5, 10], i/5); i++; paint.setPathEffect(dpe); paint.setStyle(CanvasKit.PaintStyle.Stroke); paint.setStrokeWidth(5.0 + -3 * Math.cos(i/30)); paint.setAntiAlias(true); paint.setColor(CanvasKit.Color(66, 129, 164, 1.0)); canvas.clear(CanvasKit.TRANSPARENT); canvas.drawPath(path, paint); canvas.drawText('Try Clicking!', 10, 280, textPaint, textFont); dpe.delete(); path.delete(); surface.requestAnimationFrame(drawFrame); } surface.requestAnimationFrame(drawFrame); // Make animation interactive let interact = (e) => { if (!e.pressure) { return; } X = e.offsetX; Y = e.offsetY; }; document.getElementById('patheffect').addEventListener('pointermove', interact); document.getElementById('patheffect').addEventListener('pointerdown', interact); preventScrolling(document.getElementById('patheffect')); // A client would need to delete this if it didn't go on for ever. // paint.delete(); // textPaint.delete(); // textFont.delete(); } function PathPersonExample(CanvasKit) { const surface = CanvasKit.MakeSWCanvasSurface('pathperson'); if (!surface) { console.error('Could not make surface'); return; } function drawFrame(canvas) { const paint = new CanvasKit.Paint(); paint.setStrokeWidth(1.0); paint.setAntiAlias(true); paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); paint.setStyle(CanvasKit.PaintStyle.Stroke); const path = new CanvasKit.Path(); path.moveTo(10, 10); path.lineTo(100, 10); path.moveTo(10, 10); path.lineTo(10, 200); path.moveTo(10, 100); path.lineTo(100,100); path.moveTo(10, 200); path.lineTo(100, 200); canvas.drawPath(path, paint); path.delete(); paint.delete(); } // Intentionally just draw frame once surface.drawOnce(drawFrame); } function PathExample(CanvasKit) { const surface = CanvasKit.MakeSWCanvasSurface('paths'); if (!surface) { console.error('Could not make surface'); return; } function drawFrame(canvas) { const paint = new CanvasKit.Paint(); paint.setStrokeWidth(1.0); paint.setAntiAlias(true); paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); paint.setStyle(CanvasKit.PaintStyle.Stroke); const path = new CanvasKit.Path(); path.moveTo(20, 5); path.lineTo(30, 20); path.lineTo(40, 10); path.lineTo(50, 20); path.lineTo(60, 0); path.lineTo(20, 5); path.moveTo(20, 80); path.cubicTo(90, 10, 160, 150, 190, 10); path.moveTo(36, 148); path.quadTo(66, 188, 120, 136); path.lineTo(36, 148); path.moveTo(150, 180); path.arcToTangent(150, 100, 50, 200, 20); path.lineTo(160, 160); path.moveTo(20, 120); path.lineTo(20, 120); canvas.drawPath(path, paint); const rrect = CanvasKit.RRectXY([100, 10, 140, 62], 10, 4); const rrectPath = new CanvasKit.Path().addRRect(rrect, true); canvas.drawPath(rrectPath, paint); rrectPath.delete(); path.delete(); paint.delete(); } // Intentionally just draw frame once surface.drawOnce(drawFrame); } function preventScrolling(canvas) { canvas.addEventListener('touchmove', (e) => { // Prevents touch events in the canvas from scrolling the canvas. e.preventDefault(); e.stopPropagation(); }); } function InkExample(CanvasKit) { const surface = CanvasKit.MakeCanvasSurface('ink'); if (!surface) { console.error('Could not make surface'); return; } let paint = new CanvasKit.Paint(); paint.setAntiAlias(true); paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); paint.setStyle(CanvasKit.PaintStyle.Stroke); paint.setStrokeWidth(4.0); paint.setPathEffect(CanvasKit.PathEffect.MakeCorner(50)); // Draw I N K let path = new CanvasKit.Path(); path.moveTo(80, 30); path.lineTo(80, 80); path.moveTo(100, 80); path.lineTo(100, 15); path.lineTo(130, 95); path.lineTo(130, 30); path.moveTo(150, 30); path.lineTo(150, 80); path.moveTo(170, 30); path.lineTo(150, 55); path.lineTo(170, 80); let paths = [path]; let paints = [paint]; function drawFrame(canvas) { canvas.clear(CanvasKit.Color(255, 255, 255, 1.0)); for (let i = 0; i < paints.length && i < paths.length; i++) { canvas.drawPath(paths[i], paints[i]); } surface.requestAnimationFrame(drawFrame); } let hold = false; let interact = (e) => { let type = e.type; if (type === 'lostpointercapture' || type === 'pointerup' || !e.pressure ) { hold = false; return; } if (hold) { path.lineTo(e.offsetX, e.offsetY); } else { paint = paint.copy(); paint.setColor(CanvasKit.Color(Math.random() * 255, Math.random() * 255, Math.random() * 255, Math.random() + .2)); paints.push(paint); path = new CanvasKit.Path(); paths.push(path); path.moveTo(e.offsetX, e.offsetY); } hold = true; }; document.getElementById('ink').addEventListener('pointermove', interact); document.getElementById('ink').addEventListener('pointerdown', interact); document.getElementById('ink').addEventListener('lostpointercapture', interact); document.getElementById('ink').addEventListener('pointerup', interact); preventScrolling(document.getElementById('ink')); surface.requestAnimationFrame(drawFrame); } function starPath(CanvasKit, X=128, Y=128, R=116) { let p = new CanvasKit.Path(); p.moveTo(X + R, Y); for (let i = 1; i < 8; i++) { let a = 2.6927937 * i; p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a)); } return p; } function CanvasAPI1(CanvasKit) { let skcanvas = CanvasKit.MakeCanvas(300, 300); let realCanvas = document.getElementById('api1_c'); let skPromise = fetch(cdn + 'test.png') // if clients want to use a Blob, they are responsible // for reading it themselves. .then((response) => response.arrayBuffer()) .then((buffer) => { skcanvas._img = skcanvas.decodeImage(buffer); }); let realPromise = fetch(cdn + 'test.png') .then((response) => response.blob()) .then((blob) => createImageBitmap(blob)) .then((bitmap) => { realCanvas._img = bitmap; }); let realFontLoaded = new FontFace('Bungee', 'url(/tests/assets/Bungee-Regular.ttf)', { 'family': 'Bungee', 'style': 'normal', 'weight': '400', }).load().then((font) => { document.fonts.add(font); }); let skFontLoaded = fetch('/tests/assets/Bungee-Regular.ttf').then( (response) => response.arrayBuffer()).then( (buffer) => { // loadFont is synchronous skcanvas.loadFont(buffer, { 'family': 'Bungee', 'style': 'normal', 'weight': '400', }); }); Promise.all([realPromise, skPromise, realFontLoaded, skFontLoaded]).then(() => { for (let canvas of [skcanvas, realCanvas]) { let ctx = canvas.getContext('2d'); ctx.fillStyle = '#EEE'; ctx.fillRect(0, 0, 300, 300); ctx.fillStyle = 'black'; ctx.font = '26px Bungee'; ctx.rotate(.1); let text = ctx.measureText('Awesome'); ctx.fillText('Awesome ', 25, 100); ctx.strokeText('Groovy!', 35+text.width, 100); // Draw line under Awesome ctx.strokeStyle = 'rgba(125,0,0,0.5)'; ctx.beginPath(); ctx.lineWidth = 6; ctx.moveTo(25, 105); ctx.lineTo(25 + text.width, 105); ctx.stroke(); // squished vertically ctx.globalAlpha = 0.7 ctx.imageSmoothingQuality = 'medium'; ctx.drawImage(canvas._img, 150, 150, 150, 100); ctx.rotate(-.2); ctx.imageSmoothingEnabled = false; ctx.drawImage(canvas._img, 100, 150, 400, 350, 10, 200, 150, 100); let idata = ctx.getImageData(80, 220, 40, 45); ctx.putImageData(idata, 250, 10); ctx.putImageData(idata, 200, 10, 20, 10, 20, 30); ctx.resetTransform(); ctx.strokeStyle = 'black'; ctx.lineWidth = 1; ctx.strokeRect(200, 10, 40, 45); idata = ctx.createImageData(10, 20); ctx.putImageData(idata, 10, 10); } document.getElementById('api1').src = skcanvas.toDataURL(); skcanvas.dispose(); }); } function CanvasAPI2(CanvasKit) { let skcanvas = CanvasKit.MakeCanvas(300, 300); let realCanvas = document.getElementById('api2_c'); realCanvas.width = 300; realCanvas.height = 300; // svg data for a clock 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'); 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'); for (let canvas of [skcanvas, realCanvas]) { let ctx = canvas.getContext('2d'); ctx.scale(1.5, 1.5); ctx.moveTo(20, 5); ctx.lineTo(30, 20); ctx.lineTo(40, 10); ctx.lineTo(50, 20); ctx.lineTo(60, 0); ctx.lineTo(20, 5); ctx.moveTo(20, 80); ctx.bezierCurveTo(90, 10, 160, 150, 190, 10); ctx.moveTo(36, 148); ctx.quadraticCurveTo(66, 188, 120, 136); ctx.lineTo(36, 148); ctx.rect(5, 170, 20, 25); ctx.moveTo(150, 180); ctx.arcTo(150, 100, 50, 200, 20); ctx.lineTo(160, 160); ctx.moveTo(20, 120); ctx.arc(20, 120, 18, 0, 1.75 * Math.PI); ctx.lineTo(20, 120); ctx.moveTo(150, 5); ctx.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI); ctx.lineWidth = 4/3; ctx.stroke(); // make a clock ctx.stroke(canvas._path); // Test edgecases and draw direction ctx.beginPath(); ctx.arc(50, 100, 10, Math.PI, -Math.PI/2); ctx.stroke(); ctx.beginPath(); ctx.arc(75, 100, 10, Math.PI, -Math.PI/2, true); ctx.stroke(); ctx.beginPath(); ctx.arc(100, 100, 10, Math.PI, 100.1 * Math.PI, true); ctx.stroke(); ctx.beginPath(); ctx.arc(125, 100, 10, Math.PI, 100.1 * Math.PI, false); ctx.stroke(); ctx.beginPath(); ctx.ellipse(155, 100, 10, 15, Math.PI/8, 100.1 * Math.PI, Math.PI, true); ctx.stroke(); ctx.beginPath(); ctx.ellipse(180, 100, 10, 15, Math.PI/8, Math.PI, 100.1 * Math.PI, true); ctx.stroke(); } document.getElementById('api2').src = skcanvas.toDataURL(); skcanvas.dispose(); } function CanvasAPI3(CanvasKit) { let skcanvas = CanvasKit.MakeCanvas(300, 300); let realCanvas = document.getElementById('api3_c'); realCanvas.width = 300; realCanvas.height = 300; for (let canvas of [skcanvas, realCanvas]) { let ctx = canvas.getContext('2d'); ctx.rect(10, 10, 20, 20); ctx.scale(2.0, 4.0); ctx.rect(30, 10, 20, 20); ctx.resetTransform(); ctx.rotate(Math.PI / 3); ctx.rect(50, 10, 20, 20); ctx.resetTransform(); ctx.translate(30, -2); ctx.rect(70, 10, 20, 20); ctx.resetTransform(); ctx.translate(60, 0); ctx.rotate(Math.PI / 6); ctx.transform(1.5, 0, 0, 0.5, 0, 0, 0); // effectively scale ctx.rect(90, 10, 20, 20); ctx.resetTransform(); ctx.save(); ctx.setTransform(2, 0, -.5, 2.5, -40, 120); ctx.rect(110, 10, 20, 20); ctx.lineTo(110, 0); ctx.restore(); ctx.lineTo(220, 120); ctx.scale(3.0, 3.0); ctx.font = '6pt Noto Mono'; ctx.fillText('This text should be huge', 10, 80); ctx.resetTransform(); ctx.strokeStyle = 'black'; ctx.lineWidth = 2; ctx.stroke(); ctx.beginPath(); ctx.moveTo(250, 30); ctx.lineTo(250, 80); ctx.scale(3.0, 3.0); ctx.lineTo(280/3, 90/3); ctx.closePath(); ctx.strokeStyle = 'black'; ctx.lineWidth = 5; ctx.stroke(); } document.getElementById('api3').src = skcanvas.toDataURL(); skcanvas.dispose(); } function CanvasAPI4(CanvasKit) { let skcanvas = CanvasKit.MakeCanvas(300, 300); let realCanvas = document.getElementById('api4_c'); realCanvas.width = 300; realCanvas.height = 300; for (let canvas of [skcanvas, realCanvas]) { let ctx = canvas.getContext('2d'); ctx.strokeStyle = '#000'; ctx.fillStyle = '#CCC'; ctx.shadowColor = 'rebeccapurple'; ctx.shadowBlur = 1; ctx.shadowOffsetX = 3; ctx.shadowOffsetY = -8; ctx.rect(10, 10, 30, 30); ctx.save(); ctx.strokeStyle = '#C00'; ctx.fillStyle = '#00C'; ctx.shadowBlur = 0; ctx.shadowColor = 'transparent'; ctx.stroke(); ctx.restore(); ctx.fill(); ctx.beginPath(); ctx.moveTo(36, 148); ctx.quadraticCurveTo(66, 188, 120, 136); ctx.closePath(); ctx.stroke(); ctx.beginPath(); ctx.shadowColor = '#993366AA'; ctx.shadowOffsetX = 8; ctx.shadowBlur = 5; ctx.setTransform(2, 0, -.5, 2.5, -40, 120); ctx.rect(110, 10, 20, 20); ctx.lineTo(110, 0); ctx.resetTransform(); ctx.lineTo(220, 120); ctx.stroke(); ctx.fillStyle = 'green'; ctx.font = '16pt Noto Mono'; ctx.fillText('This should be shadowed', 20, 80); ctx.beginPath(); ctx.lineWidth = 6; ctx.ellipse(10, 290, 30, 30, 0, 0, Math.PI * 2); ctx.scale(2, 1); ctx.moveTo(10, 290) ctx.ellipse(10, 290, 30, 60, 0, 0, Math.PI * 2); ctx.resetTransform(); ctx.scale(3, 1); ctx.moveTo(10, 290) ctx.ellipse(10, 290, 30, 90, 0, 0, Math.PI * 2); ctx.stroke(); } document.getElementById('api4').src = skcanvas.toDataURL(); skcanvas.dispose(); } function CanvasAPI5(CanvasKit) { let skcanvas = CanvasKit.MakeCanvas(600, 600); let realCanvas = document.getElementById('api5_c'); realCanvas.width = 600; realCanvas.height = 600; for (let canvas of [skcanvas, realCanvas]) { let ctx = canvas.getContext('2d'); ctx.scale(1.1, 1.1); ctx.translate(10, 10); // Shouldn't impact the fillRect calls ctx.setLineDash([5, 3]); ctx.fillStyle = 'rgba(200, 0, 100, 0.81)'; ctx.fillRect(20, 30, 100, 100); ctx.globalAlpha = 0.81; ctx.fillStyle = 'rgba(200, 0, 100, 1.0)'; ctx.fillRect(120, 30, 100, 100); // This shouldn't do anything ctx.globalAlpha = 0.1; ctx.fillStyle = 'rgba(200, 0, 100, 0.9)'; ctx.globalAlpha = 0.9; // Intentional no-op to check ordering ctx.clearRect(220, 30, 100, 100); ctx.fillRect(220, 30, 100, 100); ctx.fillRect(320, 30, 100, 100); ctx.clearRect(330, 40, 80, 80); ctx.strokeStyle = 'blue'; ctx.lineWidth = 3; ctx.setLineDash([5, 3]); ctx.strokeRect(20, 150, 100, 100); ctx.setLineDash([50, 30]); ctx.strokeRect(125, 150, 100, 100); ctx.lineDashOffset = 25; ctx.strokeRect(230, 150, 100, 100); ctx.setLineDash([2, 5, 9]); ctx.strokeRect(335, 150, 100, 100); ctx.setLineDash([5, 2]); ctx.moveTo(336, 400); ctx.quadraticCurveTo(366, 488, 120, 450); ctx.lineTo(300, 400); ctx.stroke(); ctx.font = '36pt Noto Mono'; ctx.strokeText('Dashed', 20, 350); ctx.fillText('Not Dashed', 20, 400); } document.getElementById('api5').src = skcanvas.toDataURL(); skcanvas.dispose(); } function CanvasAPI6(CanvasKit) { let skcanvas = CanvasKit.MakeCanvas(600, 600); let realCanvas = document.getElementById('api6_c'); realCanvas.width = 600; realCanvas.height = 600; for (let canvas of [skcanvas, realCanvas]) { let ctx = canvas.getContext('2d'); let rgradient = ctx.createRadialGradient(200, 300, 10, 100, 100, 300); // Add three color stops rgradient.addColorStop(0, 'red'); rgradient.addColorStop(0.7, 'white'); rgradient.addColorStop(1, 'blue'); ctx.fillStyle = rgradient; ctx.globalAlpha = 0.7; ctx.fillRect(0, 0, 600, 600); ctx.globalAlpha = 0.95; ctx.beginPath(); ctx.arc(300, 100, 90, 0, Math.PI*1.66); ctx.closePath(); ctx.strokeStyle = 'yellow'; ctx.lineWidth = 5; ctx.stroke(); ctx.save(); ctx.clip(); let lgradient = ctx.createLinearGradient(200, 20, 420, 40); // Add three color stops lgradient.addColorStop(0, 'green'); lgradient.addColorStop(0.5, 'cyan'); lgradient.addColorStop(1, 'orange'); ctx.fillStyle = lgradient; ctx.fillRect(200, 30, 200, 300); ctx.restore(); ctx.fillRect(550, 550, 40, 40); } document.getElementById('api6').src = skcanvas.toDataURL(); skcanvas.dispose(); } function CanvasAPI7(CanvasKit) { let skcanvas = CanvasKit.MakeCanvas(300, 300); let realCanvas = document.getElementById('api7_c'); let skPromise = fetch(cdn + 'test.png') // if clients want to use a Blob, they are responsible // for reading it themselves. .then((response) => response.arrayBuffer()) .then((buffer) => { skcanvas._img = skcanvas.decodeImage(buffer); }); let realPromise = fetch(cdn + 'test.png') .then((response) => response.blob()) .then((blob) => createImageBitmap(blob)) .then((bitmap) => { realCanvas._img = bitmap; }); Promise.all([realPromise, skPromise]).then(() => { for (let canvas of [skcanvas, realCanvas]) { let ctx = canvas.getContext('2d'); ctx.fillStyle = '#EEE'; ctx.fillRect(0, 0, 300, 300); ctx.lineWidth = 20; ctx.scale(0.1, 0.2); let pattern = ctx.createPattern(canvas._img, 'repeat'); ctx.fillStyle = pattern; ctx.fillRect(0, 0, 1500, 750); pattern = ctx.createPattern(canvas._img, 'repeat-x'); ctx.fillStyle = pattern; ctx.fillRect(1500, 0, 3000, 750); ctx.globalAlpha = 0.7 pattern = ctx.createPattern(canvas._img, 'repeat-y'); ctx.fillStyle = pattern; ctx.fillRect(0, 750, 1500, 1500); ctx.strokeRect(0, 750, 1500, 1500); pattern = ctx.createPattern(canvas._img, 'no-repeat'); ctx.fillStyle = pattern; pattern.setTransform({a: 1, b: -.1, c:.1, d: 0.5, e: 1800, f:800}); ctx.fillRect(0, 0, 3000, 1500); } document.getElementById('api7').src = skcanvas.toDataURL(); skcanvas.dispose(); }); } function CanvasAPI8(CanvasKit) { let skcanvas = CanvasKit.MakeCanvas(300, 300); let realCanvas = document.getElementById('api8_c'); function drawPoint(ctx, x, y, color) { ctx.fillStyle = color; ctx.fillRect(x, y, 1, 1); } const IN = 'purple'; const OUT = 'orange'; const SCALE = 4; const pts = [[3, 3], [4, 4], [5, 5], [10, 10], [8, 10], [6, 10], [6.5, 9], [15, 10], [17, 10], [17, 11], [24, 24], [25, 25], [26, 26], [27, 27]]; const tests = [ { xOffset: 0, yOffset: 0, fillType: 'nonzero', strokeWidth: 0, testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'nonzero'), }, { xOffset: 30, yOffset: 0, fillType: 'evenodd', strokeWidth: 0, testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'evenodd'), }, { xOffset: 0, yOffset: 30, fillType: null, strokeWidth: 1, testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE), }, { xOffset: 30, yOffset: 30, fillType: null, strokeWidth: 2, testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE), }, ]; for (let canvas of [skcanvas, realCanvas]) { let ctx = canvas.getContext('2d'); ctx.font = '11px Noto Mono'; // Draw some visual aids ctx.fillText('path-nonzero', 30, 15); ctx.fillText('path-evenodd', 150, 15); ctx.fillText('stroke-1px-wide', 30, 130); ctx.fillText('stroke-2px-wide', 150, 130); ctx.fillText('purple is IN, orange is OUT', 10, 280); // Scale up to make single pixels easier to see ctx.scale(SCALE, SCALE); for (let test of tests) { ctx.beginPath(); let xOffset = test.xOffset; let yOffset = test.yOffset; ctx.fillStyle = '#AAA'; ctx.lineWidth = test.strokeWidth; ctx.rect(5+xOffset, 5+yOffset, 20, 20); ctx.arc(15+xOffset, 15+yOffset, 8, 0, Math.PI*2, false); if (test.fillType) { ctx.fill(test.fillType); } else { ctx.stroke(); } for (let pt of pts) { let [x, y] = pt; x += xOffset; y += yOffset; // naively apply transform when querying because the points queried // ignore the CTM. if (test.testFn(ctx, x, y)) { drawPoint(ctx, x, y, IN); } else { drawPoint(ctx, x, y, OUT); } } } } document.getElementById('api8').src = skcanvas.toDataURL(); skcanvas.dispose(); } function VertexAPI1(CanvasKit) { const surface = CanvasKit.MakeCanvasSurface('vertex1'); if (!surface) { console.error('Could not make surface'); return; } const canvas = surface.getCanvas(); let paint = new CanvasKit.Paint(); // See https://fiddle.skia.org/c/f48b22eaad1bb7adcc3faaa321754af6 // for original c++ version. let points = [[ 0, 0 ], [ 250, 0 ], [ 100, 100 ], [ 0, 250 ]]; let colors = [CanvasKit.RED, CanvasKit.BLUE, CanvasKit.YELLOW, CanvasKit.CYAN]; let vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TriangleFan, points, null, colors, false /*isVolatile*/); canvas.drawVertices(vertices, CanvasKit.BlendMode.Src, paint); vertices.delete(); // See https://fiddle.skia.org/c/e8bdae9bea3227758989028424fcac3d // for original c++ version. points = [[ 300, 300 ], [ 50, 300 ], [ 200, 200 ], [ 300, 50 ]]; let texs = [[ 0, 0 ], [ 0, 250 ], [ 250, 250 ], [ 250, 0 ]]; vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TriangleFan, points, texs, colors); let shader = CanvasKit.Shader.MakeLinearGradient([0, 0], [250, 0], colors, null, CanvasKit.TileMode.Clamp); paint.setShader(shader); canvas.drawVertices(vertices, CanvasKit.BlendMode.Darken, paint); surface.flush(); shader.delete(); paint.delete(); surface.delete(); } function GradiantAPI1(CanvasKit) { const surface = CanvasKit.MakeSWCanvasSurface('gradient1'); if (!surface) { console.error('Could not make surface'); return; } const canvas = surface.getCanvas(); let paint = new CanvasKit.Paint(); // See https://fiddle.skia.org/c/f48b22eaad1bb7adcc3faaa321754af6 // for original c++ version. let points = [[ 0, 0 ], [ 250, 0 ], [ 100, 100 ], [ 0, 250 ]]; let colors = [CanvasKit.BLUE, CanvasKit.YELLOW, CanvasKit.RED]; let pos = [0, .7, 1.0]; let transform = [2, 0, 0, 0, 2, 0, 0, 0, 1] let shader = CanvasKit.Shader.MakeRadialGradient([150,150], 130, colors, pos, CanvasKit.TileMode.Mirror, transform); paint.setShader(shader); const textFont = new CanvasKit.Font(null, 75); const textBlob = CanvasKit.TextBlob.MakeFromText('Radial', textFont); canvas.drawTextBlob(textBlob, 10, 200, paint); paint.delete() textFont.delete(); textBlob.delete(); surface.flush(); } function TextShapingAPI1(CanvasKit, notoserifData) { if (!notoserifData || !CanvasKit) { return; } const surface = CanvasKit.MakeSWCanvasSurface('shape1'); if (!surface) { console.error('Could not make surface'); return; } const canvas = surface.getCanvas(); const paint = new CanvasKit.Paint(); paint.setColor(CanvasKit.BLUE); paint.setStyle(CanvasKit.PaintStyle.Stroke); const fontMgr = CanvasKit.FontMgr.RefDefault(); const notoSerif = fontMgr.MakeTypefaceFromData(notoserifData); const textPaint = new CanvasKit.Paint(); const textFont = new CanvasKit.Font(notoSerif, 20); canvas.drawRect(CanvasKit.LTRBRect(30, 30, 200, 200), paint); canvas.drawText('This text is not shaped, and overflows the boundry', 35, 50, textPaint, textFont); const shapedText = new CanvasKit.ShapedText({ font: textFont, leftToRight: true, text: 'This text *is* shaped, and wraps to the right width.', width: 160, }); const textBoxX = 35; const textBoxY = 55; canvas.drawText(shapedText, textBoxX, textBoxY, textPaint); const bounds = shapedText.getBounds(); bounds[0] += textBoxX; // left bounds[2] += textBoxX; // right bounds[1] += textBoxY; // top bounds[3] += textBoxY // bottom canvas.drawRect(bounds, paint); const SHAPE_TEST_TEXT = 'VAVAVAVAVAFIfi'; const textFont2 = new CanvasKit.Font(notoSerif, 60); const shapedText2 = new CanvasKit.ShapedText({ font: textFont2, leftToRight: true, text: SHAPE_TEST_TEXT, width: 600, }); canvas.drawText('no kerning ↓', 10, 240, textPaint, textFont); canvas.drawText(SHAPE_TEST_TEXT, 10, 300, textPaint, textFont2); canvas.drawText(shapedText2, 10, 300, textPaint); canvas.drawText('kerning ↑', 10, 390, textPaint, textFont); surface.flush(); paint.delete(); notoSerif.delete(); textPaint.delete(); textFont.delete(); shapedText.delete(); textFont2.delete(); shapedText2.delete(); surface.delete(); } function TextShapingAPI2(CanvasKit, notoserifData) { if (!notoserifData || !CanvasKit) { return; } const surface = CanvasKit.MakeSWCanvasSurface('shape2'); if (!surface) { console.error('Could not make surface'); return; } const paint = new CanvasKit.Paint(); paint.setColor(CanvasKit.BLUE); paint.setStyle(CanvasKit.PaintStyle.Stroke); const fontMgr = CanvasKit.FontMgr.RefDefault(); const notoSerif = fontMgr.MakeTypefaceFromData(notoserifData); const textPaint = new CanvasKit.Paint(); const bigFont = new CanvasKit.Font(notoSerif, 40); const smallFont = new CanvasKit.Font(notoSerif, 25); const TEXT = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris ac leo vitae ipsum hendrerit euismod quis rutrum nibh. Quisque non suscipit urna. Donec enim urna, facilisis vitae volutpat in, mattis at elit. Sed quis augue et dolor dignissim fringilla. Sed non massa eu neque tristique malesuada. '; let X = 240; let Y = 190; function drawFrame(canvas) { canvas.clear(CanvasKit.TRANSPARENT); const shapedText = new CanvasKit.ShapedText({ font: smallFont, leftToRight: true, text: TEXT, width: (X * 2) - 10, }); canvas.drawRect(CanvasKit.LTRBRect(10, 10, X*2, Y*2), paint); canvas.drawText(shapedText, 10, 40, textPaint, smallFont); canvas.drawText('Try Clicking!', 10, 480, textPaint, bigFont); shapedText.delete(); surface.requestAnimationFrame(drawFrame); } surface.requestAnimationFrame(drawFrame); // Make animation interactive let interact = (e) => { if (!e.pressure) { return; } X = e.offsetX; Y = e.offsetY; }; document.getElementById('shape2').addEventListener('pointermove', interact); document.getElementById('shape2').addEventListener('pointerdown', interact); preventScrolling(document.getElementById('shape2')); } function TextOnPathAPI1(CanvasKit) { const surface = CanvasKit.MakeSWCanvasSurface('textonpath'); if (!surface) { console.error('Could not make surface'); return; } const canvas = surface.getCanvas(); const paint = new CanvasKit.Paint(); paint.setStyle(CanvasKit.PaintStyle.Stroke); paint.setAntiAlias(true); const font = new CanvasKit.Font(null, 24); const fontPaint = new CanvasKit.Paint(); fontPaint.setStyle(CanvasKit.PaintStyle.Fill); fontPaint.setAntiAlias(true); const arc = new CanvasKit.Path(); arc.arcToOval(CanvasKit.LTRBRect(20, 40, 280, 300), -160, 140, true); arc.lineTo(210, 140); arc.arcToOval(CanvasKit.LTRBRect(20, 0, 280, 260), 160, -140, true); const str = 'This téxt should follow the curve across contours...'; const textBlob = CanvasKit.TextBlob.MakeOnPath(str, arc, font); canvas.drawPath(arc, paint); canvas.drawTextBlob(textBlob, 0, 0, fontPaint); surface.flush(); textBlob.delete(); arc.delete(); paint.delete(); font.delete(); fontPaint.delete(); } function SurfaceAPI1(CanvasKit) { const surface = CanvasKit.MakeCanvasSurface('surfaces'); if (!surface) { console.error('Could not make surface'); return; } const grContext = surface.grContext; // create a subsurface as a temporary workspace. const subSurface = surface.makeSurface({ width: 50, height: 50, alphaType: CanvasKit.AlphaType.Premul, colorType: CanvasKit.ColorType.RGBA_8888, colorSpace: CanvasKit.ColorSpace.SRGB, }); if (!subSurface) { console.error('Could not make subsurface'); return; } // draw a small "scene" const paint = new CanvasKit.Paint(); paint.setColor(CanvasKit.Color(139, 228, 135, 0.95)); // greenish paint.setStyle(CanvasKit.PaintStyle.Fill); paint.setAntiAlias(true); const subCanvas = subSurface.getCanvas(); subCanvas.clear(CanvasKit.BLACK); subCanvas.drawRect(CanvasKit.LTRBRect(5, 15, 45, 40), paint); paint.setColor(CanvasKit.Color(214, 93, 244)); // purplish for (let i = 0; i < 10; i++) { const x = Math.random() * 50; const y = Math.random() * 50; subCanvas.drawOval(CanvasKit.XYWHRect(x, y, 6, 6), paint); } // Snap it off as an Image - this image will be in the form the // parent surface prefers (e.g. Texture for GPU / Raster for CPU). const img = subSurface.makeImageSnapshot(); // clean up the temporary surface (which also cleans up subCanvas) subSurface.delete(); paint.delete(); // Make it repeat a bunch with a shader const pattern = img.makeShader(CanvasKit.TileMode.Repeat, CanvasKit.TileMode.Mirror); const patternPaint = new CanvasKit.Paint(); patternPaint.setShader(pattern); let i = 0; function drawFrame(canvas) { i++; canvas.clear(CanvasKit.WHITE); canvas.drawOval(CanvasKit.LTRBRect(i % 60, i % 60, 300 - (i% 60), 300 - (i % 60)), patternPaint); surface.requestAnimationFrame(drawFrame); // if (grContext) { // console.log(grContext.getResourceCacheUsageBytes() + ' bytes used in the GPU'); // } } surface.requestAnimationFrame(drawFrame); } function AtlasAPI1(CanvasKit, imgData) { if (!CanvasKit || !imgData) { return; } const surface = CanvasKit.MakeCanvasSurface('atlas'); if (!surface) { console.error('Could not make surface'); return; } const img = CanvasKit.MakeImageFromEncoded(imgData); const paint = new CanvasKit.Paint(); paint.setColor(CanvasKit.Color(0, 0, 0, 0.8)); const srcs = new CanvasKit.RectBuilder(); srcs.push(0, 0, 250, 250 ); // LTRB srcs.push(250, 0, 500, 250 ); const dsts = new CanvasKit.RSXFormBuilder(); dsts.push(.5, 0, 0, 0); // scos, ssin, tx, ty dsts.push(0, .8, 200, 100); const colors = new CanvasKit.ColorBuilder(); colors.push(CanvasKit.ColorAsInt(85, 170, 10, 128)); // light green colors.push(CanvasKit.ColorAsInt(51, 51, 191, 128)); // light blue let i = 0; function drawFrame(canvas) { canvas.clear(CanvasKit.WHITE); i++; let scale = 0.5 + Math.sin(i/40)/4; // update the coordinates of existing sprites - note that this // does not require a full re-copy of the full array; they are // updated in-place. dsts.set(0, 0.5, 0, (2*i)%200, (5*Math.round(i/200)) % 200); dsts.set(1, scale*Math.sin(i/20), scale*Math.cos(i/20), 200, 100); canvas.drawAtlas(img, srcs, dsts, paint, CanvasKit.BlendMode.Plus, colors); surface.requestAnimationFrame(drawFrame); } surface.requestAnimationFrame(drawFrame); } async function DecodeAPI(CanvasKit, imgData) { if (!CanvasKit || !imgData) { return; } const surface = CanvasKit.MakeCanvasSurface('decode'); if (!surface) { console.error('Could not make surface'); return; } const blob = new Blob([ imgData ]); // ImageBitmap is not supported in Safari const imageBitmap = await createImageBitmap(blob); const img = await CanvasKit.MakeImageFromCanvasImageSource(imageBitmap); surface.drawOnce((canvas) => { canvas.drawImage(img, 0, 0, null); }); } </script>