import {GifReader} from 'omggif'; export default (arrayBuffer, onFrame) => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const gifReader = new GifReader(new Uint8Array(arrayBuffer)); const numFrames = gifReader.numFrames(); canvas.width = gifReader.width; canvas.height = gifReader.height; let imageData = ctx.createImageData(canvas.width, canvas.height); let previousData = ctx.createImageData(canvas.width, canvas.height); const loadFrame = i => { const framePixels = []; gifReader.decodeAndBlitFrameRGBA(i, framePixels); const {x, y, width, height, disposal} = gifReader.frameInfo(i); for (let row = 0; row < height; row++) { for (let column = 0; column < width; column++) { const indexOffset = 4 * (x + (y * canvas.width)); const j = indexOffset + (4 * (column + (row * canvas.width))); if (framePixels[j + 3]) { imageData.data[j + 0] = framePixels[j + 0]; imageData.data[j + 1] = framePixels[j + 1]; imageData.data[j + 2] = framePixels[j + 2]; imageData.data[j + 3] = framePixels[j + 3]; } } } ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.putImageData(imageData, 0, 0); const dataUrl = canvas.toDataURL(); switch (disposal) { case 2: // "Return to background", blank out the current frame ctx.clearRect(x, y, width, height); imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); break; case 3: // "Restore to previous", copy previous data to current imageData = ctx.createImageData(canvas.width, canvas.height); imageData.data.set(previousData.data); break; default: // 0 and 1, as well as 4+ modes = do-not-dispose, so cache frame previousData = ctx.getImageData(0, 0, canvas.width, canvas.height); break; } onFrame(i, dataUrl, numFrames); if (i < numFrames - 1) { setTimeout(() => { loadFrame(i + 1); }); } }; loadFrame(0); };