Spaces:
Running
Running
Download index.html from SkHugger/icon-vectorizer: direct link, hf CLI and curl.
- Browser
- Download file 9.59 kB
-
https://huggingface.co/spaces/SkHugger/icon-vectorizer/resolve/main/index.html
- Command line
-
hf download hf://spaces/SkHugger/icon-vectorizer/index.html
-
curl -L -o index.html https://huggingface.co/spaces/SkHugger/icon-vectorizer/resolve/main/index.html
9.59 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>Icon Vectorizer — Accurate Centerline</title> | |
| <script src="https://cdn.jsdelivr.net/pyodide/v0.26.4/full/pyodide.js"></script> | |
| <style> | |
| * { box-sizing: border-box; } | |
| body { font-family: "Segoe UI", system-ui, sans-serif; background:#111417; color:#e8e8e8; margin:0; padding:20px; } | |
| h1 { font-size:22px; margin:0 0 4px; } | |
| .sub { color:#9aa; margin-bottom:16px; font-size:13px; } | |
| .wrap { display:flex; gap:16px; flex-wrap:wrap; } | |
| .panel { background:#1b2026; border:1px solid #2c343d; border-radius:10px; padding:16px; flex:1 1 340px; min-width:300px; } | |
| label { display:block; font-size:13px; margin:10px 0 4px; color:#bcd; } | |
| input[type=file] { width:100%; } | |
| input[type=range] { width:100%; } | |
| select, input[type=number] { background:#0f1317; color:#eee; border:1px solid #2c343d; border-radius:6px; padding:6px 8px; } | |
| button { background:#1f7a3a; color:#fff; border:0; border-radius:8px; padding:12px 18px; font-size:15px; cursor:pointer; width:100%; margin-top:14px; } | |
| button:disabled { background:#3a424b; cursor:wait; } | |
| button.secondary { background:#2c343d; width:auto; margin-top:8px; margin-right:8px; padding:8px 14px; font-size:13px; } | |
| #status { font-size:13px; color:#fc6; min-height:20px; margin-top:10px; white-space:pre-wrap; } | |
| #preview { background:repeating-conic-gradient(#ededed 0% 25%, #fff 0% 50%) 0 0/24px 24px; border-radius:8px; padding:12px; text-align:center; min-height:200px; } | |
| #preview svg { max-width:100%; height:auto; } | |
| table { width:100%; border-collapse:collapse; font-size:12px; margin-top:8px; } | |
| th, td { text-align:left; padding:4px 6px; border-bottom:1px solid #2c343d; } | |
| th { color:#9aa; } | |
| #stats { font-size:13px; } | |
| .row { display:flex; gap:8px; align-items:center; } | |
| .row output { min-width:44px; text-align:right; } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>ICON VECTORIZER <span style="color:#9aa;font-weight:normal">— accurate centerline</span></h1> | |
| <div class="sub">Black-outline icon (JPG/PNG) → clean centerline SVG. 100% browser me chalta hai — slow but accurate (~20–60s).</div> | |
| <div class="wrap"> | |
| <div class="panel"> | |
| <label>1. Icon image (JPG/PNG)</label> | |
| <input type="file" id="file" accept=".png,.jpg,.jpeg,.bmp,.webp"> | |
| <label>2. Threshold <span style="color:#777">(default 110)</span></label> | |
| <div class="row"><input type="range" id="threshold" min="0" max="255" value="110"><output id="threshold_v">110</output></div> | |
| <label>Quality <span style="color:#777">(default 3x)</span></label> | |
| <select id="quality"><option>1x</option><option>2x</option><option selected>3x</option><option>4x</option></select> | |
| <label>Smoothing <span style="color:#777">(default Low)</span></label> | |
| <select id="smoothing"><option selected>Low</option><option>Medium</option><option>High</option></select> | |
| <label>Tolerance px <span style="color:#777">(default 0.6)</span></label> | |
| <input type="number" id="tolerance" value="0.6" step="0.1" min="0.1" max="5"> | |
| <label><input type="checkbox" id="reduce"> Reduce Anchors</label> | |
| <button id="convert" disabled>Engine load ho raha hai…</button> | |
| <div id="status">Pyodide + packages load ho rahe hain (pehli dafa 1–2 min lag sakte hain)…</div> | |
| </div> | |
| <div class="panel"> | |
| <label style="margin-top:0">SVG preview</label> | |
| <div id="preview"><span style="color:#777">yahan result nazar aayega</span></div> | |
| <div id="stats"></div> | |
| <div> | |
| <button class="secondary" id="download" disabled>Download SVG</button> | |
| <button class="secondary" id="copy" disabled>Copy SVG</button> | |
| </div> | |
| <table id="layers" style="display:none"> | |
| <thead><tr><th>name</th><th>kind</th><th>anchors</th><th>max dev</th></tr></thead> | |
| <tbody></tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <script> | |
| let pyodide = null; | |
| let lastSvg = ""; | |
| const $ = id => document.getElementById(id); | |
| $("threshold").addEventListener("input", e => { $("threshold_v").textContent = e.target.value; }); | |
| function setStatus(t){ $("status").textContent = t; } | |
| // File ko select hote hi memory me parh lo — taake dobara convert par | |
| // browser ki file-reference expire na ho ("could not be read" error fix). | |
| let fileBytes = null, fileName = "icon"; | |
| $("file").addEventListener("change", async (e) => { | |
| const f = e.target.files[0]; | |
| fileBytes = null; | |
| if(!f) return; | |
| try { | |
| fileBytes = new Uint8Array(await f.arrayBuffer()); | |
| fileName = (f.name || "icon").replace(/\.[^.]+$/, ""); | |
| setStatus("File ready: " + f.name + " — Convert dabao."); | |
| } catch(err){ | |
| setStatus("File parh nahi saki, dobara select karo."); | |
| } | |
| }); | |
| async function init(){ | |
| try { | |
| setStatus("Pyodide load ho raha hai…"); | |
| pyodide = await loadPyodide(); | |
| setStatus("Packages load ho rahe hain: numpy, pillow, opencv, scipy, scikit-image… (thoda waqt lagega)"); | |
| await pyodide.loadPackage(["numpy", "pillow", "opencv-python", "scipy", "scikit-image"]); | |
| setStatus("Tracer engine load ho raha hai…"); | |
| const core = await (await fetch("vectorizer_core.py")).text(); | |
| if(!core || core.length < 1000) throw new Error("vectorizer_core.py download nahi ho saki"); | |
| pyodide.FS.writeFile("/vectorizer_core.py", core); | |
| if(!pyodide.FS.analyzePath("/vectorizer_core.py").exists) throw new Error("file write failed"); | |
| await pyodide.runPythonAsync("import sys; sys.path.insert(0, '/')\nimport vectorizer_core as vc"); | |
| setStatus("Tayyar! Image select karo aur Convert dabao."); | |
| $("convert").disabled = false; | |
| $("convert").textContent = "Convert to SVG"; | |
| } catch(err){ | |
| setStatus("Load error:\n" + err.message); | |
| } | |
| } | |
| const PY_RUN = [ | |
| "import gc; gc.collect()", | |
| "from PIL import Image as _PIL", | |
| "_im = _PIL.open('/input.png'); _w, _h = _im.size", | |
| "_Q = int(Q); _cap = 2048", | |
| "NOTE = ''", | |
| "if max(_w, _h) * _Q > _cap:", | |
| " _s = _cap / max(_w, _h)", | |
| " _im = _im.resize((max(1, int(_w * _s)), max(1, int(_h * _s))), _PIL.LANCZOS)", | |
| " _im.save('/input.png')", | |
| " NOTE = ' (note: bari image auto-resize hui taake phone ki memory me aaye)'", | |
| "import vectorizer_core as vc", | |
| "cfg = vc.VectorizerConfig(", | |
| " threshold=int(TH), upscale=_Q, smoothing=SM, tolerance=float(TOL),", | |
| " reduce_anchors=bool(RA), use_clip=False, use_sam=False)", | |
| "out, res = vc.convert_file('/input.png', '/output.svg', cfg)", | |
| "els = [{'name': e['name'], 'kind': e['kind'],", | |
| " 'anchors': int(e['anchors']), 'max_dev': float(e['max_dev'])}", | |
| " for e in res['elements']]", | |
| "st = res['stats']", | |
| "stats = {'paths': int(st['paths']), 'anchors': int(st['anchors']),", | |
| " 'stroke_w': float(st['stroke_w']), 'max_dev': float(st['max_dev']),", | |
| " 'mean_dev': float(st['mean_dev']), 'elapsed': float(st['elapsed'])}", | |
| "RESULT = {'svg': res['svg'], 'stats': stats, 'elements': els, 'note': NOTE}", | |
| "del _im; gc.collect()", | |
| ].join("\n"); | |
| async function convert(){ | |
| if(!fileBytes){ setStatus("Pehle image select karo (dobara select karo agar error aaye)."); return; } | |
| if($("convert").disabled) return; | |
| $("convert").disabled = true; | |
| $("convert").textContent = "Converting… (20–60s)"; | |
| setStatus("Convert ho raha hai… slow but accurate, intezar karo."); | |
| try { | |
| pyodide.FS.writeFile("/input.png", fileBytes); | |
| pyodide.globals.set("TH", $("threshold").value); | |
| pyodide.globals.set("Q", $("quality").value.replace("x","")); | |
| pyodide.globals.set("SM", $("smoothing").value); | |
| pyodide.globals.set("TOL", $("tolerance").value); | |
| pyodide.globals.set("RA", $("reduce").checked ? 1 : 0); | |
| await pyodide.runPythonAsync(PY_RUN); | |
| const r = pyodide.globals.get("RESULT").toJs({dict_converter: Object.fromEntries}); | |
| lastSvg = r.svg; | |
| $("preview").innerHTML = lastSvg; | |
| const st = r.stats; | |
| $("stats").innerHTML = "<p><b>" + st.paths + "</b> paths · <b>" + st.anchors + | |
| "</b> anchors · stroke <b>" + st.stroke_w + "px</b><br>max deviation <b>" + | |
| st.max_dev + "px</b> · mean <b>" + st.mean_dev + "px</b> · " + st.elapsed + "s</p>"; | |
| const tb = document.querySelector("#layers tbody"); | |
| tb.innerHTML = ""; | |
| for(const e of r.elements){ | |
| const tr = document.createElement("tr"); | |
| tr.innerHTML = "<td>" + e.name + "</td><td>" + e.kind + "</td><td>" + | |
| e.anchors + "</td><td>" + e.max_dev.toFixed(2) + "px</td>"; | |
| tb.appendChild(tr); | |
| } | |
| $("layers").style.display = ""; | |
| $("download").disabled = false; | |
| $("copy").disabled = false; | |
| setStatus("Ho gaya!" + r.note + " Neeche Download SVG dabao."); | |
| } catch(err){ | |
| const msg = err.message || String(err); | |
| if(/allocate|memory/i.test(msg)){ | |
| setStatus("Phone ki memory full ho gayi. Quality ko 2x ya 1x karke dobara try karo."); | |
| } else { | |
| setStatus("Convert error:\n" + msg.slice(0, 600)); | |
| } | |
| } | |
| $("convert").disabled = false; | |
| $("convert").textContent = "Convert to SVG"; | |
| } | |
| $("convert").addEventListener("click", convert); | |
| $("download").addEventListener("click", () => { | |
| const blob = new Blob([lastSvg], {type: "image/svg+xml"}); | |
| const a = document.createElement("a"); | |
| a.href = URL.createObjectURL(blob); | |
| a.download = fileName + ".svg"; | |
| a.click(); | |
| setTimeout(() => URL.revokeObjectURL(a.href), 5000); | |
| }); | |
| $("copy").addEventListener("click", async () => { | |
| try { await navigator.clipboard.writeText(lastSvg); setStatus("SVG copy ho gaya."); } | |
| catch(e){ setStatus("Copy nahi ho saka, code manually select karo."); } | |
| }); | |
| init(); | |
| </script> | |
| </body> | |
| </html> | |