function loadImage(src) { return new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = "anonymous"; img.onload = () => resolve(img); img.onerror = reject; img.src = src; }); } export async function buildGifFromFrames(frames, fps = 6) { if (!frames?.length) throw new Error("No frames to build GIF."); const delay = Math.max(40, Math.floor(1000 / Math.max(1, fps))); const images = await Promise.all(frames.map(loadImage)); const gif = new GIF({ workers: 2, quality: 10, workerScript: "https://cdn.jsdelivr.net/npm/gif.js.optimized/dist/gif.worker.js", width: images[0].naturalWidth, height: images[0].naturalHeight }); for (const img of images) gif.addFrame(img, { delay }); const blob = await new Promise((resolve) => { gif.on("finished", resolve); gif.render(); }); return URL.createObjectURL(blob); }