如何使用 JavaScript 将多张 GIF 图合成为一张 GIF 图

推荐关注↓

转自:网络

  1. 解析 DOM 中包含的 GIF

  2. 重新生成 GIF 的每一帧


解析 GIF 插件 gifuct-js

解析出来每一帧的数据结构

{    // The color table lookup index for each pixel
pixels: [...], // the dimensions of the gif frame (see disposal method)
dims: { top: 0, left: 10, width: 100, height: 50
}, // the time in milliseconds that this frame should be shown
delay: 50, // the disposal method (see below)
disposalType: 1, // an array of colors that the pixel data points to
colorTable: [...], // An optional color index that represents transparency (see below)
transparentIndex: 33, // Uint8ClampedArray color converted patch information for drawing
patch: [...]
}

patch 表示当前帧图,数据格式是 Uint8ClampedArray,可以使用 putImageData 在 canvas 渲染。

Automatic Patch Generation:If the buildPatch param of the dcompressFrames() function is true, the parser will not only return the parsed and decompressed gif frames, but will also create canvas ready Uint8ClampedArray arrays of each gif frame image, so that they can easily be drawn using ctx.putImageData() for example. This requirement is common, however it was made optional because it makes assumptions about transparency. The demo makes use of this option.

ctx.putImageData(  new ImageData(patch, dims.width, dims.height),
dims.left,
dims.top);

生成 GIF 插件 gif.js

const gif = new GIF({ workers: 2, quality: 10 });
gif.addFrame(imageElement, { delay: 50 });
gif.on('finished', (blob) => { window.open(URL.createObjectURL(blob));
});
gif.render();

此插件没有 npm 包,只能将文件拷贝到项目中,通过 script 标签引用。

问题:多个 GIF 素材时长不一致,如何保证同时结束?

场景一

  • GIFa 时长 1000ms

  • GIFb 时长 1200ms

假设生成的 GIF 时长 1200ms,GIFb 正常结束,GIFa 丢失 200ms。

解决方案:取平均值

平均值:(1000 + 1200) / 2 = 1100 ms
GIFa 增加 100ms,假设总共 20 帧,单帧时长 50ms。则每帧增加 100 / 20 = 5ms,为 55ms。
GIFb 减少 100ms,假设总共 24 帧,单帧时长 50ms。则每帧减少 100 / 24 = 4ms,为 46ms。

场景二

  • GIFa 时长 1200ms

  • GIFb 时长 3200ms

生成的 GIF 时长 3200ms,GIFb 正常结束,GIFa 轮播 2 次,第 3 次播放 800ms,丢失 400ms。

解决方案:

  1. 计算最大公倍数
    时长会很大,导致数据量很大。❌

  2. 计算最小时长与最大时长的合理倍数关系,最小时长 * 倍数。

function computeTotalTime(min: number, max: number) {  const div = max / min;  const decimal = div % 1;  const multiple = decimal > 0.5 ? Math.ceil(div) : Math.floor(div);  return min * multiple;
}

合理倍数关系,GIFa 与 GIFb 的合理倍数应该是 2 还是 3?
如果是 2,则二者差距是 3200 - (1200 * 2) = 800ms;
如果是 3,则二者差距是 (1200 * 3) - 3200 = 400ms;
差距越小越好,所以合理倍数关系为 3,总时长为 3600ms。

调整每张 GIF 图每帧的时长,适配总时长。

// 调整多张 gif 图的数据,适配总时长function adaptGifFrames(gifsData: GifData[], time: number) {  // 适配时长
gifsData.forEach((gifData) => { const { totalTime, frames } = gifData; // 目标时长与当前 gif 图时长的倍数关系
const multiple = Math.floor(time / totalTime); // 目标时长与当前 gif 图时长的差值
let diff = time - totalTime; if (multiple !== 0) {
diff = (time - multiple * totalTime) / multiple;
} if (diff === 0) return;
frames.forEach((frame) => { // 依据每一帧占比增加时长
const precent = frame.delay / totalTime;
frame.delay += diff * precent;
}); // 设置适配后的总时长
gifData.totalTime = Math.round(
frames.reduce((pre, cur) => pre + cur.delay, 0)
);
});
}

问题:生成 GIF 图时,总共多少帧?每帧的延时时长是多少?

