压缩 gif 图片的方法(命令行)

(20.12.2 更新) 有条件的话看一下上面几篇参考文章:

很长且都是英文,但讲清楚了 gif 的生效和渲染及优化机制。

如果原图还是 mp4 格式的话,可以通过 ffmepg 压缩。但如果已经是 gif 成品了,就相对麻烦一点。但严格意义上,不推荐修改 gif 的尺寸。参见参考文章 2 的 resize 一节

通过 ffmpeg 减少帧数和分辨率

先使用 ffprobe 查看 gif 帧数和分辨率

我们可以看到,这个 gif 分辨率为 360*360,帧数为 10fps

可以通过 scale 和 -r 进行压缩

1
ffmpeg -i 0.gif -r 10 -vf scale=350:-1 0_change.gif

然后。。。尴尬的事情发生了,压缩完以后体积反而膨胀了?试着更改分辨率,发现一旦更改分辨率体积都会膨胀(并且已经开了 transdiff 透明参数,没开的话甚至会膨胀到 18m),于是只能另求它法。

(20.12.3 更新) 大概是因为:

Problems with Resizing Animations

The biggest problem with resizing GIF animations is that the “-resize“ operator is designed specifically to make the resulting images as close to ideal (after the resize) as possible. It does this by merging and generating lots of additional colors in the image to make it look better.

The resulting images are far from ideal for saving to the limited GIF file format. With GIF’s limited color table, this results in heavy Color Reductions in the resized images. For a single GIF image that is not so bad, but for a GIF animations, the default Error Correction Dithering of the reduced color set produces problems, in ‘dither noise’ between frames, and in turn a bad frame optimization for final file size.

It is even worse when transparent colors are also being used, which is a common practice for typical GIF animations used for web pages. Transparency is also commonly used for Compression Optimization techniques, for animations that would otherwise not need it.

What happens is that “-resize“ produces semi-transparent pixels in the overlay images. Then when the images are saved back to a GIF file format, these pixels are then converted to either fully-transparent or fully-opaque, both producing major color distortions in the resulting animation.

If any form of optimization is used… frame, transparency or LZW… then the transparency effects will basically result in a disastrously resized GIF animation. That is the facts, Jack! So you will need to live with it.

Even if you avoid using “-resize“, by using “-sample“, you will still have major problems unless you “-coalesce“ the animation first.

调整动画大小的问题
调整GIF动画大小的最大问题是,”-resize “操作符是专门设计用来使产生的图像尽可能地接近理想(调整大小后)。它通过在图像中合并和生成大量额外的颜色来使其看起来更好。
所产生的图像远不是理想的保存到有限的GIF文件格式。由于GIF的颜色表有限,这就导致在调整后的图像中出现了严重的颜色还原。对于单个GIF图像来说,这还不算太坏,但对于一个GIF动画来说,默认的错误校正抖动的还原颜色集会产生问题,在帧之间的 “抖动噪声”,反过来对最终文件大小的帧优化也不好。
当同时使用透明色时,情况就更糟糕了,这也是用于网页的典型GIF动画的常见做法。透明色也常用于压缩优化技术,用于本来不需要的动画。
发生的情况是,”-resize “会在覆盖图像中产生半透明的像素。然后当图像被保存回GIF文件格式时,这些像素就会被转换为全透明或全不透明,都会在生成的动画中产生重大的色彩失真。
如果使用任何形式的优化……帧,透明度或LZW……那么透明度效果基本上会导致灾难性的调整GIF动画大小。这就是事实,杰克!所以你需要忍受它。所以你需要接受它。
即使你避免使用”-resize”,通过使用”-sample”,你仍然会有很大的问题,除非你先”-coalesce “动画。

所以也许是因为 ffmpeg 转换后 gif 的各类优化手段失效了。

(20.12.15 更新) 也许是码率原因。用新版 ffprobe 探测一下视频就知道了。

另外 ffmpeg 因为算法原因转换后的视频宽和高必须是偶数,如果要缩小分辨率的话要用一些奇特的手段:

1
ffmpeg -i 0.gif  -b:v 5000k -vf "scale=trunc(iw*0.6*0.5)*2:-2" 0_change.gif

