From 4de18f3825d90d394c1fe0a737309686967f2aa8 Mon Sep 17 00:00:00 2001 From: multipleof4 Date: Sat, 21 Mar 2026 00:36:15 -0700 Subject: [PATCH] Feat: semi-transparent mode with animated webp --- src/main.js | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/main.js b/src/main.js index ee8cf2a..072570d 100644 --- a/src/main.js +++ b/src/main.js @@ -2,6 +2,8 @@ import { getApiKey, setApiKey, hasApiKey } from './core/storage.js'; import { buildFirstMessage, buildNextMessage } from './core/messages.js'; import { generateFrame } from './services/openrouter.js'; import { assembleGif } from './services/gif.js'; +import { removeBackgroundFrames } from './services/background.js'; +import { assembleAnimatedWebp } from './services/webp.js'; import { ELEMENT_IDS, grabElements, @@ -62,6 +64,9 @@ async function handleGenerate() { const maxSize = Math.max(32, Math.min(1024, Number.parseInt(el.inpMaxSize?.value || '256', 10) || 256)); const imageSize = '1K'; const aspectRatio = el.selRatio?.value || '1:1'; + const semiTransparent = !!el.chkSemiTransparent?.checked; + + const generationMaxPct = semiTransparent ? 55 : 100; setGenerating(el, true); resetProgress(el); @@ -86,10 +91,10 @@ async function handleGenerate() { allFrames.push(first.base64); history.push(firstMsg, first.assistantMsg); addFramePreview(el, first.base64, 0); - setProgress(el, Math.round(100 / frameCount), `frame 1 of ${frameCount} done`); + setProgress(el, Math.round((1 / frameCount) * generationMaxPct), `frame 1 of ${frameCount} done`); for (let i = 2; i <= frameCount; i++) { - setProgress(el, Math.round(((i - 1) / frameCount) * 100), `generating frame ${i} of ${frameCount}...`); + setProgress(el, Math.round(((i - 1) / frameCount) * generationMaxPct), `generating frame ${i} of ${frameCount}...`); const nextUserMsg = buildNextMessage(i, frameCount); const startIdx = Math.max(1, history.length - WINDOW * 2); @@ -106,14 +111,35 @@ async function handleGenerate() { allFrames.push(next.base64); history.push(nextUserMsg, next.assistantMsg); addFramePreview(el, next.base64, i - 1); - setProgress(el, Math.round((i / frameCount) * 100), `frame ${i} of ${frameCount} done`); + setProgress(el, Math.round((i / frameCount) * generationMaxPct), `frame ${i} of ${frameCount} done`); } - setProgress(el, 100, 'assembling gif...'); - const blob = await assembleGif(allFrames, fps, maxSize); - currentGifUrl = URL.createObjectURL(blob); - showResult(el, currentGifUrl); - setProgress(el, 100, 'done! 🎉'); + if (!semiTransparent) { + setProgress(el, 100, 'assembling gif...'); + const blob = await assembleGif(allFrames, fps, maxSize); + currentGifUrl = URL.createObjectURL(blob); + showResult(el, currentGifUrl, 'vibegif.gif'); + setProgress(el, 100, 'done! 🎉'); + return; + } + + setProgress(el, 56, 'starting background removal...'); + const transparentFrames = await removeBackgroundFrames(allFrames, { + onProgress: ({ pct, text }) => { + const mapped = Math.round(55 + (Math.max(0, Math.min(100, pct)) * 0.30)); + setProgress(el, mapped, text || 'removing backgrounds...'); + } + }); + + setProgress(el, 86, 'assembling animated webp...'); + const webpBlob = await assembleAnimatedWebp(transparentFrames, fps, maxSize, (pct, text) => { + const mapped = Math.round(85 + (Math.max(0, Math.min(100, pct)) * 0.15)); + setProgress(el, mapped, text || 'assembling animated webp...'); + }); + + currentGifUrl = URL.createObjectURL(webpBlob); + showResult(el, currentGifUrl, 'vibegif.webp'); + setProgress(el, 100, 'done! transparent animated webp ready ✨'); } catch (err) { const msg = err?.message || String(err); setProgress(el, 0, `error: ${msg}`);