理想情况下,每帧延时时长为 50ms,但所使用的 GIF 图来源不一,无法保证其一致性。另外总时长越长,帧数就越多,体积就越大,GIF 图生成时间就越长。
所以限制帧数最大值为 100,如果超过 100,则每帧增加相应时长。

function computeGifData(gifsData: GifData[]) {  const times = gifsData.map((gif) => gif.totalTime);  const minTime = Math.min(...times);  const maxTime = Math.max(...times);  const div = maxTime / minTime;  let totalTime = 0;  if (div > 2) {    // 计算最大时长
totalTime = computeTotalTime(minTime, maxTime);
} else { // 取平均时长
totalTime = times.reduce((pre, cur) => pre + cur, 0) / times.length;
} let delay = DEFAULT_GIF_DELAY; let frameLen = totalTime / delay; // 帧数
if (frameLen > MAX_GIF_FRAME) {
delay = delay * (frameLen / MAX_GIF_FRAME);
frameLen = MAX_GIF_FRAME;
} return { totalTime, frameLen, delay };
}

根据帧数 frameLen 循环获取每一帧的图片,延时时长为 delay。

gif.addFrame(image, { delay });

问题:字体较多导致生成速度较慢

中文字体文件体积大多以 M 为单位,导致每帧图片的 svg 体积较大,继而导致每帧图片加载耗时较长。

上图 3000ms ~ 10000ms,近 7s 时间都是在加载每帧的图片。
百度前端团队有个 NodeJS 工具 fontmin,可以按照指定文字内容对字体文件进行裁剪,返回仅包含指定文字内容的字体文件,可以极大的减少字体体积。
使用这个工具后,GIF 图生成速度得到显著提升。

全文完,如果觉得这篇文章对你有用,欢迎 点赞 👍、评论 ✍️、收藏 👀

- EOF -


加主页君微信,不仅前端技能+1

主页君日常还会在个人微信分享前端开发学习资源技术文章精选,不定期分享一些有意思的活动岗位内推以及如何用技术做业余项目

加个微信,打开一扇窗



推荐阅读  点击标题可跳转

1、面试官:请使用 JS 完成一个 LRU 缓存?

2、用 JS 解析 excel 文件需要分几步

3、面试必问之 JS 事件循环(Event Loop),看这一篇足够!


觉得本文对你有帮助?请分享给更多人

推荐关注「前端大全」,提升前端技能

点赞和在看就是最大的支持❤️

相关推荐

  • 8 个很棒的 Vue 开发技巧
  • 我为什么会放弃了大厂 45 万美元的高薪工作?
  • 雷军:小米汽车争取15-20年进入世界前五;GitHub宣布裁员10%,全员转远程办公;谷歌AR/VR负责人宣布离职|极客头条
  • 特批!本科以下请注意!国家政策扶持特招120人直升本科!最低初中学历可报!
  • 实操!记一次小黄站渗透过程
  • 兰蔻杀疯了!情人节限定爱心口红礼盒直接封神!299元开抢!
  • 为什么ChatGPT,必然不会来自Google或者百度?
  • 一个35岁技术经理的忠告:在职场多点雷霆手段,少点菩萨心肠!
  • 除了 xxl-job 之外,另一款更强大的分布式任务调度框架来了!
  • DataFrame 与内置数据结构的相互转换
  • 分库分表我用Sharding-JDBC
  • 加入最火OpenAI,特斯拉前AI总监Andrej Karpathy自宣回归
  • 万字长文:AI产品经理视角的ChatGPT全解析
  • 符尧@爱丁堡大学:ChatGPT一系列大模型背后的技术演化过程!
  • ChatGPT出来后,我们是否真的面临范式转变?
  • 五粮液经销商让利了!1800多的酒,限时999(节假日就不是这价了!)
  • 3名中国公民在地震中获救;美记者爆料“拜登下令炸北溪管道”;泽连斯基向英议会下院议长“送礼” | 每日大新闻
  • 有多少美国士兵,在帮黑社会偷军火?
  • 北溪管道系美军炸毁;100万买私募6年只剩1.99万;默沙东新冠口服药诱导新冠病毒突变......|酷玩日爆
  • 求职时VS转正后 | 每日一冷