携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情
概述
在 Scratch 二次开发或者其它对图片有比较大处理需求的场景中,可能会遇到这样一个需求——将 gif 图片中的每一帧切割出来,让用户自行选择里面需要用到的帧,再将其重新组合。
里面原始需求就是获取 gif 中的每一帧图片,把 gif 转换成一张张 png 图片。
效果如下:
将这张 gif 切割成一帧帧的 png 图片:
如图:
实现方案
先安装 omggif
依赖库:
npm i omggif --save
以下函数可以直接使用
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);
};
简单解释一下,函数中两个参数:
- arrayBuffer
- ArrayBuffer 二进制数据缓冲区
- 如果是通过
<input>
标签获取的 File 对象(或者其它方式获取的 File 对象也一样),可以通过 FileReader 将 File 对象转换成 ArrayBuffer
- onFrame
- 回调函数,返回每一帧的 base64 dataURL
- 回调函数的参数:
- frameNumber,当前是第几帧,从 0 开始
- dataURL,base64 字符串
- numFrames,一共有多少帧
后记
本来想结合 libgif.js,对比两个库切割帧的效果,但发现这个库侧重点好像是通过 js 控制 gif 的播放、暂停,不太符合前文提到的需求场景,另外,使用的时候还需要搭配 <img>
元素,感觉不太方便(没深入研究,如有错误,欢迎指出)
另外,除了切割 gif 之外,相对的就是将多种图片合成一张 gif,有两个备选库:
据说 gifshot 效果比 gif.js 好一点,支持的参数更多、速度也快一点,后续有时间验证一下,再对比对比。