Java thumbnailator 图片裁剪格式转换 支持 JPEG/PNG/WEBP 互转
2021-04-27
阅读 {{counts.readCount}}
评论 {{counts.commentCount}}
<br><br>
## 前言
<br>
这里必须先介绍 **`webp`** , 一种神仙级别的图片格式。
<br>
![](/api/file/getImage?fileId=6066e4f416199b501c020fdc)
<br>
参考上图,同一张图,在允许有损压缩的前提下
| 格式 | 容量 |
| ---- | ---- |
| png | 1.4 mb |
| jpg | 489 kb |
| webp | 98 kb |
<br>
而且肉眼几乎看不出明显区别。
这个放在一个网站或者app中,速度就可以拉开5~14倍的差距,而且不牺牲用户体验,还可以节省运营的带宽和流量。
<br>
一般主流的网站或APP中的图片格式为
| 格式 | 透明 | 清晰度 | 压缩率 | 动图 |
| ---- | ---- | ---- | ---- | ---- |
| png | 支持 | 高清 | 低压缩率 | 不支持 |
| jpg | 不支持 | 有损 | 高压缩率 | 不支持 |
| gif | 支持 | 有损 | 中压缩率 | 支持 |
而webp另外一个开挂的能力就来了,他支持以上所有特性,且同时保持着极高的压缩率
| 格式 | 透明 | 清晰度 | 压缩率 | 动图 |
| ---- | ---- | ---- | ---- | ---- |
| webp | 支持 | 无损/高/中/低 皆可 | 神仙级压缩率 | 支持 |
<br><br>
可以说,这种有百利,无一害的技术,理论上应该早就普及了。
毕竟10年前就已经由谷歌发布了。
并且早早的就在chrome浏览器,以及各大图标编辑处理显示软件中得到支持。
<br>
**BUT !!!**
<br>
他喵的 `Firefox` 和 `Safari` 一直迟迟不支持,IE更别谈了。
<br>
在此之前为了兼容不支持 `webp` 的浏览器,需要特地写2套代码,准备2套图片,反而浪费资源
<br>
**好消息是就在2020年, `Firefox` 和 `Safari` 也相继开始支持 `webp` 了**
<br>
具体版本如下
![](/api/file/getImage?fileId=6066e4f416199b501c020fde)
其中比较显眼的是 `Safari 14` ,显示部分支持,因为需要系统同时升级到 `mac os 11 bigsur` 搭配 `Safari 14`
<br><br>
## 折腾
承接上文,后续就要考虑如何将图片转换为 `webp`
较为原始的方法可以是用 `linux` 安装 `webp` 工具,再使用脚本转换
[https://linux.cn/article-12193-1.html](https://linux.cn/article-12193-1.html)
亦或者用 `Java` ,在 `javahome` 中加入 `webp` 的库文件支持
[https://my.oschina.net/who7708/blog/2878444](https://my.oschina.net/who7708/blog/2878444)
<br>
两种方案我都浅尝了一下,发现一个共同的缺点,即:参数都只有 **质量 0 - 100** 一个参数
那么如果需求是将 `1928 * 1080` `png` 的图片 转换为 `600 * 360` `75%` `webp` 的缩略图,
就需要
1. 先转换为 `600 * 360` `jpg`
2. 再转换为 `75%` `webp`
劳民又伤财
<br><br>
最后我意外发现 `Java` 图片转换工具 `thumbnailator` ,现已支持webp,且支持全套参数调整。
起初仅通过阅读源码可以发现允许输出以下5种格式,JPEG、JPG、GIF、WBMP、PNG、WEBP,因此得出thumbnailator可以支持webp的结论
然额,一执行就报
```java
Exception in thread "main" java.lang.IllegalArgumentException: Specified format is not supported: webp
at net.coobird.thumbnailator.Thumbnails$Builder.outputFormat(Unknown Source)
at com.zzzmh.utils.ImageUtils.main(ImageUtils.java:23)
```
实际上用我蹩脚的英文也看出来官方文档和github issues中也说了,并不直接支持webp
那到底要怎么才能让他支持webp呢?
<br>
**就是要搭配这玩意的依赖 webp-imageio-core**
[https://github.com/nintha/webp-imageio-core](https://github.com/nintha/webp-imageio-core)
需要注意这玩意没有maven中央仓库的依赖!
他的github中写了,要先下载jar包 `webp-imageio-core-0.1.3.jar`
放到项目根目录 `libs` 文件夹下 (没有lib就创建一个)
然后再maven中用以下方式引入本地依赖
```maven
<dependency>
<groupId>com.github.nintha</groupId>
<artifactId>webp-imageio-core</artifactId>
<version>{version}</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/webp-imageio-core-{version}.jar</systemPath>
</dependency>
```
<br><br>
当然如果你不是maven项目,可以用java最原始的方法引入依赖
就是下载这2个jar包,并且在开发工具中配置依赖,添加jar包路径即可
`webp-imageio-core-0.1.3.jar`
`thumbnailator-0.4.14.jar`
<br>
**2020/03/18补充:目前经测试,webp-imageio-core在windows下无法正常工作!已有github issues,但仍未得到解决,等我发现解决方案,再到这里更新**
这里目前调查结果是
如果作者不更新,无法直接在 `windows` 下使用 `webp-imageio-core`
个人更推荐在mac或linux下使用,同样硬件的情况下,算力也会更强
<br>
如果一定要在windows下用,需要先让 `Java` 支持 `webp`
类似这篇文章的方案
[https://my.oschina.net/who7708/blog/2878444](https://my.oschina.net/who7708/blog/2878444)
<br><br>
## 实现
参考刚才的需求
**将 `1928 * 1080` `png` 的图片 转换为 `600 * 360` `75%` `webp` 的缩略图**
<br>
最终代码如下
<br><br>
**maven方式引入依赖**
(省略了 webp-imageio-core-0.1.3.jar 的部分,请参考上一个段落)
```maven
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.14</version>
</dependency>
```
<br><br>
**Java 代码如下**
```Java
public static void main(String[] args) throws IOException {
Thumbnails.of("C:\\test\\input.png")
.size(600, 360)
.outputQuality(0.75f)
.outputFormat("webp")
.toFile("C:\\test\\output.webp");
}
```
**亲测 3M的高清原图 可以压缩至30kb的预览图 jpg则需要 60kb左右**
<br><br>
## 填坑
说一个稍微深入一点的用法
<br>
1. 如果设置了宽度高度,如果比例不同,只会满足其中一个,另一个则会小于设置的数值,原图如果高度超出需求的比例,则高度压缩到设置成的高度,宽度会小于设置的宽度,反之也是以此类推。如果你期望的是宽度必须是1920,高度可以是 1920 * 1080 或 1920 * 1280 等等,那就只设置一个宽度即可。
<br>
2. 例如我需要一个缩略图,无论原图如何,缩略图都要设置成 450 * 300,如果原图比例 比 3:2 更宽,则截掉2边,拿中间,如果原图更窄,则截掉上下取中间。
最终没有找到直接实现的现成方法,于是自己写了一个
<br>
思路如下
```
最难的就是这里 需要考虑长短边 然后裁切出需要的部分
例如 1920 * 1080 的图片
转换 450 * 300
要么变形
要么会转换出 450 * 253
这时候就需要裁切 首先计算出
长边裁切 按 1080 / 300 * 450 = 1620
短边裁切 按 1920 / 450 * 300 = 1280
显然 1080 无法裁切出 1920 * 1280
只能是1920 裁切成 1620 * 1080
4个参数可以推得出
(1920 - 1620) / 2 = 150
既 150 , 0 , 1620 , 1080
意思分别是 从width 150 height 0 开始截取,截取一个width 1620 height 1080的方框
最终得出两套公式 分别用于长边4个参数和短边4个参数
字太多懒得打,直接看Java代码即可
```
<br>
核心代码如下
```java
Thumbnails.Builder<File> builder = Thumbnails.of(inputFilePath)
.outputQuality(quality)
.outputFormat(format);
// 这里需要用到传入的高度和宽度 我这里传入了integer类型,你可以直接传入int 就不需要.intValue()
int ogHeight = originHeight.intValue();
int ogWidth = originWidth.intValue();
// 这里计算原图比例和需求比例哪个更宽
// 这里的CalculatorUtils类就是java计算类(防止double丢失精度),我在后面会补上代码
int compare = CalculatorUtils.compareTo(CalculatorUtils.div(ogWidth, ogHeight), CalculatorUtils.div(width, height));
// 这里根据2种情况进行截图,获取到合适的区域
if (compare == 1) {
double tempWidth = CalculatorUtils.mul(CalculatorUtils.div(ogHeight, height), width);
double start = CalculatorUtils.div(CalculatorUtils.sub(ogWidth, tempWidth), 2);
builder.sourceRegion((int) start, 0, (int) tempWidth, ogHeight);
} else if (compare == -1) {
double tempHeight = CalculatorUtils.mul(CalculatorUtils.div(ogWidth, width), height);
double start = CalculatorUtils.div(CalculatorUtils.sub(ogHeight, tempHeight), 2);
builder.sourceRegion(0, (int) start, ogWidth, (int) tempHeight);
}
// 防止小数点造成意外,这里强制拉伸下
// 上面写了2种情况,其实还有第三种,就是2者比例完全相等 compare == 0 , 不写是因为,就不需要截图了,直接走后面的流程刚刚好
builder.forceSize(width, height);
// 导出结果文件
builder.toFile(outputFilePath);
```
<br><br>
## END
参考
[https://github.com/coobird/thumbnailator](https://github.com/coobird/thumbnailator)