mirror of
https://github.com/vibegif/vibegif.lol.git
synced 2026-04-07 02:12:12 +00:00
Fix: PNG preprocess + faster bg remove
This commit is contained in:
115
test.html
115
test.html
@@ -34,37 +34,29 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<p class="text-neutral-500">
|
<p class="text-neutral-500">
|
||||||
Upload image(s), run <code>@imgly/background-removal</code>, inspect output + logs.
|
Upload image(s), convert to PNG, remove background, inspect output + logs.
|
||||||
First run can take longer due model download.
|
First run can take longer due model download.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<section class="grid md:grid-cols-5 gap-4">
|
<section class="grid md:grid-cols-5 gap-4">
|
||||||
<div class="flex flex-col gap-2 md:col-span-2">
|
<div class="flex flex-col gap-2 md:col-span-2">
|
||||||
<label class="text-xs text-neutral-400 uppercase tracking-wider">Files</label>
|
<label class="text-xs text-neutral-400 uppercase tracking-wider">Files</label>
|
||||||
<input id="inp-files" type="file" multiple class="border border-neutral-300 rounded-lg px-3 py-2" />
|
<input id="inp-files" type="file" multiple accept="image/*" class="border border-neutral-300 rounded-lg px-3 py-2" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<label class="text-xs text-neutral-400 uppercase tracking-wider">Output Type</label>
|
<label class="text-xs text-neutral-400 uppercase tracking-wider">Output Type</label>
|
||||||
<select id="sel-type" class="border border-neutral-300 rounded-lg px-3 py-2 bg-white">
|
<input value="foreground (fixed)" disabled class="border border-neutral-300 rounded-lg px-3 py-2 bg-neutral-100 text-neutral-500" />
|
||||||
<option value="foreground">foreground</option>
|
|
||||||
<option value="mask">mask</option>
|
|
||||||
<option value="background">background</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<label class="text-xs text-neutral-400 uppercase tracking-wider">Format</label>
|
<label class="text-xs text-neutral-400 uppercase tracking-wider">Format</label>
|
||||||
<select id="sel-format" class="border border-neutral-300 rounded-lg px-3 py-2 bg-white">
|
<input value="image/png (fixed)" disabled class="border border-neutral-300 rounded-lg px-3 py-2 bg-neutral-100 text-neutral-500" />
|
||||||
<option value="image/png">image/png</option>
|
|
||||||
<option value="image/webp">image/webp</option>
|
|
||||||
<option value="image/jpeg">image/jpeg</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<label class="text-xs text-neutral-400 uppercase tracking-wider">Quality (0-1)</label>
|
<label class="text-xs text-neutral-400 uppercase tracking-wider">Max Side (px)</label>
|
||||||
<input id="inp-quality" type="number" min="0" max="1" step="0.05" value="0.9" class="border border-neutral-300 rounded-lg px-3 py-2" />
|
<input id="inp-max-side" type="number" min="256" max="4096" step="32" value="1024" class="border border-neutral-300 rounded-lg px-3 py-2" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -88,9 +80,7 @@
|
|||||||
<script type="module">
|
<script type="module">
|
||||||
const el = {
|
const el = {
|
||||||
files: document.getElementById("inp-files"),
|
files: document.getElementById("inp-files"),
|
||||||
type: document.getElementById("sel-type"),
|
maxSide: document.getElementById("inp-max-side"),
|
||||||
format: document.getElementById("sel-format"),
|
|
||||||
quality: document.getElementById("inp-quality"),
|
|
||||||
run: document.getElementById("btn-run"),
|
run: document.getElementById("btn-run"),
|
||||||
clear: document.getElementById("btn-clear"),
|
clear: document.getElementById("btn-clear"),
|
||||||
clearLog: document.getElementById("btn-clear-log"),
|
clearLog: document.getElementById("btn-clear-log"),
|
||||||
@@ -127,13 +117,7 @@
|
|||||||
|
|
||||||
const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
|
const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
|
||||||
|
|
||||||
const toDownloadExt = (mime) => {
|
const makeCard = ({ name, originalUrl, outputUrl }, idx) => {
|
||||||
if (mime === "image/webp") return "webp";
|
|
||||||
if (mime === "image/jpeg") return "jpg";
|
|
||||||
return "png";
|
|
||||||
};
|
|
||||||
|
|
||||||
const makeCard = ({ name, originalUrl, outputUrl, outputMime }, idx) => {
|
|
||||||
const card = document.createElement("article");
|
const card = document.createElement("article");
|
||||||
card.className = "border border-neutral-200 rounded-xl p-4 flex flex-col gap-3";
|
card.className = "border border-neutral-200 rounded-xl p-4 flex flex-col gap-3";
|
||||||
|
|
||||||
@@ -162,7 +146,7 @@
|
|||||||
|
|
||||||
const rightLabel = document.createElement("p");
|
const rightLabel = document.createElement("p");
|
||||||
rightLabel.className = "text-xs text-neutral-400 uppercase tracking-wider";
|
rightLabel.className = "text-xs text-neutral-400 uppercase tracking-wider";
|
||||||
rightLabel.textContent = "background removed (checkerboard)";
|
rightLabel.textContent = "background removed (transparent png)";
|
||||||
|
|
||||||
const checker = document.createElement("div");
|
const checker = document.createElement("div");
|
||||||
checker.className = "checker rounded-lg border border-neutral-200 h-56 flex items-center justify-center overflow-hidden";
|
checker.className = "checker rounded-lg border border-neutral-200 h-56 flex items-center justify-center overflow-hidden";
|
||||||
@@ -173,7 +157,6 @@
|
|||||||
checker.appendChild(rightImg);
|
checker.appendChild(rightImg);
|
||||||
|
|
||||||
right.append(rightLabel, checker);
|
right.append(rightLabel, checker);
|
||||||
|
|
||||||
wrap.append(left, right);
|
wrap.append(left, right);
|
||||||
|
|
||||||
const actions = document.createElement("div");
|
const actions = document.createElement("div");
|
||||||
@@ -181,7 +164,7 @@
|
|||||||
|
|
||||||
const dl = document.createElement("a");
|
const dl = document.createElement("a");
|
||||||
dl.href = outputUrl;
|
dl.href = outputUrl;
|
||||||
dl.download = `${name.replace(/\.[^.]+$/, "")}-nobg.${toDownloadExt(outputMime)}`;
|
dl.download = `${name.replace(/\.[^.]+$/, "")}-nobg.png`;
|
||||||
dl.className = "bg-neutral-800 text-white rounded-lg px-4 py-2 hover:bg-neutral-700 transition no-underline";
|
dl.className = "bg-neutral-800 text-white rounded-lg px-4 py-2 hover:bg-neutral-700 transition no-underline";
|
||||||
dl.textContent = "download output";
|
dl.textContent = "download output";
|
||||||
|
|
||||||
@@ -250,6 +233,42 @@
|
|||||||
try { return JSON.stringify(err); } catch { return String(err); }
|
try { return JSON.stringify(err); } catch { return String(err); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fileToPng(file, maxSide) {
|
||||||
|
const srcUrl = URL.createObjectURL(file);
|
||||||
|
try {
|
||||||
|
const img = new Image();
|
||||||
|
img.decoding = "async";
|
||||||
|
img.src = srcUrl;
|
||||||
|
await img.decode();
|
||||||
|
|
||||||
|
let w = img.naturalWidth || img.width;
|
||||||
|
let h = img.naturalHeight || img.height;
|
||||||
|
const longest = Math.max(w, h);
|
||||||
|
const scale = longest > maxSide ? (maxSide / longest) : 1;
|
||||||
|
|
||||||
|
w = Math.max(1, Math.round(w * scale));
|
||||||
|
h = Math.max(1, Math.round(h * scale));
|
||||||
|
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = w;
|
||||||
|
canvas.height = h;
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
if (!ctx) throw new Error("Could not create 2D canvas context");
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, w, h);
|
||||||
|
ctx.drawImage(img, 0, 0, w, h);
|
||||||
|
|
||||||
|
const blob = await new Promise((resolve, reject) => {
|
||||||
|
canvas.toBlob((b) => b ? resolve(b) : reject(new Error("Failed converting to PNG")), "image/png", 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
const pngName = `${file.name.replace(/\.[^.]+$/, "")}.png`;
|
||||||
|
return new File([blob], pngName, { type: "image/png" });
|
||||||
|
} finally {
|
||||||
|
URL.revokeObjectURL(srcUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
el.clear.addEventListener("click", clearResults);
|
el.clear.addEventListener("click", clearResults);
|
||||||
el.clearLog.addEventListener("click", () => { el.log.textContent = ""; });
|
el.clearLog.addEventListener("click", () => { el.log.textContent = ""; });
|
||||||
|
|
||||||
@@ -278,32 +297,37 @@
|
|||||||
setStatus("loading @imgly/background-removal...");
|
setStatus("loading @imgly/background-removal...");
|
||||||
const removeBackground = await loadLib();
|
const removeBackground = await loadLib();
|
||||||
|
|
||||||
const outputType = el.type.value;
|
const maxSide = clamp(Number.parseInt(el.maxSide.value || "1024", 10) || 1024, 256, 4096);
|
||||||
const outputFormat = el.format.value;
|
appendLog("config:", { outputType: "foreground", outputFormat: "image/png", maxSide });
|
||||||
const quality = clamp(Number.parseFloat(el.quality.value || "0.9") || 0.9, 0, 1);
|
|
||||||
appendLog("config:", { outputType, outputFormat, quality });
|
|
||||||
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
const file = files[i];
|
const file = files[i];
|
||||||
setStatus(`processing ${i + 1}/${files.length}: ${file.name} ...`);
|
setStatus(`preparing ${i + 1}/${files.length}: ${file.name} ...`);
|
||||||
|
|
||||||
|
appendLog("converting to PNG:", file.name);
|
||||||
|
const startedPrep = performance.now();
|
||||||
|
const pngFile = await fileToPng(file, maxSide);
|
||||||
|
const prepMs = Math.round(performance.now() - startedPrep);
|
||||||
|
appendLog("png ready:", pngFile.name, `${pngFile.size} bytes`, `in ${prepMs}ms`);
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
debug: true,
|
debug: false,
|
||||||
output: {
|
output: {
|
||||||
type: outputType,
|
type: "foreground",
|
||||||
format: outputFormat,
|
format: "image/png",
|
||||||
quality
|
quality: 1
|
||||||
},
|
},
|
||||||
progress: (key, current, total) => {
|
progress: (key, current, total) => {
|
||||||
if (!total) return;
|
if (!total) return;
|
||||||
const pct = Math.round((current / total) * 100);
|
const pct = Math.round((current / total) * 100);
|
||||||
el.status.textContent = `downloading model assets (${key}) ${pct}% — file ${i + 1}/${files.length}`;
|
el.status.textContent = `loading model assets (${key}) ${pct}% — file ${i + 1}/${files.length}`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
appendLog("calling removeBackground for:", file.name);
|
appendLog("calling removeBackground for:", pngFile.name);
|
||||||
|
setStatus(`processing ${i + 1}/${files.length}: ${file.name} ...`);
|
||||||
const started = performance.now();
|
const started = performance.now();
|
||||||
const outBlob = await removeBackground(file, config);
|
const outBlob = await removeBackground(pngFile, config);
|
||||||
const ms = Math.round(performance.now() - started);
|
const ms = Math.round(performance.now() - started);
|
||||||
appendLog("done:", file.name, `in ${ms}ms`, `blob=${outBlob.type} ${outBlob.size} bytes`);
|
appendLog("done:", file.name, `in ${ms}ms`, `blob=${outBlob.type} ${outBlob.size} bytes`);
|
||||||
|
|
||||||
@@ -315,8 +339,7 @@
|
|||||||
const card = makeCard({
|
const card = makeCard({
|
||||||
name: file.name,
|
name: file.name,
|
||||||
originalUrl,
|
originalUrl,
|
||||||
outputUrl,
|
outputUrl
|
||||||
outputMime: outBlob.type || outputFormat
|
|
||||||
}, i);
|
}, i);
|
||||||
|
|
||||||
el.results.appendChild(card);
|
el.results.appendChild(card);
|
||||||
@@ -334,8 +357,16 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener("beforeunload", revokeAllUrls);
|
window.addEventListener("beforeunload", revokeAllUrls);
|
||||||
|
|
||||||
|
const idleWarmup = () => loadLib().then(
|
||||||
|
() => appendLog("warmup: library ready"),
|
||||||
|
(err) => appendLog("warmup failed:", formatErr(err))
|
||||||
|
);
|
||||||
|
|
||||||
|
if ("requestIdleCallback" in window) requestIdleCallback(idleWarmup, { timeout: 2500 });
|
||||||
|
else setTimeout(idleWarmup, 800);
|
||||||
|
|
||||||
appendLog("test page booted");
|
appendLog("test page booted");
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user