JavaScript纯前端实现在线GIF压缩

 更新时间:2024年03月06日 08:42:09   作者:可乐鸡翅kele  
这篇文章主要为大家详细介绍了如何利用JavaScript纯前端实现在线GIF压缩工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下

前言

原因是我在写公众号文章的时候,公众号编辑器只能上传最大10MGIF,而我刚好需要上传的GIF超过了这个阈值。

所以我就随便搜了一个压缩GIF的工具,有一些压缩完了下载需要付费,有一些不付费就只能压缩低于某个阈值的文件。

不过也能理解吧,毕竟别人也是需要赚钱的,这种文件的处理如果放在服务端做,那必然是要耗费不少资源的,如果放在纯前端做,又不能保证文件处理的速度。

然后我就想着GIF压缩应该不难吧,应该有现成的工具库吧,于是我就开始动手去实现一个GIF压缩工具。这里我很执拗地要去实现一个纯客户端的GIF压缩(也不知道为啥我这么执拗。)

初探GIF压缩

我们都知道GIF是一张张静态的图片播放形成的动图,所以我一开始就想着,如果我有一个库,能帮我把GIF所有帧都抽取出来,然后我对所有帧的图片进行一个缩放的有损压缩,压缩完之后再把所有图片合成一个新的GIF,那么我不就把GIF给压缩了么。

然后我就找到了gif.jsomggif.js这两个库:

  • omggif负责解析GIF,获取GIF的每一帧图像
  • 把获取到的每一帧图像进行处理了之后,使用gif.js合成GIF

那么可以写出下面的代码,以下的代码就是一个分离GIF帧+合成GIF的代码,还没有加上压缩每一帧的逻辑:

fetch("/test.gif")
  .then((response) => response.arrayBuffer())
  .then((buffer) => {
    // 用omggif解析GIF
    let reader = new omggif.GifReader(new Uint8Array(buffer));

    // 创建新的gif.js实例
    let gif = new GIF({
      workers: 2,
      quality: 5,
      width: reader.width,
      height: reader.height,
    });

    const width = reader.width;
    const height = reader.height;

    // 创建canvas元素
    const imgs = [];
    // 遍历所有的frames,并添加到gif.js实例中
    for (let i = 0; i < reader.numFrames(); i++) {
      let frameInfo = reader.frameInfo(i);
      let canvas = document.createElement("canvas");
      canvas.width = width;
      canvas.height = height;
      let ctx = canvas.getContext("2d", { willReadFrequently: true });
      let imageData = ctx.createImageData(width, height);
      reader.decodeAndBlitFrameRGBA(i, imageData.data);
      ctx.putImageData(imageData, 0, 0);
      gif.addFrame(ctx, { delay: frameInfo.delay });
      imgs.push(canvas.toDataURL());
    }

    //预览分离出来的帧
    const div = document.createElement("div");
    imgs.forEach((url) => {
      const img = document.createElement("img");
      img.src = url;
      div.appendChild(img);
    });
    document.body.appendChild(div);

    // 生成GIF并下载
    gif.on("finished", function (blob) {
      console.log("finish");
      let url = URL.createObjectURL(blob);
      document.querySelector("#img").src = url;
      const a = document.createElement("a");
      a.download = true;
      a.href = url;
      a.click();
    });

    gif.render();
  });

但是我分离帧之后发现,分离出来的帧相当奇怪:

而且合并之后的GIF感觉完全被破坏掉了,我不清楚是不是我的这种思路本身就是不可行的,还是说这两个库不能这么用。是不是我这样做了之后导致描述GIF的信息丢了?(比如调色盘或者其他的一些全局信息,评论区有大神可以指导一下么)

这种方式不可行之后,我又尝试了一下别的方式。这一次我不再寄希望于纯js实现的库,而是往wasm方向去探索。

然后我尝试使用Rust去解析GIF,并返回所有解析后的图片给前端,前端再去做有损压缩,但这一做法耗时太长,也不是成功的尝试,所以代码就不放出来了。

