柚子青年。

拆分APNG帧动画以及生成ZIP下载
柚子青年。
2022-09-27

起因

某天晚上我在某牙看直播时,发现他们新推了一个表情包还挺逗,大概是这样的

如果你看到这段字说明你的浏览器不支持

于是我突发奇想做一个表情包,这样的

拆分APNG帧动画以及生成ZIP下载

由于PS不支持APNG的帧动画拆分,所以我只能自己动手了 👨‍💻

简单介绍一下APNG

GIF:

  • 最多支持 8 位 256 色,色阶过渡糟糕,图片具有颗粒感
  • 不支持 Alpha 透明通道,边缘有杂边

APNG:

  • 支持 24 位真彩色图片
  • 支持 8 位 Alpha 透明通道
  • 向下兼容 PNG

GIF的动画每帧都是完整图片

拆分APNG帧动画以及生成ZIP下载

APNG会通过算法计算帧之间的差异,只存储帧之前的差异,而不是存储全帧。

拆分APNG帧动画以及生成ZIP下载

APNG兼容性

拆分APNG帧动画以及生成ZIP下载

apng-js

Github:https://github.com/davidmz/apng-js

apng-js 是一个拆分APNG文件并且提供播放能力的一个库,由于我想做一个拆分APNG动画帧然后生成ZIP文件下载的工具库,所以我抽取了一些核心代码重新实现了一个拆分APNG生成图片的功能。(源码

因为APNG后面的图片只有差异帧,所以会导致图片大小不一,在canvas上重新绘制一遍,统一所有图片大小。这里只帖部分代码,感兴趣的可以点击源码查看。

createImage(width, height) {
    return new Promise((resolve, reject) => {
        const url = URL.createObjectURL(this.imageData);
        const img = document.createElement('img');
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext("2d");

        /* 计算屏幕分辨率 */
        const resolution = this.getScreenResolution(ctx);
        canvas.width = width * resolution;
        canvas.height = height * resolution;
        canvas.style.width = width + "px";
        canvas.style.height = height + "px";
        ctx.scale(resolution, resolution);

        img.onload = () => {
            URL.revokeObjectURL(url);
            ctx.drawImage(img, this.left, this.top);
            this.canvas = canvas;
            resolve(canvas);
        };
        img.onerror = () => {
            URL.revokeObjectURL(url);
            reject(new Error("Image creation error"));
        };
        img.src = url;
    });
}

HTMLCanvasElement.toBlob

HTMLCanvasElement.toBlob() 方法创造Blob对象,用以展示canvas上的图片;这个图片文件可以被缓存或保存到本地,由用户代理端自行决定。如不特别指明,图片的类型默认为 image/png,分辨率为96dpi。

HTMLCanvasElement.toBlob 可以生成一个Blob对象,后续生成文件的时候就可以使用这个方法。

jszip

Github:https://github.com/Stuk/jszip

jszip是一个生成zip的库,使用方式也很简单。作者也提供了普通版本以及min版本的js,都在dist目录下。我这边简单封装了一下生成图片ZIP然后下载的方法。

ZIPjs.images = (fileName, dataSource: imagesZipProps[]) => {
    var zip = new window.JSZip();
    var img = zip.folder(fileName);

    dataSource.forEach(({ name, imgData }) => {
        img.file(name, imgData, { base64: true });
    });

    // 生成zip文件并下载
    zip.generateAsync({
        type: 'blob'
    }).then(function (content) {
        // 创建隐藏的可下载链接
        var eleLink = document.createElement('a');
        eleLink.download = fileName + '.zip';
        eleLink.style.display = 'none';
        // 下载内容转变成blob地址
        eleLink.href = URL.createObjectURL(content);
        // 触发点击
        document.body.appendChild(eleLink);
        eleLink.click();
        // 然后移除
        document.body.removeChild(eleLink);
    });
}

在线体验地址:柚子青年 - 工具