UNPKG

@remotion/gif

Version:

Embed GIFs in a Remotion video

116 lines (115 loc) 4.31 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.generate = exports.parse = void 0; const gifuct_1 = require("./gifuct"); const decompress_frames_1 = require("./parser/decompress-frames"); const validateAndFix = (gif) => { let currentGce = null; for (const frame of gif.frames) { currentGce = frame.gce ? frame.gce : currentGce; // fix loosing graphic control extension for same frames if ('image' in frame && !('gce' in frame) && currentGce !== null) { frame.gce = currentGce; } } }; const resetPixels = ({ typedArray, dx, dy, width, height, gifWidth, }) => { const offset = dy * gifWidth + dx; for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const taPos = offset + y * gifWidth + x; typedArray[taPos * 4] = 0; typedArray[taPos * 4 + 1] = 0; typedArray[taPos * 4 + 2] = 0; typedArray[taPos * 4 + 3] = 0; } } }; const putPixels = (typedArray, frame, gifSize) => { const { width, height, top: dy, left: dx } = frame.dims; const offset = dy * gifSize.width + dx; for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const pPos = y * width + x; const colorIndex = frame.pixels[pPos]; if (colorIndex !== frame.transparentIndex) { const taPos = offset + y * gifSize.width + x; const color = frame.colorTable[colorIndex]; typedArray[taPos * 4] = color[0]; typedArray[taPos * 4 + 1] = color[1]; typedArray[taPos * 4 + 2] = color[2]; typedArray[taPos * 4 + 3] = colorIndex === frame.transparentIndex ? 0 : 255; } } } }; const parse = (src, { signal, }) => fetch(src, { signal }) .then((resp) => { var _a; if (!((_a = resp.headers.get('Content-Type')) === null || _a === void 0 ? void 0 : _a.includes('image/gif'))) throw Error(`Wrong content type: "${resp.headers.get('Content-Type')}"`); return resp.arrayBuffer(); }) .then((buffer) => (0, gifuct_1.parseGIF)(buffer)) .then((gif) => { validateAndFix(gif); return gif; }) .then((gif) => Promise.all([ (0, decompress_frames_1.decompressFrames)(gif), { width: gif.lsd.width, height: gif.lsd.height }, ])) .then(([frames, options]) => { const readyFrames = []; const size = options.width * options.height * 4; let canvas = new Uint8ClampedArray(size); for (let i = 0; i < frames.length; ++i) { const frame = frames[i]; // Read about different disposal types // https://giflib.sourceforge.net/whatsinagif/animation_and_transparency.html const prevCanvas = frames[i].disposalType === 3 ? canvas.slice() : null; putPixels(canvas, frame, options); readyFrames.push(canvas.slice()); // Disposal type 2: The canvas should be restored to the background color if (frames[i].disposalType === 2) { resetPixels({ typedArray: canvas, dx: frame.dims.left, dy: frame.dims.top, width: frame.dims.width, height: frame.dims.height, gifWidth: options.width, }); } // Disposal type 3: The decoder should restore the canvas to its previous state before the current image was drawn else if (frames[i].disposalType === 3) { if (!prevCanvas) { throw Error('Disposal type 3 without previous frame'); } canvas = prevCanvas; } // Disposal type 1: Draw the next image on top of it else { canvas = readyFrames[i].slice(); } } return { ...options, loaded: true, delays: frames.map((frame) => frame.delay), frames: readyFrames, }; }); exports.parse = parse; const generate = (info) => { return { ...info, frames: info.frames.map((buffer) => { const image = new ImageData(info.width, info.height); image.data.set(new Uint8ClampedArray(buffer)); return image; }), }; }; exports.generate = generate;