UNPKG

canvaskit-wasm

Version:

A WASM version of Skia's Canvas API

1,294 lines (1,092 loc) 40.6 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; } #api5_c, #api6_c { 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 id=title1> 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=300 height=300></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 id=title2> CanvasKit can allow for text measurement/placement (e.g. breaking, kerning)</h2> <canvas id=textonpath width=300 height=300></canvas> <canvas id=drawGlyphs width=300 height=300></canvas> <h2 id=title3> Interactive drawPatch</h2> <canvas id=interdrawpatch width=512 height=512></canvas> <script type="text/javascript" src="/build/canvaskit.js"></script> <script type="text/javascript" charset="utf-8" async> var CanvasKit = null; var cdn = 'https://storage.googleapis.com/skia-cdn/misc/'; const ckLoaded = CanvasKitInit({locateFile: (file) => '/build/'+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()); async function initWebGpu(CK) { if (navigator.gpu && CK.webgpu) { const adapter = await navigator.gpu.requestAdapter(); const device = await adapter.requestDevice(); var gpu = CK.MakeGPUDeviceContext(device); if (!gpu) { console.error('Failed to initialize WebGPU device context'); } return gpu; } return null; } const ready = async function() { let CK = await ckLoaded; let gpu = await initWebGpu(CK); return [CK, gpu]; }(); // Examples which only require canvaskit ready.then((initData) => { const [CK, gpu] = initData; if (gpu) { let titles = ['title1', 'title2', 'title3']; titles.forEach((title) => document.getElementById(title).innerText += " (using WebGPU)"); } CanvasKit = CK; PathExample(CanvasKit); InkExample(CanvasKit, gpu); PathPersonExample(CanvasKit, gpu); VertexAPI1(CanvasKit, gpu); GradientAPI1(CanvasKit, gpu); TextOnPathAPI1(CanvasKit, gpu); DrawGlyphsAPI1(CanvasKit, gpu); SurfaceAPI1(CanvasKit, gpu); if (CanvasKit.MakeCanvas){ CanvasAPI1(CanvasKit); CanvasAPI2(CanvasKit); CanvasAPI3(CanvasKit); CanvasAPI4(CanvasKit); CanvasAPI5(CanvasKit); CanvasAPI6(CanvasKit); CanvasAPI7(CanvasKit); CanvasAPI8(CanvasKit); } else { console.log("Skipping CanvasAPI1 because it's not compiled in"); } InteractivePatch(CanvasKit, gpu); }); // Examples requiring external resources Promise.all([ready, loadRoboto]).then((results) => {DrawingExample(...results.flat())}); Promise.all([ready, loadTestImage]).then((results) => {AtlasAPI1(...results.flat())}); Promise.all([ckLoaded, loadTestImage]).then((results) => {DecodeAPI(...results)}); // Helper function to create an optional WebGPU canvas surface, if WebGPU is supported. Falls back // to CanvasKit.MakeCanvasSurface for SW/WebGL otherwise. function MakeCanvasSurface(CanvasKit, gpu, canvasId) { if (gpu) { const canvasContext = CanvasKit.MakeGPUCanvasContext( gpu, document.getElementById(canvasId)); if (!canvasContext) { console.error('Failed to configure WebGPU canvas context'); return; } const surface = CanvasKit.MakeGPUCanvasSurface(canvasContext); if (!surface) { console.error('Failed to initialize current swapchain Surface'); } return surface; } return CanvasKit.MakeCanvasSurface(canvasId); } function DrawingExample(CanvasKit, gpu, robotoData) { if (!robotoData || !CanvasKit) { return; } const surface = MakeCanvasSurface(CanvasKit, gpu, 'patheffect'); if (!surface) { console.error('Could not make surface'); return; } const paint = new CanvasKit.Paint(); const roboto = CanvasKit.Typeface.MakeFreeTypeFaceFromData(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 InteractivePatch(CanvasKit, gpu) { const ELEM = 'interdrawpatch'; const surface = MakeCanvasSurface(CanvasKit, gpu, ELEM); if (!surface) { console.error('Could not make surface'); return; } let live_corner, live_index; const paint = new CanvasKit.Paint(); const pts_paint = new CanvasKit.Paint(); pts_paint.setStyle(CanvasKit.PaintStyle.Stroke); pts_paint.setStrokeWidth(9); pts_paint.setStrokeCap(CanvasKit.StrokeCap.Round); const line_paint = new CanvasKit.Paint(); line_paint.setStyle(CanvasKit.PaintStyle.Stroke); line_paint.setStrokeWidth(2); const colors = [CanvasKit.RED, CanvasKit.BLUE, CanvasKit.YELLOW, CanvasKit.CYAN]; const patch = [ [ 10,170, 10, 10, 170, 10], // prev_vector, point, next_vector [340, 10, 500, 10, 500,170], [500,340, 500,500, 340,500], [170,500, 10,500, 10,340], ]; function get_corner(corner, index) { return [corner[index*2+0], corner[index*2+1]]; } function push_xy(array, xy) { array.push(xy[0], xy[1]); } function patch_to_cubics(patch) { const array = []; push_xy(array, get_corner(patch[0],1)); push_xy(array, get_corner(patch[0],2)); for (let i = 1; i < 4; ++i) { push_xy(array, get_corner(patch[i],0)); push_xy(array, get_corner(patch[i],1)); push_xy(array, get_corner(patch[i],2)); } push_xy(array, get_corner(patch[0],0)); return array; } function drawFrame(canvas) { const cubics = patch_to_cubics(patch); canvas.drawColor(CanvasKit.WHITE); canvas.drawPatch(cubics, colors, null, CanvasKit.BlendMode.Dst, paint); if (live_corner) { canvas.drawPoints(CanvasKit.PointMode.Polygon, live_corner, line_paint); } canvas.drawPoints(CanvasKit.PointMode.Points, cubics, pts_paint); surface.requestAnimationFrame(drawFrame); } surface.requestAnimationFrame(drawFrame); function length2(x, y) { return x*x + y*y; } function hit_test(x,y, x1,y1) { return length2(x-x1, y-y1) <= 10*10; } function pointer_up(e) { live_corner = null; live_index = null; } function pointer_down(e) { live_corner = null; live_index = null; for (p of patch) { for (let i = 0; i < 6; i += 2) { if (hit_test(p[i], p[i+1], e.offsetX, e.offsetY)) { live_corner = p; live_index = i; } } } } function pointer_move(e) { if (e.pressure && live_corner) { if (live_index == 2) { // corner const dx = e.offsetX - live_corner[2]; const dy = e.offsetY - live_corner[3]; for (let i = 0; i < 3; ++i) { live_corner[i*2+0] += dx; live_corner[i*2+1] += dy; } } else { // control-point live_corner[live_index+0] = e.offsetX; live_corner[live_index+1] = e.offsetY; } } } document.getElementById(ELEM).addEventListener('pointermove', pointer_move); document.getElementById(ELEM).addEventListener('pointerdown', pointer_down); document.getElementById(ELEM).addEventListener('pointerup', pointer_up); preventScrolling(document.getElementById(ELEM)); } function PathPersonExample(CanvasKit, gpu) { const surface = MakeCanvasSurface(CanvasKit, gpu, '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, gpu) { const surface = MakeCanvasSurface(CanvasKit, gpu, '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(200, 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); // 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, gpu) { const surface = MakeCanvasSurface(CanvasKit, gpu, '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.Dst, 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(); vertices.delete(); } function GradientAPI1(CanvasKit, gpu) { const surface = MakeCanvasSurface(CanvasKit, gpu, '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 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); surface.flush(); paint.delete(); textFont.delete(); textBlob.delete(); shader.delete(); surface.delete(); } function TextOnPathAPI1(CanvasKit, gpu) { const surface = MakeCanvasSurface(CanvasKit, gpu, '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(); surface.delete(); textBlob.delete(); arc.delete(); paint.delete(); font.delete(); fontPaint.delete(); } function DrawGlyphsAPI1(CanvasKit, gpu) { const surface = MakeCanvasSurface(CanvasKit, gpu, 'drawGlyphs'); if (!surface) { console.error('Could not make surface'); return; } const canvas = surface.getCanvas(); const paint = new CanvasKit.Paint(); const font = new CanvasKit.Font(null, 16); paint.setAntiAlias(true); let glyphs = []; let positions = []; for (let i = 0; i < 256; ++i) { glyphs.push(i); positions.push((i % 16) * 16); positions.push(Math.round(i/16) * 16); } canvas.drawGlyphs(glyphs, positions, 16, 20, font, paint); surface.flush(); surface.delete(); paint.delete(); font.delete(); } function SurfaceAPI1(CanvasKit, gpu) { const surface = MakeCanvasSurface(CanvasKit, gpu, 'surfaces'); if (!surface) { console.error('Could not make surface'); return; } // 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.makeShaderCubic(CanvasKit.TileMode.Repeat, CanvasKit.TileMode.Mirror, 1/3, 1/3); 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); } surface.requestAnimationFrame(drawFrame); } function AtlasAPI1(CanvasKit, gpu, imgData) { if (!CanvasKit || !imgData) { return; } const surface = MakeCanvasSurface(CanvasKit, gpu, '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)); // Allocate space for 2 rectangles. const srcs = CanvasKit.Malloc(Float32Array, 8); srcs.toTypedArray().set([ 0, 0, 250, 250, // LTRB 250, 0, 500, 250 ]); // Allocate space for 2 RSXForms const dsts = CanvasKit.Malloc(Float32Array, 8); dsts.toTypedArray().set([ .5, 0, 0, 0, // scos, ssin, tx, ty 0, .8, 200, 100 ]); // Allocate space for 4 colors. const colors = new CanvasKit.Malloc(Uint32Array, 2); colors.toTypedArray().set([ CanvasKit.ColorAsInt( 85, 170, 10, 128), // light green 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.toTypedArray().set([0.5, 0, (2*i)%200, (5*Math.round(i/200)) % 200], 0); dsts.toTypedArray().set([scale*Math.sin(i/20), scale*Math.cos(i/20), 200, 100], 4); canvas.drawAtlas(img, srcs, dsts, paint, CanvasKit.BlendMode.Plus, colors, {filter: CanvasKit.FilterMode.Nearest}); 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>