Created
January 19, 2026 18:02
-
-
Save JessicaWachtel/b92783215d7ae5900b508799d893c458 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>Wasm vs JS: The Showdown</title> | |
| <style> | |
| body { font-family: sans-serif; text-align: center; background: #f4f4f4; padding: 20px; } | |
| .race-box { background: white; padding: 20px; border-radius: 10px; margin: 20px auto; max-width: 800px; border: 1px solid #ddd; } | |
| .container { display: flex; justify-content: center; gap: 20px; } | |
| canvas { background: #eee; max-width: 45%; height: auto; border: 1px solid #999; } | |
| .stats { font-weight: bold; padding: 10px; border-radius: 5px; margin-bottom: 10px; } | |
| .js-label { color: #d68910; } | |
| .wasm-label { color: #2e86c1; } | |
| button { padding: 10px 20px; cursor: pointer; font-weight: bold; } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>How Much Faster is WebAssembly?</h1> | |
| <input type="file" id="upload" accept="image/*"><br><br> | |
| <div class="race-box"> | |
| <h2>Race 1: Simple Grayscale (Light Math)</h2> | |
| <button id="btnGrayscale">Run Grayscale Race</button> | |
| <div class="container"> | |
| <div> | |
| <div id="jsTimeGray" class="stats js-label">JS: --ms</div> | |
| <canvas id="canvasJSGray"></canvas> | |
| </div> | |
| <div> | |
| <div id="wasmTimeGray" class="stats wasm-label">Wasm: --ms</div> | |
| <canvas id="canvasWasmGray"></canvas> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="race-box"> | |
| <h2>Race 2: Sharpen Filter (Heavy Task)</h2> | |
| <button id="btnSharpen">Run Sharpen Race</button> | |
| <div class="container"> | |
| <div> | |
| <div id="jsTimeSharp" class="stats js-label">JS: --ms</div> | |
| <canvas id="canvasJSSharp"></canvas> | |
| </div> | |
| <div> | |
| <div id="wasmTimeSharp" class="stats wasm-label">Wasm: --ms</div> | |
| <canvas id="canvasWasmSharp"></canvas> | |
| </div> | |
| </div> | |
| </div> | |
| <script type="module"> | |
| import init, { apply_grayscale_wasm, apply_sharpen_wasm } from './pkg/wasm_image_proc.js'; | |
| async function start() { | |
| await init(); | |
| const upload = document.getElementById('upload'); | |
| const canvases = ['canvasJSGray', 'canvasWasmGray', 'canvasJSSharp', 'canvasWasmSharp'].map(id => document.getElementById(id)); | |
| const ctxs = canvases.map(c => c.getContext('2d')); | |
| upload.onchange = (e) => { | |
| const img = new Image(); | |
| img.onload = () => { | |
| canvases.forEach(c => { c.width = img.width; c.height = img.height; }); | |
| ctxs.forEach(ctx => ctx.drawImage(img, 0, 0)); | |
| }; | |
| img.src = URL.createObjectURL(e.target.files[0]); | |
| }; | |
| // RACE 1: Light Task | |
| document.getElementById('btnGrayscale').onclick = () => { | |
| const w = canvases[0].width; const h = canvases[0].height; | |
| // JS Grayscale | |
| const imgJS = ctxs[0].getImageData(0,0,w,h); | |
| const t0 = performance.now(); | |
| for(let i=0; i<imgJS.data.length; i+=4) { | |
| const avg = (imgJS.data[i] + imgJS.data[i+1] + imgJS.data[i+2])/3; | |
| imgJS.data[i] = imgJS.data[i+1] = imgJS.data[i+2] = avg; | |
| } | |
| document.getElementById('jsTimeGray').innerText = `JS: ${(performance.now()-t0).toFixed(2)}ms`; | |
| ctxs[0].putImageData(imgJS, 0, 0); | |
| // Wasm Grayscale | |
| const imgWasm = ctxs[1].getImageData(0,0,w,h); | |
| const t1 = performance.now(); | |
| apply_grayscale_wasm(imgWasm.data); | |
| document.getElementById('wasmTimeGray').innerText = `Wasm: ${(performance.now()-t1).toFixed(2)}ms`; | |
| ctxs[1].putImageData(imgWasm, 0, 0); | |
| }; | |
| // RACE 2: Heavy Task | |
| document.getElementById('btnSharpen').onclick = () => { | |
| const w = canvases[2].width; const h = canvases[2].height; | |
| // JS Sharpen | |
| const imgJS = ctxs[2].getImageData(0,0,w,h); | |
| const copy = new Uint8Array(imgJS.data); | |
| const t0 = performance.now(); | |
| for (let y = 1; y < h - 1; y++) { | |
| for (let x = 1; x < w - 1; x++) { | |
| const i = (y * w + x) * 4; | |
| for (let c = 0; c < 3; c++) { | |
| const center = i + c; | |
| const val = 5 * copy[center] - copy[center - (w*4)] - copy[center + (w*4)] - copy[center - 4] - copy[center + 4]; | |
| imgJS.data[center] = Math.min(Math.max(val, 0), 255); | |
| } | |
| } | |
| } | |
| document.getElementById('jsTimeSharp').innerText = `JS: ${(performance.now()-t0).toFixed(2)}ms`; | |
| ctxs[2].putImageData(imgJS, 0, 0); | |
| // Wasm Sharpen | |
| const imgWasm = ctxs[3].getImageData(0,0,w,h); | |
| const t1 = performance.now(); | |
| apply_sharpen_wasm(imgWasm.data, w, h); | |
| document.getElementById('wasmTimeSharp').innerText = `Wasm: ${(performance.now()-t1).toFixed(2)}ms`; | |
| ctxs[3].putImageData(imgWasm, 0, 0); | |
| }; | |
| } | |
| start(); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment