mirror of
https://github.com/vibegif/vibegif.lol.git
synced 2026-04-07 02:12:12 +00:00
121 lines
3.6 KiB
JavaScript
121 lines
3.6 KiB
JavaScript
const LIB_URLS = [
|
|
'https://esm.sh/@imgly/background-removal@1.7.0?bundle',
|
|
'https://cdn.jsdelivr.net/npm/@imgly/background-removal@1.7.0/+esm'
|
|
];
|
|
|
|
let removeBackgroundFn = null;
|
|
let loadingPromise = null;
|
|
|
|
async function loadLib() {
|
|
if (removeBackgroundFn) return removeBackgroundFn;
|
|
if (loadingPromise) return loadingPromise;
|
|
|
|
loadingPromise = (async () => {
|
|
let lastErr = null;
|
|
for (const url of LIB_URLS) {
|
|
try {
|
|
const mod = await import(url);
|
|
const fn = mod?.default || mod?.removeBackground || mod?.imglyRemoveBackground;
|
|
if (typeof fn !== 'function') throw new Error(`No removeBackground export from ${url}`);
|
|
removeBackgroundFn = fn;
|
|
return removeBackgroundFn;
|
|
} catch (err) {
|
|
lastErr = err;
|
|
}
|
|
}
|
|
throw lastErr || new Error('Failed to load background-removal library.');
|
|
})();
|
|
|
|
return loadingPromise;
|
|
}
|
|
|
|
function loadImage(src) {
|
|
return new Promise((resolve, reject) => {
|
|
const img = new Image();
|
|
img.decoding = 'async';
|
|
img.onload = () => resolve(img);
|
|
img.onerror = () => reject(new Error('Failed to decode frame image'));
|
|
img.src = src;
|
|
});
|
|
}
|
|
|
|
async function toPngFile(src, name = 'frame.png') {
|
|
const img = await loadImage(src);
|
|
const w = img.naturalWidth || img.width;
|
|
const h = img.naturalHeight || img.height;
|
|
|
|
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 frame to PNG'))), 'image/png', 1);
|
|
});
|
|
|
|
return new File([blob], name, { type: 'image/png' });
|
|
}
|
|
|
|
function blobToDataUrl(blob) {
|
|
return new Promise((resolve, reject) => {
|
|
const fr = new FileReader();
|
|
fr.onload = () => resolve(String(fr.result || ''));
|
|
fr.onerror = () => reject(new Error('Failed to convert blob to data URL'));
|
|
fr.readAsDataURL(blob);
|
|
});
|
|
}
|
|
|
|
export async function removeBackgroundFrames(frameDataUrls, { onProgress } = {}) {
|
|
if (!Array.isArray(frameDataUrls) || !frameDataUrls.length) throw new Error('No frames for background removal.');
|
|
const removeBackground = await loadLib();
|
|
|
|
const out = [];
|
|
const total = frameDataUrls.length;
|
|
|
|
for (let i = 0; i < total; i++) {
|
|
const frameNo = i + 1;
|
|
const src = frameDataUrls[i];
|
|
|
|
onProgress?.({
|
|
pct: Math.round((i / total) * 100),
|
|
text: `preparing frame ${frameNo} of ${total} for background removal...`
|
|
});
|
|
|
|
const pngFile = await toPngFile(src, `frame-${frameNo}.png`);
|
|
|
|
onProgress?.({
|
|
pct: Math.round((i / total) * 100),
|
|
text: `removing background from frame ${frameNo} of ${total}...`
|
|
});
|
|
|
|
const outBlob = await removeBackground(pngFile, {
|
|
debug: false,
|
|
output: { type: 'foreground', format: 'image/png', quality: 1 },
|
|
progress: (_key, current, stepTotal) => {
|
|
if (!stepTotal) return;
|
|
const inner = current / stepTotal;
|
|
const pct = Math.round(((i + inner) / total) * 100);
|
|
onProgress?.({
|
|
pct,
|
|
text: `removing background from frame ${frameNo} of ${total}...`
|
|
});
|
|
}
|
|
});
|
|
|
|
const outDataUrl = await blobToDataUrl(outBlob);
|
|
out.push(outDataUrl);
|
|
|
|
onProgress?.({
|
|
pct: Math.round((frameNo / total) * 100),
|
|
text: `frame ${frameNo} of ${total} background removed`
|
|
});
|
|
}
|
|
|
|
onProgress?.({ pct: 100, text: 'background removal done' });
|
|
return out;
|
|
}
|