接着我就想到了ffmpeg,我知道它有压缩GIF的功能,而且他也有成熟的wasm版本,可以便捷地在前端引入使用,但是它压缩后近10M的体积依旧让人望而生畏。

gifsicle

在继续搜索GIF压缩等关键词时,发现了gifsicle这个库。它是一个用于处理GIF图像的命令行工具和库,提供了很多功能包括创建、编辑、优化和调整GIF

ffmpeg对比起来,它是一个更专注于GIF处理的库,所以体积会比ffmpeg肯定小不少,所以我就想着能不能弄一个它的wasm版本移植到浏览器中使用。

后面还真让我找到了它对应的wasm版本,具体链接可以查看:gifsicle-wasm-browse,这个库GZip压缩之后只有150K左右,完全不用担心体积问题。

使用gifsicle的时候,常常使用如下的手段去减少GIF的体积:

-O参数:

  • O1:该选项对应于轻度优化,它会执行一些基本的优化步骤,删除无用的图像数据和元数据,以减小文件大小。这个级别的优化通常执行较快,但可能不会最大限度地减小文件大小。
  • O2:中度优化比轻度优化更深入,它可能会花费更多的时间来寻找更多的优化机会。这通常会导致更好的压缩比和更小的文件大小。
  • O3:最大优化级别会执行最深入的优化,可能需要更多的时间来完成,但通常会实现最大的文件大小减小。这个级别的优化可能对CPU和内存的需求更高。

--loosy

  • --lossy 会减少图像中使用的颜色数量,从而减小颜色表的大小。这会导致颜色的近似和失真,因为原始的颜色信息会被近似为颜色表中的颜色。
  • --lossy 程度下,一些微小的图像细节可能会被去除,以进一步减小文件大小。这可能导致图像的一些细节在视觉上的损失

减少颜色数:

  • GIF中每个像素可以使用图像调色板中的一种颜色,而颜色数量表示这些不同颜色的总数。GIF使用的颜色调色板通常是8位色,允许最多256种不同的颜色。
  • 减少GIF的颜色数量可以降低图像的文件大小,因为图像中使用的颜色越少,每个像素的颜色信息所需的位数就越少。然而,减少颜色数量也可能导致图像的视觉质量下降,尤其是对于包含大量颜色细节的图像。

删除注释和元数据

裁剪和缩小:最直观的压缩手段,属于有损压缩

调整帧速率:相当于减少组成这个GIF文件的图片数量,也是有损压缩

具体实现

首先安装gifsicle-wasm-browser这个包,然后简单搭建一个表单如下:

gifsicle的具体文档,可以点击这里查看。

在前端中可以参照以下方式来使用gifsicle

  gifsicle
    .run({
      input: [
        {
          file: buffer,
          name: "input.gif",
        },
      ],
      command: [command],
    })
    .then(async (res) => {

    });

GIF的压缩过程中,常常会把多种压缩方式结合起来使用。如果单纯的想压缩GIF的体积而不考虑GIF的质量,那就直接使用缩放进行有损压缩就好了;如果想保留GIF的质量的同时压缩GIF的体积,那还是需要多方参数的组合尝试。

压缩前的图像:

以下是我一些尝试压缩的参数以及压缩前后的对比:

压缩参数: -O2 --lossy=180 input.gif --colors 64 --scale 0.8 -o /out/out.gif

  • 压缩后:体积:4.0MB,颜色数量:64,帧数不变,分辨率不变
  • 执行时间:72s

压缩参数:-O2 --lossy=180 input.gif --colors 32 --scale 0.8 -o /out/out.gif

  • 压缩后:体积:3MB,颜色数量:32,帧数不变,分辨率不变
  • 执行时间:60s
  • 可见图像颜色已经严重变形

