Files
vibegif.lol/test.html

341 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vibegif.lol — background-removal test</title>
<style>
@font-face {
font-family: "Stain";
src: url("https://cdn.jsdelivr.net/gh/multipleof4/stain.otf@master/dist/Stain.otf") format("opentype");
font-weight: normal;
font-style: normal;
}
body, input, select, button, a, label, code, pre {
font-family: "Stain", sans-serif;
}
.checker {
background-image:
linear-gradient(45deg, #eee 25%, transparent 25%),
linear-gradient(-45deg, #eee 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #eee 75%),
linear-gradient(-45deg, transparent 75%, #eee 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0;
}
</style>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-white text-neutral-800 min-h-screen">
<main class="max-w-6xl mx-auto px-4 py-8 flex flex-col gap-6">
<header class="flex items-center justify-between">
<h1 class="text-4xl tracking-tight">bg test<span class="text-neutral-400">.lol</span></h1>
<a href="/" class="text-sm underline text-neutral-500 hover:text-neutral-800">back to vibegif</a>
</header>
<p class="text-neutral-500">
Upload image(s), run <code>@imgly/background-removal</code>, 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" accept="image/*" multiple 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>
</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>
</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" />
</div>
</section>
<section class="flex flex-wrap items-center gap-3">
<button id="btn-run" class="bg-neutral-800 text-white rounded-lg px-5 py-3 hover:bg-neutral-700 transition">remove background</button>
<button id="btn-clear" class="border border-neutral-300 rounded-lg px-5 py-3 hover:bg-neutral-100 transition">clear</button>
<p id="status" class="text-sm text-neutral-500"></p>
</section>
<section class="border border-neutral-200 rounded-xl p-3">
<div class="flex items-center justify-between mb-2">
<p class="text-xs text-neutral-400 uppercase tracking-wider">debug log</p>
<button id="btn-clear-log" class="text-xs underline text-neutral-500 hover:text-neutral-800">clear log</button>
</div>
<pre id="log" class="text-xs whitespace-pre-wrap break-words max-h-56 overflow-auto bg-neutral-50 rounded-lg p-3"></pre>
</section>
<section id="results" class="grid md:grid-cols-2 gap-4"></section>
</main>
<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"),
run: document.getElementById("btn-run"),
clear: document.getElementById("btn-clear"),
clearLog: document.getElementById("btn-clear-log"),
status: document.getElementById("status"),
log: document.getElementById("log"),
results: document.getElementById("results")
};
const objectUrls = new Set();
let removeBackgroundFn = null;
let loadingLibPromise = null;
const now = () => new Date().toLocaleTimeString();
const appendLog = (...args) => {
const line = `[${now()}] ${args.map((v) => {
if (v instanceof Error) return `${v.name}: ${v.message}`;
if (typeof v === "object") {
try { return JSON.stringify(v); } catch { return String(v); }
}
return String(v);
}).join(" ")}`;
console.log("[bg-test]", ...args);
if (el.log) {
el.log.textContent += (el.log.textContent ? "\n" : "") + line;
el.log.scrollTop = el.log.scrollHeight;
}
};
const setStatus = (msg) => {
el.status.textContent = msg || "";
appendLog("STATUS:", msg || "");
};
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 card = document.createElement("article");
card.className = "border border-neutral-200 rounded-xl p-4 flex flex-col gap-3";
const title = document.createElement("h2");
title.className = "text-lg";
title.textContent = `${idx + 1}. ${name}`;
const wrap = document.createElement("div");
wrap.className = "grid grid-cols-2 gap-3";
const left = document.createElement("div");
left.className = "flex flex-col gap-2";
const leftLabel = document.createElement("p");
leftLabel.className = "text-xs text-neutral-400 uppercase tracking-wider";
leftLabel.textContent = "original";
const leftImg = document.createElement("img");
leftImg.src = originalUrl;
leftImg.className = "w-full h-56 object-contain rounded-lg border border-neutral-200 bg-white";
left.append(leftLabel, leftImg);
const right = document.createElement("div");
right.className = "flex flex-col gap-2";
const rightLabel = document.createElement("p");
rightLabel.className = "text-xs text-neutral-400 uppercase tracking-wider";
rightLabel.textContent = "background removed (checkerboard)";
const checker = document.createElement("div");
checker.className = "checker rounded-lg border border-neutral-200 h-56 flex items-center justify-center overflow-hidden";
const rightImg = document.createElement("img");
rightImg.src = outputUrl;
rightImg.className = "max-w-full max-h-full object-contain";
checker.appendChild(rightImg);
right.append(rightLabel, checker);
wrap.append(left, right);
const actions = document.createElement("div");
actions.className = "flex items-center gap-3";
const dl = document.createElement("a");
dl.href = outputUrl;
dl.download = `${name.replace(/\.[^.]+$/, "")}-nobg.${toDownloadExt(outputMime)}`;
dl.className = "bg-neutral-800 text-white rounded-lg px-4 py-2 hover:bg-neutral-700 transition no-underline";
dl.textContent = "download output";
actions.append(dl);
card.append(title, wrap, actions);
return card;
};
const revokeAllUrls = () => {
for (const url of objectUrls) URL.revokeObjectURL(url);
objectUrls.clear();
appendLog("revoked object urls");
};
const clearResults = () => {
revokeAllUrls();
el.results.innerHTML = "";
el.status.textContent = "";
appendLog("cleared results");
};
const setBusy = (busy) => {
el.run.disabled = busy;
el.run.style.opacity = busy ? "0.5" : "1";
el.run.style.cursor = busy ? "not-allowed" : "pointer";
};
async function loadLib() {
if (removeBackgroundFn) return removeBackgroundFn;
if (loadingLibPromise) return loadingLibPromise;
const urls = [
"https://esm.sh/@imgly/background-removal@1.7.0?bundle",
"https://cdn.jsdelivr.net/npm/@imgly/background-removal@1.7.0/+esm"
];
loadingLibPromise = (async () => {
let lastErr = null;
for (const url of urls) {
try {
appendLog("trying import:", url);
const mod = await import(url);
const fn = mod?.default || mod?.removeBackground || mod?.imglyRemoveBackground;
if (typeof fn !== "function") {
throw new Error(`No usable remove function export. keys=${Object.keys(mod || {}).join(",")}`);
}
removeBackgroundFn = fn;
appendLog("library loaded from:", url);
return removeBackgroundFn;
} catch (err) {
lastErr = err;
appendLog("import failed:", url, err);
}
}
throw lastErr || new Error("Failed to load @imgly/background-removal from all CDNs.");
})();
return loadingLibPromise;
}
function formatErr(err) {
if (!err) return "Unknown error";
if (err instanceof Error) return `${err.name}: ${err.message}`;
try { return JSON.stringify(err); } catch { return String(err); }
}
el.clear.addEventListener("click", clearResults);
el.clearLog.addEventListener("click", () => { el.log.textContent = ""; });
window.addEventListener("error", (e) => {
appendLog("window.error:", e.message || e.error || e);
});
window.addEventListener("unhandledrejection", (e) => {
appendLog("unhandledrejection:", formatErr(e.reason));
});
el.run.addEventListener("click", async () => {
const files = [...(el.files.files || [])];
appendLog("run clicked. files:", files.map(f => `${f.name} (${f.size} bytes)`));
if (!files.length) {
setStatus("pick at least one image first.");
el.files.focus();
return;
}
clearResults();
setBusy(true);
try {
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 });
for (let i = 0; i < files.length; i++) {
const file = files[i];
setStatus(`processing ${i + 1}/${files.length}: ${file.name} ...`);
const config = {
debug: true,
output: {
type: outputType,
format: outputFormat,
quality
},
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}`;
}
};
appendLog("calling removeBackground for:", file.name);
const started = performance.now();
const outBlob = await removeBackground(file, config);
const ms = Math.round(performance.now() - started);
appendLog("done:", file.name, `in ${ms}ms`, `blob=${outBlob.type} ${outBlob.size} bytes`);
const originalUrl = URL.createObjectURL(file);
const outputUrl = URL.createObjectURL(outBlob);
objectUrls.add(originalUrl);
objectUrls.add(outputUrl);
const card = makeCard({
name: file.name,
originalUrl,
outputUrl,
outputMime: outBlob.type || outputFormat
}, i);
el.results.appendChild(card);
}
setStatus(`done ✓ processed ${files.length} image${files.length > 1 ? "s" : ""}.`);
} catch (err) {
const msg = formatErr(err);
console.error(err);
setStatus(`error: ${msg}`);
appendLog("FATAL:", msg, err?.stack || "");
} finally {
setBusy(false);
}
});
window.addEventListener("beforeunload", revokeAllUrls);
appendLog("test page booted");
</script>
</body>
</html>