Java thumbnailator 图片裁剪格式转换 支持 JPEG/PNG/WEBP 互转 home 编辑时间 2021/03/18 ![](/api/file/getImage?fileId=6066e4f416199b501c020fdd) <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) 送人玫瑰,手留余香 赞赏 Wechat Pay Alipay 2017年毕业设计笔记补充 win10 相关备忘录