前段时间写了一篇文章《Java 图片等比例缩放以及按像素压缩》,今天有网友看到了,给我回复说,应该是使用 Thumbnailator。我一搜,发现确实是一个宝藏。感谢这位热心网友的提醒,让我多学到了一个框架,这就是 Java 图像处理的瑞士军刀:Thumbnailator,本文介绍该框架的全解析。
话说在 Java 后端开发中,图片处理几乎无处不在:头像上传、商品缩略图、活动海报、证书生成、水印打码……如果每次都手写 ImageIO + Graphics2D,不仅要处理大量细节,还容易踩坑。我之前就是,在踩坑与填坑之间徘徊,好在完美解决了问题。
今天呢,就花点时间给大家介绍一款网友推荐的轻量级、零依赖、链式 API 的宝藏类库:Thumbnailator。希望通过本文大家都能搞懂它是什么、能做什么、怎么玩,以及如何把 GIF 也安排得明明白白。
Thumbnailator 是谁?
Thumbnailator 是谷歌开源的 Java 处理图片缩略图生成库。
| 维度 | 说明 |
|---|---|
| 定位 | 专为 Java 设计的缩略图与通用图片处理库 |
| 作者 | Chris Kwon(coobird) |
| 开源 | MIT 协议,https://github.com/coobird/thumbnailator 5.3k+ star |
| 体积 | 仅一个 JAR(≈ 200 KB),零外部依赖 |
| 最新版 | 0.4.20(2023-06 发布,兼容 Java 17+ & Spring Boot 3) |
简单来说:就是把原本需要写 100 行的图片操作,压缩成 1 行链式代码。
它解决了什么痛点?
| 痛点 | Thumbnailator 解法 |
|---|---|
| 缩略图失真 | 内置高质量双线性/双三次插值算法 |
| 代码臃肿 | 链式调用,一行完成缩放、裁剪、旋转、水印 |
| 格式支持少 | 支持 JPEG、PNG、GIF、BMP、WEBP(依赖插件) |
| 批量处理难 | 直接传 File[] 或 Directory,一键批量 |
| 只缩小不放大 | 官方未提供,社区已给出 ImageFilter 方案 |
包括 github 上的介绍,无需学习如何使用图像 I/O API、Java 2D API、图像处理、图像缩放等技术即可实现图片操作。
5 分钟快速上手
下面,我们看一下如何上手。我今天花了可不止 5 分钟呢🤣,阅读官文加 demo 项目,再加写文章前后有一个小时了🤣。
引入依赖
上手很简单,先引入依赖。
<!-- Maven -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.20</version>
</dependency>
// Gradle
implementation 'net.coobird:thumbnailator:0.4.20'
一行代码生成缩略图
写下和官网一样的代码。
Thumbnails.of("input.jpg")
.size(300, 300) // 最大边 300,保持宽高比
.toFile("thumb.jpg");
运行后,即可看到缩略图效果。
核心玩法大全
我们先来看一个尺寸调整和比例缩放。
Thumbnails.of("input.jpg").size(200,200).keepAspectRatio(false).toFile("thumb.jpg");
注意,这个如果不是等比例的话,可能会变形。
旋转
旋转度数根据需要调整,默认是顺时针。
Thumbnails.of("input.jpg")
.rotate(90) // 顺时针 90°
.toFile("rot90.jpg");
添加水印
水印可以是图片,也可以是文字,还可以设置水印的透明度 50%。
BufferedImage watermark = ImageIO.read(new File("wm.png"));
Thumbnails.of("input.jpg")
.size(800, 600)
.watermark(Positions.BOTTOM_RIGHT, watermark, 0.5f) // 透明度 50%
.outputQuality(0.8f)
.toFile("wm.jpg");
裁剪
图片裁剪。
Thumbnails.of("input.jpg")
.sourceRegion(Positions.CENTER, 400, 400) // 中心 400×400
.size(200, 200)
.toFile("cut.jpg");
格式转换
图片格式转换。
Thumbnails.of("input.jpg")
.outputFormat("png")
.toFile("output.png");
输出到流 / BufferedImage / Base64
转换输出到不同的流。
// 输出到 OutputStream(适用于 Web 下载)
try (OutputStream os = response.getOutputStream()) {
Thumbnails.of("input.jpg").scale(0.5).toOutputStream(os);
}
// 输出到 BufferedImage(继续二次处理)
BufferedImage bi = Thumbnails.of("input.jpg").size(200, 200).asBufferedImage();
// Base64 字符串
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ImageIO.write(bi, "jpg", bos);
String base64 = Base64.getEncoder().encodeToString(bos.toByteArray());
获取文件大小
这个不需要使用类库,Java 中的 File 对象就有长度大小。
File file = new File("thumb.jpg");
long size = file.length(); // 单位:字节
只缩小不放大
官方 .size() 会强制贴合框,小图会被放大。社区方案:自定义 ImageFilter。
下面是关键代码。
public class NoScaleUpFilter implements ImageFilter {
privatefinalint maxWidth;
privatefinalint maxHeight;
public NoScaleUpFilter(int maxWidth, int maxHeight) {
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
}
@Override
public BufferedImage apply(BufferedImage img) {
if (img.getWidth() <= maxWidth && img.getHeight() <= maxHeight) {
return img; // 直接返回原图
}
try {
return Thumbnails.of(img).size(maxWidth, maxHeight).asBufferedImage();
} catch (IOException e) {
thrownew RuntimeException(e);
}
}
}
// 使用
Thumbnails.of("small.jpg")
.scale(1f)
.addFilter(new NoScaleUpFilter(300, 300))
.toFile("small_thumb.jpg");
GIF 压缩专题
GIF 是多帧动画,Thumbnailator 只能处理单帧。方案 = 拆帧 → 逐帧压缩 → 重组。
这个稍微有些麻烦。
引入 GIF 编解码库
<dependency>
<groupId>com.madgag</groupId>
<artifactId>animated-gif-lib</artifactId>
<version>1.4</version>
</dependency>
Gradle 自己改一下哈。
完整工具类
public staticbyte[] compressGif(byte[] src, int maxSide) throws IOException {
GifDecoder decoder = new GifDecoder();
decoder.read(new ByteArrayInputStream(src));
int count = decoder.getFrameCount();
if (count == 1) { // 单帧当静态图
return Thumbnails.of(ImageIO.read(new ByteArrayInputStream(src)))
.size(maxSide, maxSide)
.outputQuality(0.8)
.asBufferedImage();
}
Dimension dim = decoder.getFrameSize();
float scale = Math.min(1f, (float) maxSide / Math.max(dim.width, dim.height));
int w = (int) (dim.width * scale);
int h = (int) (dim.height * scale);
ByteArrayOutputStream out = new ByteArrayOutputStream();
AnimatedGifEncoder encoder = new AnimatedGifEncoder();
encoder.start(out);
encoder.setRepeat(0);
encoder.setSize(w, h);
for (int i = 0; i < count; i++) {
BufferedImage frame = decoder.getFrame(i);
BufferedImage compressed = Thumbnails.of(frame).size(w, h).asBufferedImage();
encoder.setDelay(decoder.getDelay(i));
encoder.addFrame(compressed);
}
encoder.finish();
return out.toByteArray();
}
最终效果:将 5 MB、1280×720 的 GIF 压成 480×270、800 KB,帧率与播放速度不变。
常见踩坑 & 最佳实践
下面是我在使用中,以及 github 上有人提到的坑。
| 坑 | 说明 | 解决 |
|---|---|---|
| PNG 转 JPG 背景变黑 | JPG 不支持透明 | 先 .outputFormat("jpg").addFilter(new CanvasFilter(Color.WHITE)) |
| 压缩后体积反而变大 | 缩放+格式转换导致 | 同格式压缩最稳:JPG→JPG,PNG→PNG |
| 批量命名冲突 | 默认覆盖 | 使用 .toFiles(Rename.PREFIX_DOT_THUMBNAIL) 自动加前缀 |
| 高并发 OOM | 大图片直接加载 | 先 .size(max, max) 再读,或开启 -Xmx |
总结
使用下来,确实方便。相比我之前文章中的代码,类库简化了我们直接操作 ImageIO 的一些 api。某些效果也比我自身实现的好。
| 场景 | 推荐 |
|---|---|
| 快速生成缩略图/水印 | Thumbnailator 一行代码 |
| 复杂合成、滤镜、文字 | 可结合 Java 2D 或切换到 Marvin/FilthyRichClients |
| 海量分布式处理 | Thumbnailator 单机 + 消息队列,或转存 OSS 触发云端处理 |
| Spring Boot 3 | 直接使用 0.4.20,已验证兼容 Java 17 |
以上,希望这篇文章对大家有用!
微信扫描下方的二维码阅读本文

Comments NOTHING