SpringBoot+Thumbnailator等于Java界的美图秀秀,缩图,水印,gif拆帧压缩

whdahanh 发布于 2025-09-29 282 次阅读


前段时间写了一篇文章《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

以上,希望这篇文章对大家有用!



微信扫描下方的二维码阅读本文

此作者没有提供个人介绍
最后更新于 2025-09-29