mirror of
https://github.com/vibegif/vibegif.lol.git
synced 2026-04-07 02:12:12 +00:00
Feat: frame bg removal pipeline for data URLs
This commit is contained in:
120
src/services/background.js
Normal file
120
src/services/background.js
Normal file
@@ -0,0 +1,120 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user