为了节省资源,我们希望在不同分辨率的前端上可以获取到不同尺寸的素材资源。由于最近的业务中使用的所有素材都是PNG格式的图片,并且对图片效果要求比较严格,并没有在市面上找到可以直接使用的服务,于是自行实现整个服务。
TL;DR
首先使用ImageMagick对图片进行缩放(假设输出是64*64的图片)和去模糊,再使用pngquant对缩放后的图片进行有损压缩(假设原图颜色数量为128)。
convert src.png -resize 64x64 resize.png
convert resize.png -unsharp 1.5x1+0.7+0.02 output.png
pngquant 128 -f --ext .png output.png
工具的选择的心路历程
图片缩放
最早使用了号称命令行上的Photoshop的ImageMagick来对图片进行缩放。当然功能可以实现,只是发现图片尺寸裁剪之后,文件本身的体积反而变大了。后来尝试了各种不同的工具,包括Java的ImageIO,Python的PIL,以及知名图片压缩服务TinyPng的SDK。不同的工具不仅裁剪后文件大小不同,效果也有所差异。其中TinyPng表现最好,大部分时候图片被缩小后文件尺寸也会随之减小,但是也有个别图片仍然存在同样的问题。
在查阅到相关资料png 裁剪时遇到问题,得到了较好的解释:
原图类型 Type: PaletteAlpha,Base type: TrueColorAlpha,本身就是经过压缩和优化了的。
resize 时,会进行各种差值,信息也会变多,类型也变成了 TrueColorAlpha。
这里涉及到了PNG文件具体格式相关资料,可以参考PNG-8、24、32区别介绍和PNG的故事。
图片颜色信息减少
在确认造成文件变大的原因是颜色信息增多之后,自然是选择将颜色信息适当减少。由于我们要处理的图片是缩小后的图片,需要的颜色应该不会比原图要更多,所以我们目标是将缩小后的图片的颜色数较少到和原图的颜色数相同。
这里我们使用一个有损压缩工具pngquant来压缩图片和减少颜色数量。选择他是为数不多能直接改变颜色数的压缩工具。
经过缩小和压缩之后,裁剪后的图片文件已经明显小于原图。相关文件的具体信息可以通过ImageMagick的identify命令来直接查看:
identify -verbose src.png
到这一步大部分需求已经实现,但是却在个别边缘分明的素材上发现了明显的边缘模糊,导致视觉效果不佳。
图片去模糊
首先是排查原因。
起初我以为是我在压缩的过程中,过分减少了输出图片的颜色数量,导致细节丢失。后来发现那些边缘分明的图片在resize时就已经出现了模糊现象,那问题自然是在ImageMagick的使用上。
之后在ImageMagick的官方文档中找到了一片《Resampling Filter》,介绍了在图片缩放时候的会遇到的各种问题和各种参数的使用。例如:
在模糊问题上更是有另外一片文档《Resize Unsharp》来说明如何处理resize过程中的模糊问题。参照文档使用下列命令可以很好地锐化图片:
convert resize.png -unsharp 1.5x1+0.7+0.02 output.png
至此将三个步骤整合在一起,就可以达到令人满意的png缩小效果。
相关原理
Resize
Resize的实际处理过程叫做Resampling,即重采样。简单来说,可以看作操作一个数组,增加或者减少这个数组的元素,并且让处理的结果在人眼看起来不那么糟糕。其实这是一个非常复杂的问题,也有人专门从事这方面的研究,这里不展开讨论,还是要看《Resampling Filter》,里面介绍了各种Resampling的策略,也会说明为什么图片缩小后颜色数量会增多。
由于这个问题的复杂性,所以没有一套完美的方案可以解决所有场景下素材的resize,毕竟有太多的策略和参数可以选择,最终的方案是需要根据需求进行取舍的。
有损压缩
处理中关键的一部是使用pngquant进行有损压缩。为什么颜色变少之后文件尺寸迅速下降?首先是因为颜色数量如果少于255,输出的图片将可以保存为PNG-8格式,这样就减少了像素深度。另外主要缩小图片的原因还是PNG本身压缩过程中的Filtering和DEFLATE算法起作用。简而言之,如果一个数组内相似的元素越多,那么DEFLATE就可以将其压缩得越小。而Filtering步骤可以用差分编码的编码方式,即是记录当前数据和某个标准值的差距来存储当前数据。
比如说有这么一个数组[99, 100, 100, 102, 103],我们可以将其转存为[99, 1, 0, 2, 1]。转存的规则就是以数组第1位为标准值,标准值存储原始数据,后续均存储以前1位数据的差值。PNG一共支持5种Filtering,基本原理相似。通过类似这样的转换之后,DEFLATE就可以更好得压缩数据。
END
特别鸣谢:V友icyalala
参考资料:
PNG-8、24、32区别介绍
Resampling Filter
减少PNG文件的大小
PNG的故事