PNG 图片压缩对比分析

9,753 阅读11分钟
原文链接: mp.weixin.qq.com

背景与现状

随着版本的迭代,业务的增加,QQ音乐apk的大小已经超过25M,其中res目录占用的大小超过5.5M,所以提出了对安装包进行瘦身的技术需求。业务的增加导致图片越来越多,通过分析可以知道PNG格式图片是项目中数量最多的图片,关于PNG图片的介绍可以参考:PNG文件格式详解。为了实现减包任务,对图片进行压缩是很重要的一部分。

为了实现PNG图片的压缩,之前的处理方式是先在本地进行压缩,然后提交到SVN,再打包发布。一般采用在线压缩工具处理,将res目录下的PNG图片批量手动处理,这种方式容易出现的问题是:

1) 为了追求高的压缩率,容易出现一张图片重复压缩的情况,导致图片严重失真;

2) 不能自定义参数开发,无法满足开发需求;

3) 压缩效率比较低,每次发布时都需要人为进行一次图片的压缩。

压缩工具及原理分析

tinypng

1)原理介绍

根据官网tinypng.com/介绍,主要是使用Quantization的技术,通过合并图片中相似的颜色,通过将 24 位的 PNG 图片压缩成小得多的 8 位色值的图片,并且去掉了图片中不必要的 metadata(元数据,从 Photoshop 等工具中导出的图片都会带有此类信息),这种方式几乎能完美支持原图片的透明度。有部分文档指出tinypng同时采用了pngquant、optipng、advpng几种脚本。图片的压缩率能达到50%以上。

2)在线API

提供在线API供开发者二次开发,支持Ruby、PHP、Node.js、Python、Java等语言,其中Java库源码地址为tinify-java。但是,在线api需要申请api-key,并且对调用次数有限制,可以免费调用500次。在公司内部的开发网环境无法正常运行,无法通过API在线调用接口。

pngquant

根据官网pngquant.org/介绍,pngquant是国外的一个有损的PNG压缩开源库,提供了命令行形式和源码库形式。将24位或32位的RGBA PNG图转换成8位PNG图并保留透明度通道。通过这个库的转化可以显著减少png文件大小(通常减少70%)。生成的图片文件可以兼容所有现代web浏览器,在IE6下比24-bit PNGs也有更好的表现。它的特性有:

1)结合Vector_quantization算法生成高质量的色彩范围;

2)独特的自适应抖动算法,比标准的FloydSteinberg算法具有更强的抗噪性;

3)易与集成,提供了shell脚本,图形化界面,服务端库,PS插件;

4)具有快速模式,用于处理大批图片。源码库地址为pngquant

其他PNG压缩工具

网上流行的PNG压缩工具主要有ImageAlpha、ImageOptim、pngcrush、optipng、pngout、pngnq、advpng等。他们的对比可以参考以下文章;

1)John Wong的PNG图片极限压缩,介绍了ImageAlpha和ImageOptim两种压缩方法;

2)图片优化的那些工具,介绍了pngcrush、optipng、pngout、pngnq等。

各压缩工具的对比表格如下:


根据资料显示,tinypng、pngquant、ImageAlpha、pngnq都是有损压缩,基本采用的都是quantization算法,将24位的PNG图片转换为8位的PNG图片,减少图片的颜色数;pngcrush、optipng、pngout、advpng都是无损压缩,采用的都是基于LZ/Huffman的DEFLATE算法,减少图片IDAT chunk区域的数据。一般有损压缩的压缩率会大大高于无损压缩。

压缩对比

一些流行的PNG压缩工具的压缩率对比可以参照:常用PNG压缩工具压缩率对比。在参考以上文章的基础上,本文主要针对pngquant和tinypng做出了对比。

1.单个图片压缩对比

选取QQ音乐Android项目中占用空间最大的几个PNG图片进行压缩效果的对比,通过pngquant.exe脚本以及tinypng网站分别进行单个压缩,压缩率如下图所示:(pngquant使用默认压缩品质)

从表格中可以知道,tinypng的压缩率大概比pngquant的压缩率会高10%左右。其中pngquant压缩过程会出现比原来图片大的情况,所以在实际利用脚本压缩过程中需要对压缩后的图片和原来图片大小进行对比,如果出现变大的情况应该舍弃。

2.批量图片压缩对比

