前言


这里必须先介绍 webp , 一种神仙级别的图片格式。



参考上图,同一张图,在允许有损压缩的前提下

格式 容量
png 1.4 mb
jpg 489 kb
webp 98 kb


而且肉眼几乎看不出明显区别。
这个放在一个网站或者app中,速度就可以拉开5~14倍的差距,而且不牺牲用户体验,还可以节省运营的带宽和流量。

一般主流的网站或APP中的图片格式为

格式 透明 清晰度 压缩率 动图
png 支持 高清 低压缩率 不支持
jpg 不支持 有损 高压缩率 不支持
gif 支持 有损 中压缩率 支持

而webp另外一个开挂的能力就来了,他支持以上所有特性,且同时保持着极高的压缩率

格式 透明 清晰度 压缩率 动图
webp 支持 无损/高/中/低 皆可 神仙级压缩率 支持



可以说,这种有百利,无一害的技术,理论上应该早就普及了。
毕竟10年前就已经由谷歌发布了。
并且早早的就在chrome浏览器,以及各大图标编辑处理显示软件中得到支持。


BUT !!!

他喵的 FirefoxSafari 一直迟迟不支持,IE更别谈了。

在此之前为了兼容不支持 webp 的浏览器,需要特地写2套代码,准备2套图片,反而浪费资源

好消息是就在2020年, FirefoxSafari 也相继开始支持 webp

具体版本如下

其中比较显眼的是 Safari 14 ,显示部分支持,因为需要系统同时升级到 mac os 11 bigsur 搭配 Safari 14



折腾

承接上文,后续就要考虑如何将图片转换为 webp
较为原始的方法可以是用 linux 安装 webp 工具,再使用脚本转换
https://linux.cn/article-12193-1.html
亦或者用 Java ,在 javahome 中加入 webp 的库文件支持
https://my.oschina.net/who7708/blog/2878444

两种方案我都浅尝了一下,发现一个共同的缺点,即:参数都只有 质量 0 - 100 一个参数
那么如果需求是将 1928 * 1080 png 的图片 转换为 600 * 360 75% webp 的缩略图,
就需要

  1. 先转换为 600 * 360 jpg
  2. 再转换为 75% webp

劳民又伤财


最后我意外发现 Java 图片转换工具 thumbnailator ,现已支持webp,且支持全套参数调整。
起初仅通过阅读源码可以发现允许输出以下5种格式,JPEG、JPG、GIF、WBMP、PNG、WEBP,因此得出thumbnailator可以支持webp的结论
然额,一执行就报

  1. Exception in thread "main" java.lang.IllegalArgumentException: Specified format is not supported: webp
  2. at net.coobird.thumbnailator.Thumbnails$Builder.outputFormat(Unknown Source)
  3. at com.zzzmh.utils.ImageUtils.main(ImageUtils.java:23)

实际上用我蹩脚的英文也看出来官方文档和github issues中也说了,并不直接支持webp
那到底要怎么才能让他支持webp呢?

就是要搭配这玩意的依赖 webp-imageio-core
https://github.com/nintha/webp-imageio-core
需要注意这玩意没有maven中央仓库的依赖!
他的github中写了,要先下载jar包 webp-imageio-core-0.1.3.jar
放到项目根目录 libs 文件夹下 (没有lib就创建一个)

然后再maven中用以下方式引入本地依赖

  1. <dependency>
  2. <groupId>com.github.nintha</groupId>
  3. <artifactId>webp-imageio-core</artifactId>
  4. <version>{version}</version>
  5. <scope>system</scope>
  6. <systemPath>${project.basedir}/libs/webp-imageio-core-{version}.jar</systemPath>
  7. </dependency>



当然如果你不是maven项目,可以用java最原始的方法引入依赖
就是下载这2个jar包,并且在开发工具中配置依赖,添加jar包路径即可
webp-imageio-core-0.1.3.jar
thumbnailator-0.4.14.jar

2020/03/18补充:目前经测试,webp-imageio-core在windows下无法正常工作!已有github issues,但仍未得到解决,等我发现解决方案,再到这里更新
这里目前调查结果是
如果作者不更新,无法直接在 windows 下使用 webp-imageio-core
个人更推荐在mac或linux下使用,同样硬件的情况下,算力也会更强

如果一定要在windows下用,需要先让 Java 支持 webp
类似这篇文章的方案
https://my.oschina.net/who7708/blog/2878444



