Download index.html from chenjindu/browser-remove-background: direct link, hf CLI and curl.
- Browser
- Download file 5.33 kB
-
https://huggingface.co/spaces/chenjindu/browser-remove-background/resolve/main/index.html
- Command line
-
hf download hf://spaces/chenjindu/browser-remove-background/index.html
-
curl -L -o index.html https://huggingface.co/spaces/chenjindu/browser-remove-background/resolve/main/index.html
5.33 kB
| <html lang=en><meta charset=utf-8><title>Browser Remove Background — WASM vs WebGPU probe</title> | |
| <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><text y='14' font-size='14'>%E2%9C%82%EF%B8%8F</text></svg>"> | |
| <script src="./ort/ort.min.js"></script> | |
| <style> | |
| body{font:15px/1.5 system-ui;margin:2rem;max-width:900px} | |
| canvas,img{max-width:100%;border:1px solid #ccc} | |
| pre{background:#f4f4f4;padding:.6rem;white-space:pre-wrap} | |
| button{padding:.5rem 1rem;border:1px solid #999;border-radius:3px;background:#fff;cursor:pointer;font:inherit} | |
| button:hover{background:#f4f4f4} | |
| .filebtn{display:inline-block;padding:.5rem 1rem;border:1px solid #999;border-radius:3px;cursor:pointer} | |
| .filebtn:hover{background:#f4f4f4} | |
| a{color:#0a6} | |
| </style> | |
| <h2>Remove Background in the Browser — WASM vs WebGPU probe</h2> | |
| <p>ISNet (int8 weights, 44 MB) running on onnxruntime-web 1.22. The image is decoded, segmented and composited on your own device — nothing is uploaded.</p> | |
| <p>Pick a photo, then run it with each execution provider and compare the timings. Try a portrait, a product shot and something with a busy background. The first inference is always slower (warm-up and shader compilation) — read the second one.</p> | |
| <p> | |
| <label class="filebtn">Choose image<input type=file id=f accept="image/*" hidden></label> | |
| <span id=fname style="color:#666;margin-right:1rem">No file chosen</span> | |
| <button id=w>Run with WASM</button> | |
| <button id=g>Run with WebGPU</button> | |
| </p> | |
| <pre id=log>Waiting…</pre> | |
| <div style="display:flex;gap:1rem"><img id=img style="width:45%"><canvas id=out style="width:45%;background:repeating-conic-gradient(#ccc 0 25%,#eee 0 50%) 0 0/16px 16px"></canvas></div> | |
| <canvas id=c hidden></canvas> | |
| <p style="margin-top:2rem;color:#666">Production version: <a href="https://is.tools/remove-background">is.tools/remove-background</a> · Source and benchmarks: <a href="https://github.com/chenjindu/browser-remove-background">GitHub</a></p> | |
| <script> | |
| const log = (s) => { document.getElementById('log').textContent += '\n' + s; }; | |
| let session = null, sessionEp = ''; | |
| document.getElementById('f').onchange = (e) => { | |
| document.getElementById('fname').textContent = (e.target.files[0] && e.target.files[0].name) || 'No file chosen'; | |
| }; | |
| async function loadModel() { | |
| const t0 = performance.now(); | |
| const parts = await Promise.all(['isnet-w8.part00','isnet-w8.part01','isnet-w8.part02'].map(p => fetch('./model/' + p).then(r => r.arrayBuffer()))); | |
| const total = parts.reduce((n, p) => n + p.byteLength, 0); | |
| const buf = new Uint8Array(total); let off = 0; for (const p of parts) { buf.set(new Uint8Array(p), off); off += p.byteLength; } | |
| log(`Model fetched (${Math.round(total/1048576)} MB): ${Math.round(performance.now()-t0)} ms`); | |
| return buf; | |
| } | |
| async function run(ep) { | |
| const file = document.getElementById('f').files[0]; if (!file) { log('Pick an image first.'); return; } | |
| document.getElementById('log').textContent = `EP=${ep} · CPU cores ${navigator.hardwareConcurrency} · UA ${navigator.userAgent.slice(0,80)}`; | |
| ort.env.wasm.wasmPaths = new URL('./ort/', document.baseURI).href; | |
| ort.env.wasm.numThreads = 1; | |
| try { | |
| if (!session || sessionEp !== ep) { | |
| const buf = await loadModel(); | |
| const t = performance.now(); | |
| session = await ort.InferenceSession.create(buf, { executionProviders: [ep], graphOptimizationLevel: 'all' }); | |
| sessionEp = ep; | |
| log(`Session created (incl. compilation): ${Math.round(performance.now()-t)} ms`); | |
| } | |
| const img = document.getElementById('img'); img.src = URL.createObjectURL(file); await img.decode(); | |
| const c = document.getElementById('c'); c.width = 1024; c.height = 1024; const ctx = c.getContext('2d', { willReadFrequently: true }); | |
| ctx.drawImage(img, 0, 0, 1024, 1024); | |
| const d = ctx.getImageData(0, 0, 1024, 1024).data; | |
| const N = 1024 * 1024, x = new Float32Array(3 * N); | |
| for (let i = 0; i < N; i++) { x[i] = d[i*4]/255 - 0.5; x[N+i] = d[i*4+1]/255 - 0.5; x[2*N+i] = d[i*4+2]/255 - 0.5; } | |
| for (let k = 1; k <= 2; k++) { | |
| const t = performance.now(); | |
| const out = await session.run({ input_image: new ort.Tensor('float32', x, [1, 3, 1024, 1024]) }); | |
| log(`Inference #${k}: ${Math.round(performance.now()-t)} ms`); | |
| if (k === 2) { | |
| const m = out.output_image.data; let mn = 1e9, mx = -1e9; for (const v of m) { if (v < mn) mn = v; if (v > mx) mx = v; } | |
| const mc = document.createElement('canvas'); mc.width = mc.height = 1024; const mctx = mc.getContext('2d'); | |
| const id = mctx.createImageData(1024, 1024); | |
| for (let i = 0; i < N; i++) { const a = Math.round(((m[i]-mn)/(mx-mn+1e-8))*255); id.data[i*4]=id.data[i*4+1]=id.data[i*4+2]=255; id.data[i*4+3]=a; } | |
| mctx.putImageData(id, 0, 0); | |
| const o = document.getElementById('out'); o.width = img.naturalWidth; o.height = img.naturalHeight; const octx = o.getContext('2d'); | |
| octx.drawImage(img, 0, 0); octx.globalCompositeOperation = 'destination-in'; octx.drawImage(mc, 0, 0, o.width, o.height); | |
| } | |
| } | |
| log('Done.'); | |
| } catch (e) { log('Failed: ' + (e && e.message || e)); } | |
| } | |
| document.getElementById('w').onclick = () => run('wasm'); | |
| document.getElementById('g').onclick = () => run('webgpu'); | |
| </script> | |