找了一圈没有什么开箱即用的GIF压缩库,索性自己搞一个。
理论思想
其实GIF的压缩,跟其他格式图片压缩差不多,无非就是降低分辨率,减少颜色值。只不过GIF的特殊性,可以有其他一些方面来进行压缩:
- 减少调色盘数量
参考前一篇文章所分析的GIF格式,GIF图有一个颜色表的概念,存放着具体的颜色,而具体的每一帧存的是每个像素点的颜色值在颜色表中的索引。颜色表有全局颜色表,每一帧也有自己的局部颜色表。如果减少颜色数量,那么颜色表大小就会减少。这里用一个工具 gifsicle 来尝试对一张gif图进行压缩看下效果。
原图:
通过命令
gifsicle –colors=64 jr1.gif > jr-color64.gif
gifsicle –colors=2 jr1.gif > jr-color2.gif
减少调色盘大小后效果
调色盘64:
调色盘2:
大小分别压缩到了4.2M 1.6M
可以看到,调整调色盘中颜色数量是效果非常明显的,即使只剩两种颜色,还是不会影响理解GIF图内容的,只剩色彩差异很大,只是将颜色压缩至64,也会显得很明显。
- 帧复用
在一些背景不动,只有前景物体在动的GIF,可以只在个别帧保留背景,其他帧只存储相对于这些帧之外有变化的部分。比如下面这张GIF图:
背景始终是蓝色,动的只是前景中的一小部分,用以下命令:
gifsicle -I –cinfo –sinfo –xinfo mm.gif
可以看到:
没有局部颜色表,只有全局颜色表,因为复用的大部分颜色,部分帧的大小就很小了。
- 降低每一帧图像的分辨率
这个是最简单直接的方法了,直接解码出所有帧图像,降低分辨率后再重新合成,自然能减少大小。
- 抽帧
抽帧也很好理解,就是简单粗暴的从原GIF图中去掉几帧。以这张图为例:
用以下命令
gifsicle -b nyan.gif –delete ‘#1’ ‘#3’ …
去除掉一半的帧后,效果:
大小也从 34K 减小到了 17K,只不过因为没有处理延时,所以有很明显的帧率变化。
实际做法
如前所述,大致有四种方法对GIF做压缩,但是在实际应用当中,各有优缺点。
首先,第一种方法,减少调色盘数,或将局部调色盘去掉统一改为全局调色盘,这样处理虽然对压缩很有效,但是对图像的显示效果影响很大,不能保证对所有场景都适用,且在开发商实现成本较高。
第二种方法,对帧进行复用,效果上没有什么缺点,但是只在特定场景下压缩率较高,并不适用所有图片,且实现上需要依赖一些算法来找到可复用的像素和颜色。
第三种方法,降低分辨率,大体来说是属于较好的方法了,对图像质量的影响也不大。
第四种方法,抽帧,抽帧虽然效果更好,但是有两个问题。一个是抽帧的规则不好定义,抽取间隔过多会导致压缩后图像效果有很明显卡顿掉帧。一个是因为部分GIF图并不是每一帧都有全部图像详细的,可能正如前面所说,他只记录了当前帧发生变化的部分,所以如果保留了这种帧而去掉了完整的帧的话,也会显得很奇怪。
所以最后我的做法是,抽帧+降分辨率。先对原GIF图进行解码,将每一帧的图像信息都进行还原,之后进行一次抽帧,对保留的帧再进行缩放降低分辨率,这样能最大限度的既保证图像质量又能有较高的压缩率。
代码实战
GIF的编解码也是用的Glide,具体可见之前的文章。
1 |
|
有了编解码后,重点就是压缩了:
1 | private const val GIF_COMPRESS_SIZE_RATIO = 0.8f |
总结
以上就是我在Android端GIF压缩的一些实践经验,这个方案还是有很多可以优化的地方,比如可以通过在解码GIF时获取每一帧的 dispose
字段,动态的判断当前帧在抽帧时是否可以被遗弃,或者针对帧复用的情况做特殊处理等,这里我只是先提供了一个简单的压缩方案,有需要可以直接取用或者再进行拓展。大家有什么建议或者问题也欢迎指出,感谢!