实现

参考刚才的需求
1928 * 1080 png 的图片 转换为 600 * 360 75% webp 的缩略图

最终代码如下


maven方式引入依赖
(省略了 webp-imageio-core-0.1.3.jar 的部分,请参考上一个段落)

  1. <dependency>
  2. <groupId>net.coobird</groupId>
  3. <artifactId>thumbnailator</artifactId>
  4. <version>0.4.14</version>
  5. </dependency>



Java 代码如下

  1. public static void main(String[] args) throws IOException {
  2. Thumbnails.of("C:\\test\\input.png")
  3. .size(600, 360)
  4. .outputQuality(0.75f)
  5. .outputFormat("webp")
  6. .toFile("C:\\test\\output.webp");
  7. }

亲测 3M的高清原图 可以压缩至30kb的预览图 jpg则需要 60kb左右



填坑

说一个稍微深入一点的用法

1. 如果设置了宽度高度,如果比例不同,只会满足其中一个,另一个则会小于设置的数值,原图如果高度超出需求的比例,则高度压缩到设置成的高度,宽度会小于设置的宽度,反之也是以此类推。如果你期望的是宽度必须是1920,高度可以是 1920 * 1080 或 1920 * 1280 等等,那就只设置一个宽度即可。


2. 例如我需要一个缩略图,无论原图如何,缩略图都要设置成 450 * 300,如果原图比例 比 3:2 更宽,则截掉2边,拿中间,如果原图更窄,则截掉上下取中间。
最终没有找到直接实现的现成方法,于是自己写了一个


思路如下

  1. 最难的就是这里 需要考虑长短边 然后裁切出需要的部分
  2. 例如 1920 * 1080 的图片
  3. 转换 450 * 300
  4. 要么变形
  5. 要么会转换出 450 * 253
  6. 这时候就需要裁切 首先计算出
  7. 长边裁切 1080 / 300 * 450 = 1620
  8. 短边裁切 1920 / 450 * 300 = 1280
  9. 显然 1080 无法裁切出 1920 * 1280
  10. 只能是1920 裁切成 1620 * 1080
  11. 4个参数可以推得出
  12. (1920 - 1620) / 2 = 150
  13. 150 , 0 , 1620 , 1080
  14. 意思分别是 width 150 height 0 开始截取,截取一个width 1620 height 1080的方框
  15. 最终得出两套公式 分别用于长边4个参数和短边4个参数
  16. 字太多懒得打,直接看Java代码即可


核心代码如下

  1. Thumbnails.Builder<File> builder = Thumbnails.of(inputFilePath)
  2. .outputQuality(quality)
  3. .outputFormat(format);
  4. // 这里需要用到传入的高度和宽度 我这里传入了integer类型,你可以直接传入int 就不需要.intValue()
  5. int ogHeight = originHeight.intValue();
  6. int ogWidth = originWidth.intValue();
  7. // 这里计算原图比例和需求比例哪个更宽
  8. // 这里的CalculatorUtils类就是java计算类(防止double丢失精度),我在后面会补上代码
  9. int compare = CalculatorUtils.compareTo(CalculatorUtils.div(ogWidth, ogHeight), CalculatorUtils.div(width, height));
  10. // 这里根据2种情况进行截图,获取到合适的区域
  11. if (compare == 1) {
  12. double tempWidth = CalculatorUtils.mul(CalculatorUtils.div(ogHeight, height), width);
  13. double start = CalculatorUtils.div(CalculatorUtils.sub(ogWidth, tempWidth), 2);
  14. builder.sourceRegion((int) start, 0, (int) tempWidth, ogHeight);
  15. } else if (compare == -1) {
  16. double tempHeight = CalculatorUtils.mul(CalculatorUtils.div(ogWidth, width), height);
  17. double start = CalculatorUtils.div(CalculatorUtils.sub(ogHeight, tempHeight), 2);
  18. builder.sourceRegion(0, (int) start, ogWidth, (int) tempHeight);
  19. }
  20. // 防止小数点造成意外,这里强制拉伸下
  21. // 上面写了2种情况,其实还有第三种,就是2者比例完全相等 compare == 0 , 不写是因为,就不需要截图了,直接走后面的流程刚刚好
  22. builder.forceSize(width, height);
  23. // 导出结果文件
  24. builder.toFile(outputFilePath);



END

参考
https://github.com/coobird/thumbnailator