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
61 lines
2.2 KiB
JavaScript
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);
|
|
};
|