压缩参数:input.gif --colors 64 --scale 0.5 -o /out/out.gif

  • 压缩后:体积:3MB,颜色数量:64,帧数不变,分辨率变为之前一半
  • 执行时间:19s
  • 图像较为模糊,颜色也有少许变形

可以看到在大多数的压缩参数下,都需要较长的执行时间,这是因为这个wasm包还没有实现多核处理,还没办法利用多核CPU的优势,所以处理起来时间会比较长。

总结

如果这个包实现了多核处理,执行时间能缩短一些的话,那么我觉得会有更多的GIF处理会放在前端来做。

我一直觉得纯前端处理是一件很酷的事情,对于开发者来说,无需承担昂贵的计算资源成本,如果不想承担服务器成本,这个时候还有一种解决方法就是做成桌面应用,把任务处理的二进制文件打包下载到用户本地。

但如果是纯前端实现的话,对于使用者来说,无需下载任何东西,点开即用。

所以后面也理解了网上一些压缩GIF需要收费的工具,一个好的在线GIF压缩产品它能同时保证执行速度、压缩体积、压缩质量,这还是相当不容易的。

以上就是JavaScript纯前端实现在线GIF压缩的详细内容,更多关于JavaScript GIF压缩的资料请关注脚本之家其它相关文章!

相关文章

  • js传中文参数controller里获取参数乱码问题解决方法

    js传中文参数controller里获取参数乱码问题解决方法

    js传中文参数,在controller里获取参数乱码的问题在本文有个不错的解决方法,感兴趣的朋友可以参考下
    2014-01-01
  • 小程序如何支持使用 async/await详解

    小程序如何支持使用 async/await详解

    这篇文章主要介绍了小程序如何支持使用 async/await详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • JavaScript检查弹出窗口是否被阻拦的方法技巧

    JavaScript检查弹出窗口是否被阻拦的方法技巧

    这篇文章主要介绍了JavaScript检查弹出窗口是否被阻拦的方法技巧,本文用于检测window.open弹出的窗口是否被阻拦,需要的朋友可以参考下
    2015-03-03
  • 一文掌握new Date() 方法

    一文掌握new Date() 方法

    大家平时在开发的时候有没被new Date()折磨过,反正我是踩过很多坑,今天小编通过本文给大家详细讲解下new date()方法应用思考,感兴趣的朋友一起看看吧
    2023-04-04
  • js实现简单秒表走动的时钟特效

    js实现简单秒表走动的时钟特效

    这篇文章主要为大家详细介绍了js实现简单秒表走动的时钟特效,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2015-08-08
  • 原生JavaScript写出Tabs标签页的实例代码

    原生JavaScript写出Tabs标签页的实例代码

    这篇文章主要介绍了原生JavaScript写出Tabs标签页的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07
  • 浅谈Javascript中Object与Function对象

    浅谈Javascript中Object与Function对象

    JavaScript的面向对象是基于原形的,所有对象都有一条属于自己的原型链。Object与Function可能很多看Object instanceof Function , Function instanceof Object都为true而迷惑,所以首先看下对象的实例
    2015-09-09
  • Bootstrap表格制作代码

    Bootstrap表格制作代码

    这篇文章主要为大家详细介绍了Bootstrap表格的制作代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-03-03
  • 原生Js与jquery的多组处理, 仅展开一个区块的折叠效果

    原生Js与jquery的多组处理, 仅展开一个区块的折叠效果

    同一个页面, 有多组(不固定), 每组区块数量不一定一样的小区块. 要求每次只展开一个区块,需要的朋友可以参考下。
    2011-01-01
  • Javascript ES6中数据类型Symbol的使用详解

    Javascript ES6中数据类型Symbol的使用详解

    Symbol类型是es6新增的一个数据类型,Symbol值通过Symbol函数生成Symbol类型是保证每个属性的名字都是独一无二的,对于一个对象由对个模块构成的情况非常有用,本文主要介绍了Javascript ES6中数据类型Symbol使用的相关资料,需要的朋友可以参考下。
    2017-05-05

最新评论