一次 Markdown 转 PDF/Word 的坑:ImageIO 与 WebP 格式之战


📌 摘要

在做 Markdown 转 PDF/Word 的过程中,我以为图片处理只是个小问题,结果却接连踩坑:有些图片扩展名是 .png,但内容却是 WebP,导致 Java 自带的 ImageIO 无法识别。经过一番摸索,我最终通过引入 imageio-webp 扩展,顺利实现了图片的 转码与批量处理。这篇文章会聊聊:

  • WebP 格式的特点与优势
  • Java 原生 ImageIO 的局限
  • 如何用 imageio-webp 支持 WebP 导入/导出
  • 批量处理 Markdown 图片并转存为 WebP 的完整示例
  • 以及 WebP 与 JPEG/PNG/GIF 的对比

希望能帮到在 Java 项目里遇到类似问题的同学 🚀。


WebP 是什么?

WebP 是 Google 推出的新一代图像格式,目标是替代传统的 JPEG / PNG / GIF。它有几个显著特性:

  • 高压缩率:相同画质下,比 JPEG 小 25%~35%,比 PNG 小 26%~34%。

  • 透明支持:有损/无损模式都能支持 Alpha 通道。

  • 动画支持:可以替代 GIF,更小体积更高质量。

  • 浏览器兼容性好:主流浏览器(Chrome、Firefox、Edge、Safari)已全面支持。

这也是为什么很多网站(比如简书、微信公众号)都会把上传的图片自动转成 WebP,从而提升页面加载速度。


我遇到的问题

我的任务是:把 Markdown 里的远程图片下载到本地,并统一转成标准格式(方便 PDF/Word 渲染)

原始实现大概是这样:

BufferedImage image = ImageIO.read(new URL(originalUrl));
if (image == null) {
    throw new IOException("无法识别图片格式: " + originalUrl);
}
ImageIO.write(image, "png", new File("output.png"));

结果在处理简书的图片链接时,日志里不断报:

Unrecognized Image format
ImageIO.read 返回 null

原因很简单:Java 自带的 ImageIO 并不支持 WebP


解决方案:引入 imageio-webp

好在社区已经有现成的扩展 —— TwelveMonkeys ImageIO 提供了 imageio-webp 模块,直接给 Java 的 ImageIO 增加了 WebP 编码/解码能力。

在 Maven 里加上依赖:

<dependency>
  <groupId>com.twelvemonkeys.imageio</groupId>
  <artifactId>imageio-webp</artifactId>
  <version>3.12.0</version>
</dependency>


示例代码:PNG → WebP

有了扩展库之后,ImageIO 就能直接读写 WebP 了。

BufferedImage image = ImageIO.read(new File("input.png"));

// 导出为 WebP
File output = new File("output.webp");
boolean ok = ImageIO.write(image, "webp", output);

System.out.println(ok ? "导出成功" : "导出失败");

如果还想控制质量,可以用 ImageWriteParam

Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("webp");
ImageWriter writer = writers.next();

ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(0.8f); // 0~1,越大画质越好

try (ImageOutputStream ios = ImageIO.createImageOutputStream(new File("output_q80.webp"))) {
    writer.setOutput(ios);
    writer.write(null, new IIOImage(image, null, null), param);
}
writer.dispose();


批量处理 Markdown 图片 → WebP

在实际项目里,Markdown 里可能有很多远程图片:

![一张图片](https://upload-images.jianshu.io/upload_images/1455720-xxx.png)
![另一张图片](https://upload-images.jianshu.io/upload_images/1455720-yyy.jpg)

目标:把这些图片全部下载到本地,并统一转存成 WebP,Markdown 链接也改成本地路径。

实现思路

  1. 用正则扫描 Markdown 图片链接;

  2. 下载远程图片 → 用 ImageIO 转成 WebP;

  3. 存储到按日期划分的目录里(如 /images/2025/09/24/xxx.webp);

  4. 替换 Markdown 内容中的 URL。

代码示例

Matcher matcher = IMAGE_PATTERN.matcher(markdown);
StringBuffer sb = new StringBuffer();

while (matcher.find()) {
    String originalUrl = matcher.group(1);

    // 已经是本地路径,跳过
    if (originalUrl.contains("/images/")) {
        matcher.appendReplacement(sb, matcher.group(0));
        continue;
    }

    String fileName = Paths.get(new URL(originalUrl).getPath()).getFileName().toString();
    String baseName = com.google.common.io.Files.getNameWithoutExtension(fileName);

    // 日期目录
    String datePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
    Path targetDir = Paths.get(tempImagePath, datePath);
    Files.createDirectories(targetDir);

    Path fixedTarget = targetDir.resolve(baseName + ".webp");

    // 下载并转存为 WebP
    BufferedImage image = ImageIO.read(new URL(originalUrl));
    Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("webp");
    ImageWriter writer = writers.next();
    ImageWriteParam param = writer.getDefaultWriteParam();
    param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
    param.setCompressionQuality(0.8f);
    try (ImageOutputStream ios = ImageIO.createImageOutputStream(fixedTarget.toFile())) {
        writer.setOutput(ios);
        writer.write(null, new IIOImage(image, null, null), param);
    }
    writer.dispose();

    // 替换 Markdown 链接
    String localUrl = "/images/" + datePath + "/" + fixedTarget.getFileName();
    matcher.appendReplacement(sb, "![](" + localUrl.replace("\\", "/") + ")");
}

matcher.appendTail(sb);
String newMarkdown = sb.toString();

运行后,所有远程图片会被下载 → 转成 WebP → 存在本地 → Markdown 链接同步更新。


常见图片格式对比

特性 JPEG PNG GIF WebP
压缩方式 有损 无损 无损(基于调色板) 有损 + 无损
体积大小 小(但画质会损失) 大(比 JPEG 大很多) 很大 更小(比 JPEG/PNG 小 25%~35%)
透明通道 ❌ 不支持 ✅ 支持 ✅ 支持(1 位透明度) ✅ 支持(8 位 Alpha)
动画支持 ❌ 不支持 ❌ 不支持 ✅ 支持 ✅ 支持(比 GIF 更小更清晰)
色彩深度 24 位真彩色 24 位 + 8 位透明 8 位(最多 256 色) 24 位 + 8 位透明
浏览器支持 全部支持 全部支持 全部支持 ✅ 主流浏览器已全面支持
适用场景 照片、网页图像 需要透明、无损的图标 简单动画、小图标 通用:照片、透明、图标、动画均可

总结

  • WebP 已经是互联网图片的新常态,兼顾小体积和高画质。

  • 在 Java 中,需要引入 imageio-webp 扩展才能支持读写 WebP。

  • 实际项目中,可以用正则扫描 Markdown,把远程图片统一转存为 WebP,既优化了文档体积,也提升了兼容性。

图片这种“小问题”,往往决定了文档导出的体验好坏。提前踩坑并解决,后面就能一路顺畅 🚀。


©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容