Files
scratch-gui/src/lib/gif-decoder.js
T
Paul Kaplan 40a5ff783a Fix loading single frame gifs by removing onDone in favor of indices
Because the `onFrame` callback could do async work, it was possible for onDone to try to submit the costumes before the onFrame had produced any. This was subject to browsers different timing treatments of setTimeout vs. promise resolution. This makes the gif-decoder use the same style of progress API as the file uploader
2019-02-14 13:55:15 -05:00

61 lines
2.2 KiB
JavaScript

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);
};