此处的 trunc 意为取整。-2 在此处指指定了另一项参数后使本项参数自动缩放相同比例且能被 2 整除。0.6 就是让这个视频等比缩放百分之 60 啦。

但这样大概率是不行的。。。ffmpeg 没有办法很好的处理 gif 到 gif 之间的码率,可能会导致写在参数里的码率和实际码率有很大的偏差。更好的办法是先转为 mp4:

1
ffmpeg -i 0.gif  -b:v 1000k -vf "scale=trunc(iw*0.6*0.5)*2:-2" 0.mp4

如果 gif 带有透明度,就得转成 mov,但这样就没法调码率了(不生效)。不过实测就算是不调码率最后生成的 gif 还是小很多。

1
ffmpeg -i 0.gif  -movflags faststart -vcodec qtrle -vf "scale=trunc(iw*0.6*0.5)*2:-2" 0.mov

再转为 gif,顺便还能优化一下(这里这个命令是下面的 高级 magisk 压缩 中命令的精简版)

1
convert -layers optimize -quiet  0.mp4 -ordered-dither o8x8,8,8,6 +map 0_change.gif

进一步缩小,可以考虑降帧,需要配合-r-delay参数:

1
2
ffmpeg -i 0.gif  -r 10  -delay 10 -b:v 1000k -vf "scale=trunc(iw*0.6*0.5)*2:-2" 0.mp4
convert -layers optimize -quiet 0.mp4 -ordered-dither o8x8,8,8,6 +map 0_change.gif

如果原来留有视频原件,也可以参考上面的格式缩小分辨率和帧数直接转换(视频转 gif 码率显示问题不大)。

通过 magick 压缩

参考文章:

需要先安装 ImageMagick 才能使用 convert 命令

1
apt install ImageMagick

然后运行命令

1
convert xxx.gif -fuzz 15% -layers Optimize xxx2.gif

压缩是压缩了,人物都糊掉了。。。

试图减少 fuzz 数值,发现 fuzz 数字是有一个生效阶梯的,具体来说,如果在百分之 9 以内,制作出来的动图和原来大小一样;调到百分之 9 的时候,大小从 6.3m 降低到了 4.1m;后面的就懒得具体测试了,不过原来的参数 15 就更小了。百分之 9 左右,画质还能接受,但。。。这可调参数也太少了。

另外提一点,convert 和 ffmpeg 联合使用是可以调整 gif 速度的(副作用是 gif 的透明度信息会丧失。如果 gif 本身是背景透明的图像则不适用于本方法。根据观察 -f image2pipe 会先把色彩格式转换为 yuv 而不是继承原来的 brgb 色彩格式,使用 pipe 的话此问题暂时无解,通过拆分成两个命令可解,详见 imagemagick 日志):

1
ffmpeg -i xxx -vf scale=-1:-1 -r 10 -f image2pipe -vcodec ppm - | convert -delay 5 -loop 0 -layers Optimize - xxx2.gif

利用 gifsicle(也称为 giflossy)

参考文章:

先安装 gifsicle

1
apt install gifsicle

先用自动参数进行压缩(-O3 有损,-O2 无损)

1
gifsicle -O3 xxx.gif -o xxx2.gif

发现动图从 6.3m 下降到了 6m,效果还不错

不过当我想做其他操作的时候。。。问题来了

使用 scale 参数等比缩放

1
gifsicle xxx.gif --scale 0.9 -o xxx2.gif

压缩到 5.2m,但是像素细节排列没有原来好了,而且到黑色背景下就炸掉了(irfanview 各种显示错位,除非指定 –resize-method 为 lanczos 类的才可以解决,但是那样 gif 体积又变大了)。。。

用 ps 缩放 0.9 的 gif 做对比(也是 5.2m)

更要命的是,如果不用等比缩放裁剪:

1
gifsicle xxx.gif --resize 350x-350 -o xxx2.gif

效果更差,各种残影。。。

另外发现,gifsicle 有一个测试命令,类似于 ffmpeg 的 ffprobe

1
gifsicle -I xxx.gif

原图的 gifsicle 参数是这样的:

经过 ps 降低分辨率,效果还不错的 gif,参数是这样的

另外,从这里我们可以看到,gif 每帧的延迟是 0.1s(10ms),由此我们也可以用 gifsicle 来调节 gif 播放速度。