由于项目中存在大量的PNG格式图片,所以不可能单个单个进行压缩,需要通过批量压缩来进行,tinypng的批量压缩目前只能是在官网进行(由于在线API的开发会有图片数量受限以及开发网环境受限等),而pngquant的批量压缩可以通过自己完全自定义开发,调用压缩脚本进行压缩,在官网上可以下载windows和linux版本下的运行文件。

选取项目中大小4KB以上的106个PNG图片进行压缩实现,效果对比如下:(pngquant使用默认压缩品质)

从表格中可以知道,tinypng工具由于是采用网站在线压缩的方式,所以批量上传过程中容易出现上传不成功等错误,而pngquant采用的是本地脚本压缩,所以这个问题可以有效避免。由于tinypng采用了多种压缩算法,压缩效果会好于pngquant,大概可以多压缩12%左右,不过由于是有损压缩,所以在追求尽可能增加压缩比率的同时也应该考虑压缩后图片的显示效果。

3.本地运行脚本对drawable目录压缩

使用pngquant脚本压缩资源目录res下的drawable、drawable-hdpi、drawable-ldpi、drawable-mdpi、drawable-xhdpi、drawable-xhdpi-v21、drawable-xxhdpi、drawable-xxxhpi等8个文件夹,在多线程的情况下,共耗时17s940ms。主线48322版本的资源目录采用各种压缩品质(通过设置—quality参数实现)进行压缩,对比效果如下图,综合考虑压缩率和压缩后效果,最终采用品质为90的压缩方案,可以减包1.97MB。


4.结论

在综合比较tinypng和pngquant的基础上,项目最终考虑使用pngquant来对PNG图片进行批量压缩,主要考虑有:

1)虽然在pngquant采用默认压缩品质的情况下压缩率会低于tinypng,但是tinypng是在线压缩工具,不好自定义控制与维护,tinypng压缩后的显示效果还有待进一步验证;

2)同时,pngquant脚本可以自定义压缩品质,采用压缩品质更低(比如90)的情况下,压缩率会高于tinypng,而且pngquant是开源的,容易维护,风险可控。

使用pngquant的压缩流程

1.gradle编译

项目PNG图片压缩类存放在工程目录buildSrc下,是使用groovy开发的ImageCompressionTask.groovy(由haodongyuan开发),在build.gradle或build_server.gradle中通过以下方式引用:(手动运行compressImages任务即可实现压缩处理,其中quality表示压缩品质,compress表示是否开启压缩)

import com.tencent.qqmusic.ImageCompressionTask

task compressImages(type: ImageCompressionTask) {
 multiThread = true
 verbose = false
 backupRoot = new File('res-backup')
 sourceRoot = rootDir
 quality = 90
 compress = true
 folders = [
   "res/drawable",
   "res/drawable-hdpi",
   "res/drawable-ldpi",
   "res/drawable-mdpi",
   "res/drawable-xhdpi",
   "res/drawable-xhdpi-v21",
   "res/drawable-xxhdpi",
   "res/drawable-xxxhdpi"
]
}

2.RDM自动化编译

如果要做成gradle自动化编译,除了在gradle中加入上面代码外,还需要使用下面语句做成任务依赖,需要在编译apk之前执行压缩任务:

copyNativeLibs.dependsOn compressImages

并且同时关闭Android编译工具AAPT自带的压缩效果:

aaptOptions {
    cruncherEnabled = false
}

pngquant的Linux版本的运行要依赖一些是so文件,需要在build.sh文件中加入依赖的so库:

#加入动态加载pngquant压缩的依赖so库
export LD_LIBRARY_PATH=$(cd `dirname $0`; pwd)/scripts/Tools/pngquanti/linux/lib
ln -s $(cd `dirname $0`; pwd)/scripts/Tools/pngquanti/linux/lib/liblcms2.so.2.0.8 $(cd `dirname $0`; pwd)/scripts/Tools/pngquanti/linux/lib/liblcms2.so.2
ln -s $(cd `dirname $0`; pwd)/scripts/Tools/pngquanti/linux/lib/ld-2.23.so $(cd `dirname $0`; pwd)/scripts/Tools/pngquanti/linux/lib/ld-linux-x86-64.so.2
ln -s $(cd `dirname $0`; pwd)/scripts/Tools/pngquanti/linux/lib/libpng15.so.15.27.0 $(cd `dirname $0`; pwd)/scripts/Tools/pngquanti/linux/lib/libpng15.so.15

自动化编译过程压缩脚本运行的流程为:


