之前写插件,碰到了需要压缩图片的场景,当时设计给到的参考对象是 tinyPng,压缩后的图片能达到 tinyPng 的效果即可(图片质量 压缩率)。
最初设想当然是直接用 tinyPng,但是现在 tinyPng 有免费数量限制,每月免费 500 张,超出计费。这当然是不能忍的,于是就找了 nodejs 的相关压缩工具,压缩 png 广泛使用的就找到以下两个 sharp.js、pngquant。
三个压缩工具现在来做个对比,主要是压缩率做个对比,首先用一个复杂的图片,如下
pngquant 质量范围设置为 0.7-0.8, sharpjs 质量设置为 80 最终压缩后
图片 | 大小 |
---|---|
pngquant | 1.1MB |
sharpjs | 1.4MB |
tinyPng | 962KB |
代码如下
1 | import imagemin from "imagemin"; |
看上去是 tinyPng > pngquant > sharpjs,但是我们把压缩率调整一下 pngquant 设置为 0.3-0.4 sharpjs 设置为 40,结果如下
| 图片 | 大小 |
| ——– | —– |
| pngquant | 861KB |
| sharpjs | 1.4MB |
| tinyPng | 962KB |
那么现在对问题就来到了 tinyPng 压缩效果是否比得上 pngquant 质量范围 0.3-0.4 了。这个时候就无法通过肉眼看需要了解一下压缩原理,通过数据说话了。通过网上的文章了解了一下 png 格式组成,参考如下[PNG 文件格式解析][https://www.ihubin.com/blog/audio-video-basic-11-png-file-format-detail/],大佬可以直接看[png spec](https://www.w3.org/TR/2003/REC-PNG-20031110/)这里通过一张简单图片来分析一下 pngquant 和 tinyPng 压缩后的图片差异以及优劣,原图如下
查看十六进制数据
所需要注意的数据点都已经标注在上面了,再看看pngquant和tinyPng所压缩后的图像,首先是pngquant的。
然后是tinyPng的
在这里我们可以看到,tinyPng把制定色域的sRGB和gAMA删掉了,而pngquant删掉了,这里会有一个大小的差距。我们可以试试吧这部分数据删除,可以发现,现在pngquant和tinyPng的差距就只有1byte。
看上去似乎就差了这么一点内容没有删除,但是pngquant的质量范围调小后会发现,压缩率下不去了,应该是图像过于简单,后续无法优化了。依然使用上述复杂图像进行再一次对比。pngquant质量范围为0.3-0.4
图片 | 大小 |
---|---|
pngquant | 868KB |
tinyPng | 963KB |
再次查看数据,pngquant 数据plte的长度是012f 也就是 303,tinypng的plte长度是 0300 也就是 768 ,说明pngquant 0.3-0.4的质量其实是比不过tinypng的,调整一下质量参数,大致调整到两者的plte数量相等,质量区间参数大约是0.7-0.8。这个时候他们的图片大小如下
图片 | 大小 |
---|---|
pngquant | 1.06MB |
tinyPng | 963KB |
此时他们的数据差距就比较大了,这个时候我们来看看数据差距在哪里。这个时候可以用png-chunks-extract 来获取所有的png的chunk大小。 处理结果如下所示。
图片 | 组成 |
---|---|
pngquant | { IHDR: 13, PLTE: 768, IDAT: 1117493, IEND: 0 } |
tinyPng | { IHDR: 13, PLTE: 768, IDAT: 961906, IEND: 0 } |
很明显了,主要差距在于IDAT,此时我们知道,一张png,大小一致的png图片,最终解析出来的数据大小应当也是一致的。问题就只有一个,那就是IDAT的压缩算法不同,tinyPng的压缩算法更好一点。
那么压缩算法哪家强呢?我们可以找找wiki。首先,png spec规定了png的IDAT压缩算法
Compression method is a single-byte integer that indicates the method used to compress the image data. Only compression method 0 (deflate/inflate compression with a sliding window of at most 32768 bytes) is defined in this International Standard. All conforming PNG images shall be compressed with this scheme.
而Wiki上找到了更高压缩率的算法实现是 7-zip的实现 链接如下 https://zh.wikipedia.org/wiki/Deflate
更高压缩率的DEFLATE是7-zip所实现的。AdvanceCOMP也使用这种实现,它可以对gzip、PNG、MNG以及ZIP文件进行压缩从而得到比zlib更小的文件大小。在Ken Silverman的KZIP与PNGOUT中使用了一种更加高效同时要求更多用户输入的DEFLATE程序。
此时我们只需要找到一个7-zip的实现库,给png的数据做压缩,然后分段(分段需要尽量的少一点,减少crc校验码和段长码的数量)。nodejs中可以使用 https://github.com/quentinrossetti/node-7z#commands这个库来进行7-zip压缩。
尝试了一下得到压缩后的数据为999 KB (1,023,096 字节)压缩率不及tinyPng但是确实是高于pngquant。当然这里只是跑了一遍压缩,没有做分段处理,但是即使分了段,按照tinyPng那样分个两段三段,数量也比pngquant小,而且一般来说数据量越大(压缩的原图越大),最终的结果大小差距也会越大,大致来讲效果还是高于pngquant的。
至此,终于算是了解了tinyPng的压缩原理,大家如果不想付费使用tinyPng,也可以使用pngquant和7-zip压缩达到一个效果类似tinypng,压缩率还不错的服务。
后续 ->
最近看到了一篇文章 Chrome插件:切图压缩工具,里面提到了一个叫做advpng 的压缩工具,用的就是上述思路。突然发现我思路有一点局限。一开始搜索压缩工具就不应该局限在nodejs,应当直接搜索图片压缩工具。如果是网页版压缩工具,可以使用puppeteer,如果是c++的工具,可以看看是不是能用node扩展。顺便的,如果看到有啥C++工具没有nodejs版本/胶水 ,可以试试搞一个,也算是这方面nodejs先驱(水github)