1
gifsicle -d 5 xxx.gif -o xxx2.gif

这里的「5」单位是 ms。这个数字要求是整数,而且其实最小是 2,因为 1 出来的效果和原图一样(虽然用 gifsicle -I 出来的参数 delay 上确实写的是 0.01s)

(20.12.14 更新) 还可以使用 lossy 参数

1
gifsicle -O3 --lossy=80 --colors 256 0.gif -o 0.gif

(20.12.14 更新) 高级 magick 压缩

如果留着转换为 gif 的视频原件

当然把成品 gif 转回 mov 也不是不可以,但不推荐。建议用原件通过 ffmpeg 转换为减少帧数和分辨率的视频后再进行这一步。

  1. 看一眼视频怎么调整色彩才能不超过 256 色(gif 一帧最多支持 256 色,可以通过抖色和使用帧独立色板增加颜色,但后者会显著增加 gif 大小)

    1
    convert -quiet 0.mp4 -ordered-dither o8x8,4 -append -format %k info:

    如果显示的结果小于 256,那就继续调大 -ordered-dither 参数的最后一位。反之亦然。比如上述命令结果是 60,那么:

    1
    convert -quiet 0.mp4 -ordered-dither o8x8,5 -append -format %k info:

    重复上述步骤。直到得出的数字 +1 后运行命令就大于 256 为止。

  2. 假设使用上述步骤得出了 7 这个数字,那么:

    1
    convert -layers optimize -quiet -delay 1 0.mp4 -ordered-dither o8x8,7 +map 0.gif 

    此时优化了帧框架和颜色,剩下透明度还可以通过更新的 gifsicle 再次缩小体积(见上文)。

未留有原件

框架优化

大部分时候使用 -layers optimize 即可。其他的框架优化方式参见参考文章 2。

1
convert 0.gif -layers optimize  0_change.gif 

颜色优化

查看源动图总颜色数:

1
convert 0.gif +append  -format "Total Number of Colors: %k"  info:

查看源动图每帧颜色数:

1
identify -format "Colors in Frame %p: %k\n"  speed.gif

查看源动图独立色板数(需安装 giftrans,deb 系统下可使用 apt 安装):

1
giftrans -L speed.gif 2>&1 | grep -c "Local Color Table:"

优化:

1
convert 0.gif ( -clone 0--1 -background none +append -quantize transparent  -colors 63  -unique-colors -write mpr:cmap    +delete \) -map mpr:cmap      0.gif

上面的命令是将图像色彩缩水至 64 色。转换时间略久。

转换视频时使用的 -ordered-dither 参数和 -map 参数不能用于 gif 直接对转,就算和上面的那个强制色彩缩水命令搭配使用也不行,只要用其中任意一个参数就会导致独立色板出现。解决方法是使用 miff 格式进行中转(尽量不要直接使用 gif 中转,可能会导致色彩信息在中转时部分丢失):

1
2
convert -delay 1 0.gif  -ordered-dither o8x8,7  0.miff
convert 1.miff +map 10.gif

透明度优化

可以用 -layers OptimizeTransparency 参数:

1
convert 0.gif -layers OptimizeTransparency +map   0.gif

不过似乎 OptimizeTransparency 已经被包含在 optimize 中了。

更建议使用上文之前写的利用 magick 压缩和利用 gifsicle 压缩,这两个同样是透明度优化的方法。

以上三种优化可以合并使用,也可以排成队列使用,不过:

  1. 再次提醒在颜色优化这步需要用 miff 格式中转。
  2. 建议 -layers optimize 在最后一步做。

修改 gif 分辨率(不推荐)

有两种方法,-resize

1
convert 0.gif -resize 60% 0_change.gif

或是 -sample

1
convert 0.gif -sample 60% 0_change.gif

但算法并不好,容易导致花屏和拖影。另外使用这两个参数的时候尽量用百分比缩放,用分辨率缩放可能导致部分画面错位(如下面这个命令)。由于上面的 gifsicle 也部分使用了 magick 的算法,我猜测 gifsicle 等比缩放后的错位也来源于 magick。缩放分辨率推荐使用 ffmpeg。

1
convert 0.gif -sample x480 0_cuowei.gif