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>
|
||||
|
||||
<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.
|
||||
</p>
|
||||
|
||||
<section class="grid md:grid-cols-5 gap-4">
|
||||
<div class="flex flex-col gap-2 md:col-span-2">
|
||||
<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 class="flex flex-col gap-2">
|
||||
<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">
|
||||
<option value="foreground">foreground</option>
|
||||
<option value="mask">mask</option>
|
||||
<option value="background">background</option>
|
||||
</select>
|
||||
<input value="foreground (fixed)" disabled class="border border-neutral-300 rounded-lg px-3 py-2 bg-neutral-100 text-neutral-500" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<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">
|
||||
<option value="image/png">image/png</option>
|
||||
<option value="image/webp">image/webp</option>
|
||||
<option value="image/jpeg">image/jpeg</option>
|
||||
</select>
|
||||
<input value="image/png (fixed)" disabled class="border border-neutral-300 rounded-lg px-3 py-2 bg-neutral-100 text-neutral-500" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-xs text-neutral-400 uppercase tracking-wider">Quality (0-1)</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" />
|
||||
<label class="text-xs text-neutral-400 uppercase tracking-wider">Max Side (px)</label>
|
||||
<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>
|
||||
</section>
|
||||
|
||||
@@ -88,9 +80,7 @@
|
||||
<script type="module">
|
||||
const el = {
|
||||
files: document.getElementById("inp-files"),
|
||||
type: document.getElementById("sel-type"),
|
||||
format: document.getElementById("sel-format"),
|
||||
quality: document.getElementById("inp-quality"),
|
||||
maxSide: document.getElementById("inp-max-side"),
|
||||
run: document.getElementById("btn-run"),
|
||||
clear: document.getElementById("btn-clear"),
|
||||
clearLog: document.getElementById("btn-clear-log"),
|
||||
@@ -127,13 +117,7 @@
|
||||
|
||||
const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
|
||||
|
||||
const toDownloadExt = (mime) => {
|
||||
if (mime === "image/webp") return "webp";
|
||||
if (mime === "image/jpeg") return "jpg";
|
||||
return "png";
|
||||
};
|
||||
|
||||
const makeCard = ({ name, originalUrl, outputUrl, outputMime }, idx) => {
|
||||
const makeCard = ({ name, originalUrl, outputUrl }, idx) => {
|
||||
const card = document.createElement("article");
|
||||
card.className = "border border-neutral-200 rounded-xl p-4 flex flex-col gap-3";
|
||||
|
||||
@@ -162,7 +146,7 @@
|
||||
|
||||
const rightLabel = document.createElement("p");
|
||||
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");
|
||||
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);
|
||||
|
||||
right.append(rightLabel, checker);
|
||||
|
||||
wrap.append(left, right);
|
||||
|
||||
const actions = document.createElement("div");
|
||||
@@ -181,7 +164,7 @@
|
||||
|
||||
const dl = document.createElement("a");
|
||||
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.textContent = "download output";
|
||||
|
||||
@@ -250,6 +233,42 @@
|
||||
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.clearLog.addEventListener("click", () => { el.log.textContent = ""; });
|
||||
|
||||
@@ -278,32 +297,37 @@
|
||||
setStatus("loading @imgly/background-removal...");
|
||||
const removeBackground = await loadLib();
|
||||
|
||||
const outputType = el.type.value;
|
||||
const outputFormat = el.format.value;
|
||||
const quality = clamp(Number.parseFloat(el.quality.value || "0.9") || 0.9, 0, 1);
|
||||
appendLog("config:", { outputType, outputFormat, quality });
|
||||
const maxSide = clamp(Number.parseInt(el.maxSide.value || "1024", 10) || 1024, 256, 4096);
|
||||
appendLog("config:", { outputType: "foreground", outputFormat: "image/png", maxSide });
|
||||
|
||||
for (let i = 0; i < files.length; 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 = {
|
||||
debug: true,
|
||||
debug: false,
|
||||
output: {
|
||||
type: outputType,
|
||||
format: outputFormat,
|
||||
quality
|
||||
type: "foreground",
|
||||
format: "image/png",
|
||||
quality: 1
|
||||
},
|
||||
progress: (key, current, total) => {
|
||||
if (!total) return;
|
||||
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 outBlob = await removeBackground(file, config);
|
||||
const outBlob = await removeBackground(pngFile, config);
|
||||
const ms = Math.round(performance.now() - started);
|
||||
appendLog("done:", file.name, `in ${ms}ms`, `blob=${outBlob.type} ${outBlob.size} bytes`);
|
||||
|
||||
@@ -315,8 +339,7 @@
|
||||
const card = makeCard({
|
||||
name: file.name,
|
||||
originalUrl,
|
||||
outputUrl,
|
||||
outputMime: outBlob.type || outputFormat
|
||||
outputUrl
|
||||
}, i);
|
||||
|
||||
el.results.appendChild(card);
|
||||
@@ -334,8 +357,16 @@
|
||||
});
|
||||
|
||||
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");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user