从压缩流程图中可以知道,在项目资源目录下PNG图片数据过多时,默认应该采用多线程执行压缩脚本,为了避免出现重复压缩的情况,在进行压缩之前需要读取图片的压缩信息,压缩过的不再压缩,同时,压缩完成后,需要对压缩处理过的图片写入压缩信息,方便下一次读取。由于pngquant脚本的特殊性,需要判断压缩后文件是否大于原始文件,大于则需要删除并记录。

3.优势

使用pngquant自动压缩的优势主要有三点:

1)选择pngquant做为png图片压缩脚本,可以在不影响图片显示效果的基础上最大化压缩(采用压缩品质90);

2)同时通过groovy脚本对图片进行批量压缩处理与加入额外压缩信息,可以提高压缩效率并防止重复压缩;

3)做成RDM自动化压缩后,可以有效减少开发人员的工作量,方便后期维护。

JPG的压缩

JPG的压缩途经有,公司内部的优图工具:优图JPG压缩;在线jpg压缩工具tinyjpg:tinyjpg.com/;脚本工具jpegtran:jpegclub.org/jpegtran/等。由于优图工具是无损压缩,有专业的优图团队进行维护,并且压缩率可以达到20%以上,而tinyjpg是有损压缩,会出现重复压缩的情况且不方便维护,所以项目优先考虑采用优图工具进行压缩。

选取工程res目录下的49个JPG文件进行压缩对比,可以减少0.23MB,效果如下:

第三方jar包中的图片压缩

通过观察apk包会发现,assets目录下会存在一些新生成的图片目录,包括common、drawable、images、Recommend目录,通过分析发现,这些png图片来自项目引用的第三方jar包,其中TVK_MediaPlayer-V3.6.0.10721.jar会产生common、Recommend目录,videoad-sdk-1.7.2-20160527.jar会产生images目录,weiboSDKCore_3.1.2.jar会产生drawable相关目录。可以知道,这部分图片的优化空间有限,重点需要对TVK_MediaPlayer、videoad-sdk、weiboSDKCore三个jar包手动进行PNG图片压缩。总共可以减少41KB。

部分jar包PNG图片压缩减少的大小对比:


总结

本次工程图片压缩过程,主要学习了PNG图片的主要压缩脚本(tinypng/pngquant/pngout)以及JPG图片的压缩工具(优图/tinyjpg),经过对比最终选择pngquant与优图作为工程PNG和JPG图片的压缩工具。为了实现PNG图片压缩的自动化管理,将pngquant脚本集成到RDM编译,主要遇到的问题有:

1)groovy脚本执行Linux命令,Linux环境下运行bin文件,需要首先使用chmod赋予权限;

2)gradle的编译顺序问题,根据编译过程的依赖链关系,需要将compressImages任务放到最开始;

3)pngquant的运行需要依赖一些so库,所以需要通过重设“LD_LIBRARY_PATH”来定向加载so库,同时,通过ln命令来设置so库的软连接关系;

4)路径识别问题,一开始总是无法找到图片路径,后面发现是在压缩脚本路径引用的时候多加了双引号(“”),导致在RDM平台无法识别;

5)RDM自动化编译时间增加的问题,由于res目录下的JPG图片和第三方jar包的PNG图片都是本地手动压缩处理的,不用考虑时间问题。res目录下的PNG图片是在RDM平台自动压缩处理的,要考虑时间成本,如下图所示,加上压缩脚本,大概会多耗时26秒左右。


通过本次图片压缩优化,可以达到以下几个目的:

1)res目录下的PNG可以减少1.97MB,PNG的减包效果如下图,res目录下的JPG图片可以减少200KB,第三方jar包(assets)目录的PNG图片大小可以减少40KB。总共可以减包2.22MB

PNG压缩前:

PNG压缩后:

2)实现PNG图片自动化压缩处理,大大提高了开发效率,方便维护管理;

3)通过groovy脚本可以实现自定义压缩品质与写入压缩信息,有效控制压缩率和智能判断,避免二次压缩的出现。

参考

[1]blog.csdn.net/bisword/art…

[2]tinypng.com/

[3]pngquant.org/

[4]www.jianshu.com/p/a721fbaa6…

[5]johnwong.github.io/showcase/20…

[6]ued.ctrip.com/blog/image-…

[7]advsys.net/ken/utils.h…

[8]jamiemason.github.io/ImageOptim-…

[9]my.oschina.net/shede333/bl…

[10]tinyjpg.com/

[11]www.oschina.net/translate/4…