Apache XML Graphics Commons 2.11 快速入门与集成示例

用 10 分钟在 Maven 工程中引入 Apache XML Graphics Commons 2.11 并跑通第一张 PNG,覆盖依赖树与传递依赖、MultiImageWriter 非 AutoCloseable、ImageBuffered 命名、TIFF 压缩名三类实测坑,以及 TIFF 压缩对比、多帧编码与 ImageInfo 预检管线,全部已在 Ubuntu 22.04 实测。

Apache XML Graphics CommonsApache XML Graphics Commons2026/10/15

Apache XML Graphics Commons 2.11 快速入门与集成示例

目标:10 分钟内在 Maven 工程中引入 Apache XML Graphics Commons 2.11,跑通第一个 PNG 编码程序;掌握三个高频集成坑(均为编译/运行实测复现);进阶完成 TIFF 压缩对比、多帧编码与 ImageInfo 图像预检。以下全部命令与回显均已在 Ubuntu 22.04(x86_64)、OpenJDK 17.0.20.1 实测通过。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64)
  • JDK:OpenJDK 17.0.20.1(Temurin,java -version 实测)
  • 构建工具:Apache Maven 3.9.16
  • 目标库:Apache XML Graphics Commons 2.11(org.apache.xmlgraphics:xmlgraphics-commons:2.11,Maven Central 当前 latest/release,2025-05-06 发布)
  • 传递依赖:commons-io 2.17.0、commons-logging 1.3.0(依赖树实测,见 2.1 节)
  • 字节码基线:2.11 jar 的 class 主版本号为 52(Java 8,javap -verbose 实测),JDK 8+ 可运行
  • Maven 示例工程目录:/opt/xgc-demo,产物目录 /opt/xgc-demo/output

确认 JDK 与 Maven 版本基线:

java -version 2>&1
mvn -v | head -2

输出:

openjdk version "17.0.20.1" 2026-08-18
OpenJDK Runtime Environment Temurin-17.0.20.1+1 (build 17.0.20.1+1)
Apache Maven 3.9.16 (2bdd9fddda4b155ebf8000e807eb73fd829a51d5)
Maven home: /usr/share/maven

⚠️ 注意:本文所有运行命令统一为 java -Dfile.encoding=UTF-8 -Djava.awt.headless=true -cp target/classes:$CP demo.XgcDemo <组名> 形式($CP 在 2.3 节赋值);服务器无图形界面,headless 属性实测可显式指定,省略时 JVM 也会按 DISPLAY 环境变量自动判定。安装与环境准备见第 1 篇《Ubuntu 22.04 安装 Apache XML Graphics Commons 指南》。

二、10 分钟最小可用

本节从空目录到跑通第一个程序共 4 步,全程约 10 分钟:建工程(1 分钟)→ 写 pom(2 分钟)→ 写程序(2 分钟)→ 编译运行与产物验证(5 分钟,首次下载依赖视网速)。

2.1 创建工程并引入依赖

创建工程目录:

mkdir -p /opt/xgc-demo/src/main/java/demo

/opt/xgc-demo/pom.xml 全文(最小可用配置,直接照抄):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>demo</groupId>
  <artifactId>xgc-demo</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.release>17</maven.compiler.release>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.apache.xmlgraphics</groupId>
      <artifactId>xmlgraphics-commons</artifactId>
      <version>2.11</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.11.0</version>
      </plugin>
    </plugins>
  </build>
</project>

两个必做项,缺一会踩坑:

  • 显式固定 maven-compiler-plugin 为 3.11.0:老版本 Maven 默认绑定的 3.1 版插件不认识 maven.compiler.release 属性;CentOS 8 的 Maven 3.5.4 + JDK 8 组合下实测报 Fatal error compiling: invalid flag: --release(复现与修复见第 2 篇 4.4 节)。
  • 声明 project.build.sourceEncoding=UTF-8:源码含中文注释时防止按平台默认编码编译报 unmappable character。

查看依赖树(确认传递依赖自动就位):

cd /opt/xgc-demo && mvn -B dependency:tree 2>&1 | grep -E "dependency.*:tree|demo:xgc-demo|xmlgraphics-commons:jar|commons-io:commons-io:jar|commons-logging:commons-logging:jar"

输出:

[INFO] --- dependency:3.7.0:tree (default-cli) @ xgc-demo ---
[INFO] demo:xgc-demo:jar:1.0.0
[INFO] \- org.apache.xmlgraphics:xmlgraphics-commons:jar:2.11:compile
[INFO]    +- commons-io:commons-io:jar:2.17.0:compile
[INFO]    \- commons-logging:commons-logging:1.3.0:compile

解读:除工程自身外只有 xmlgraphics-commons 及其两个传递依赖。commons-io 与 commons-logging 是编译期依赖(jar tf 可见 org/apache/commons/logging/Log 被 ImageManager 直接引用),jar 直跑场景缺一即报 NoClassDefFoundError。

2.2 编写第一个程序

/opt/xgc-demo/src/main/java/demo/XgcDemo.java 全文(默认入口,直接复制;完整版源码含 TIFF/PS/Base64 等组,见第 4、5 篇文章):

package demo;

import org.apache.xmlgraphics.image.writer.ImageWriter;
import org.apache.xmlgraphics.image.writer.ImageWriterParams;
import org.apache.xmlgraphics.image.writer.ImageWriterRegistry;

import java.awt.Color;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;

public class XgcDemo {
    static final String OUT = "/opt/xgc-demo/output";

    public static void main(String[] args) throws Exception {
        new File(OUT).mkdirs();

        // 1. 用 Java2D 画一张 320x200 样张:渐变背景 + 矩形 + 文字
        BufferedImage img = new BufferedImage(320, 200, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2 = img.createGraphics();
        g2.setPaint(new GradientPaint(0, 0, new Color(30, 80, 200), 320, 200, Color.WHITE));
        g2.fillRect(0, 0, 320, 200);
        g2.setColor(new Color(220, 40, 40));
        g2.fillRect(20, 20, 120, 70);
        g2.setColor(Color.BLACK);
        g2.setFont(new Font("SansSerif", Font.BOLD, 20));
        g2.drawString("XML Graphics PNG", 30, 140);
        g2.dispose();

        // 2. 按 MIME 从注册表取写出器,300 DPI 写出 PNG
        ImageWriter writer = ImageWriterRegistry.getInstance().getWriterFor("image/png");
        System.out.println("writer 实现: " + writer.getClass().getName());
        System.out.println("writer MIME: " + writer.getMIMEType()
                + ", isFunctional=" + writer.isFunctional());
        ImageWriterParams params = new ImageWriterParams();
        params.setResolution(300);
        File target = new File(OUT, "demo.png");
        try (OutputStream out = new FileOutputStream(target)) {
            writer.writeImage(img, out, params);
        }
        System.out.println("已写入 " + target.getAbsolutePath()
                + " 字节数=" + target.length());
    }
}

程序分两段:先用标准 Java2D 绘制样张,再走 XML Graphics Commons 的写出链路。核心入口是 ImageWriterRegistry.getInstance().getWriterFor("image/png")——按目标 MIME 类型取 ImageWriter 实现,writeImage(RenderedImage, OutputStream, ImageWriterParams) 完成编码。源码文件必须放在 src/main/java/demo/ 约定目录(包 demo 对应子目录),否则触发「No sources to compile」的假成功。

2.3 编译与运行

编译:

cd /opt/xgc-demo && mvn -q -B compile
echo "EXIT=$?"

输出:

EXIT=0

运行(classpath = 编译输出 + 本地仓库三件套):

CP=target/classes:/root/.m2/repository/commons-io/commons-io/2.17.0/commons-io-2.17.0.jar:/root/.m2/repository/commons-logging/commons-logging/1.3.0/commons-logging-1.3.0.jar:/root/.m2/repository/org/apache/xmlgraphics/xmlgraphics-commons/2.11/xmlgraphics-commons-2.11.jar
java -Dfile.encoding=UTF-8 -Djava.awt.headless=true -cp "$CP" demo.XgcDemo

输出:

writer 实现: org.apache.xmlgraphics.image.writer.imageio.ImageIOPNGImageWriter
writer MIME: image/png, isFunctional=true
已写入 /opt/xgc-demo/output/demo.png 字节数=14114

用 file 验证产物是真实 PNG:

file output/demo.png

输出:

output/demo.png:           PNG image data, 320 x 200, 8-bit/color RGBA, non-interlaced

2.4 验证点与预期结果

拿到以下结果即集成成功,任一不符按第三章排查:

检查项预期(实测值)不符时排查
mvn -q -B compile 退出码EXIT=0报 invalid flag: --release → 3.4 节;No sources to compile → 源码目录不对
运行输出 writer 实现类...imageio.ImageIOPNGImageWritergetWriterFor 返回 null → classpath 缺主 jar(3.5 节)
运行输出 PNG 字节数14114NoClassDefFoundError → 缺传递依赖(3.5 节)
file 识别 demo.pngPNG image data, 320 x 200, 8-bit/color RGBA0 字节 → 输出流未 close

三、Maven 工程常见坑

以下四个坑在 xmlgraphics-commons 集成中出现频率最高,均为实测复现,给出定位与修复。

3.1 坑一:MultiImageWriter 不是 AutoCloseable,try-with-resources 编译失败

现象:对 MultiImageWriter 使用 try-with-resources,编译报错(实测回显):

[ERROR] /opt/xgc-demo/src/main/java/demo/XgcDemo.java:[126,31] incompatible types: try-with-resources not applicable to variable type
[ERROR]     (org.apache.xmlgraphics.image.writer.MultiImageWriter cannot be converted to java.lang.AutoCloseable)

定位:2.11 的 MultiImageWriter 接口只有 writeImage(RenderedImage, ImageWriterParams) 与 close() 两个方法,没有继承 AutoCloseable。修复:改为手动 close(),且 close 必须调用(多帧 TIFF 的页目录在 close 时写出):

OutputStream mout = new FileOutputStream(f4);
MultiImageWriter miw = writer.createMultiImageWriter(mout);
miw.writeImage(frame1, mp);
miw.writeImage(frame2, mp);
miw.close();     // 必须调用,否则文件不完整
mout.close();

3.2 坑二:BUFFERED_IMAGE 解码结果类是 ImageBuffered,不是 ImageBufferedImage

现象:把解码结果强转成 ImageBufferedImage,编译报错(实测回显):

[ERROR] /opt/xgc-demo/src/main/java/demo/XgcDemo.java:[11,48] cannot find symbol
[ERROR]   symbol:   class ImageBufferedImage
[ERROR]   location: package org.apache.xmlgraphics.image.loader.impl

定位:2.11 的 org.apache.xmlgraphics.image.loader.impl 包里没有 ImageBufferedImage 这个类;ImageFlavor.BUFFERED_IMAGE 对应的实现类是 ImageBuffered(jar tf 实测该包内存在 ImageBuffered.class,不存在任何 ImageBufferedImage.class)。修复:

Image image = manager.getImage(info, ImageFlavor.BUFFERED_IMAGE, session);
BufferedImage decoded = ((ImageBuffered) image).getBufferedImage();

网传教程里的 ImageBufferedImage 写法与 2.11 的 jar 不符,写代码前用 jar tf xmlgraphics-commons-2.11.jar | grep loader/impl 核对类名。

3.3 坑三:TIFF 压缩名传 "None" 抛 Unknown compression type

现象:params.setCompressionMethod("None") 写 TIFF,运行期抛异常(实测回显):

Exception in thread "main" java.lang.IllegalArgumentException: Unknown compression type!
        at java.desktop/javax.imageio.ImageWriteParam.setCompressionType(ImageWriteParam.java:1040)
        at org.apache.xmlgraphics.image.writer.imageio.ImageIOImageWriter.getDefaultWriteParam(ImageIOImageWriter.java:155)
        at org.apache.xmlgraphics.image.writer.imageio.ImageIOImageWriter.writeImage(ImageIOImageWriter.java:84)

定位:ImageIOImageWriter 把压缩名直接透传给 JDK 的 ImageWriteParam.setCompressionType,后者只认其内部注册的名称(TIFF:None 不在其中,实测 PackBits、LZW、Deflate 可用)。不设置压缩名即等于不压缩,不要传 "None"。实测可用的三种压缩对比见 4.2 节。

3.4 坑四:老 Maven + 老 compiler 插件不认识 release 语义

Maven 3.6.3 及更早版本默认绑定的 maven-compiler-plugin 3.1 不认识 maven.compiler.release 属性;el8 的 Maven 3.5.4 在 JDK 8 javac 下实测报 Fatal error compiling: invalid flag: --release(完整复现与 JAVA_HOME 修复见第 2 篇 4.4 节)。本文 pom 已显式固定 3.11.0,可避开。

3.5 坑五补充:NoClassDefFoundError 与写出器为 null

classpath 缺 commons-io 或 commons-logging 时,运行到 ImageManager/日志初始化即抛 NoClassDefFoundError;主 jar 缺失时 getWriterFor 返回 null。用 mvn dependency:tree 确认三件套齐全(2.1 节回显)。

四、TIFF 压缩对比与多帧编码

TIFF 是 XML Graphics Commons 写出能力最有特色的一块:支持压缩算法参数与多帧(多页)文件。

4.1 三种压缩实测

写出代码(tiff() 组核心片段,完整源码见第 5 篇文章附录):

ImageWriter writer = ImageWriterRegistry.getInstance().getWriterFor("image/tiff");
System.out.println("writer 实现: " + writer.getClass().getName()
        + ", supportsMultiImageWriter=" + writer.supportsMultiImageWriter());

ImageWriterParams p1 = new ImageWriterParams();
p1.setCompressionMethod("PackBits");
p1.setResolution(300);
writer.writeImage(img, new FileOutputStream(f1), p1);   // demo-packbits.tiff

ImageWriterParams p2 = new ImageWriterParams();
p2.setCompressionMethod("Deflate");
writer.writeImage(img, new FileOutputStream(f2), p2);   // demo-deflate.tiff

ImageWriterParams p3 = new ImageWriterParams();
p3.setCompressionMethod("LZW");
writer.writeImage(img, new FileOutputStream(f3), p3);   // demo-lzw.tiff

运行回显(同一 320x200 渐变+图形样张):

== TIFF 编码(ImageIOTIFFImageWriter,压缩对比) ==
writer 实现: org.apache.xmlgraphics.image.writer.imageio.ImageIOTIFFImageWriter, supportsMultiImageWriter=true
PackBits 压缩: demo-packbits.tiff 字节数=256377
Deflate  压缩: demo-deflate.tiff 字节数=82104
LZW      压缩: demo-lzw.tiff 字节数=156333

压缩率解读(同一样张):Deflate 最小(82104 字节,约为 PackBits 的 32%)、LZW 居中(156333)、PackBits 最大(256377,RLE 对渐变图像效率低)。对扫描件/二值图则 PackBits 常有优势,按素材类型选择;要兼容老 PS 解释器时 PackBits 兼容性最好。

file 验证压缩标识已写入文件头(实测):

file output/demo-packbits.tiff output/demo-deflate.tiff output/demo-lzw.tiff
output/demo-deflate.tiff:  TIFF image data, big-endian, direntries=16, height=200, bps=0, compression=Deflate (PKZIP), PhotometricIntepretation=RGB, width=320
output/demo-lzw.tiff:      TIFF image data, big-endian, direntries=13, height=200, bps=0, compression=LZW, PhotometricIntepretation=RGB, width=320
output/demo-packbits.tiff: TIFF image data, big-endian, direntries=16, height=200, bps=0, compression=PackBits (Macintosh RLE), PhotometricIntepretation=RGB, width=320

4.2 多帧(多页)TIFF

TIFF 支持一个文件多帧,ImageWriter.createMultiImageWriter() 产出 MultiImageWriter(注意 3.1 节的 close 坑):

OutputStream mout = new FileOutputStream(new File(OUT, "demo-multi.tiff"));
MultiImageWriter miw = writer.createMultiImageWriter(mout);
ImageWriterParams mp = new ImageWriterParams();
mp.setCompressionMethod("PackBits");
mp.setResolution(300);
miw.writeImage(drawSample(320, 200, "TIFF frame 1"), mp);
miw.writeImage(drawSample(320, 200, "TIFF frame 2"), mp);
miw.close();
mout.close();

运行回显:

多帧 TIFF(2 页): demo-multi.tiff 字节数=512829

验证点:2 帧 PackBits 合计 512829 字节,约等于两倍单帧(256377×2=512754)加页目录开销,符合 TIFF 无帧间去重的预期。

五、ImageInfo/ImageLoader 预检与解码

org.apache.xmlgraphics.image.loader 是本库的图像加载管线:ImageManager 负责预检(preload)与解码(getImage),ImageInfo 携带 MIME 与尺寸,ImageContext/ImageSessionContext 携带环境分辨率与会话基准目录。

5.1 预检与解码实测

核心代码(info() 组):

ImageContext ctx = new DefaultImageContext();
System.out.println("DefaultImageContext.getSourceResolution() = " + ctx.getSourceResolution());
ImageContext custom = new ImageContext() {
    @Override
    public float getSourceResolution() {
        return 300.0f;
    }
};
System.out.println("自定义 ImageContext(300dpi).getSourceResolution() = " + custom.getSourceResolution());

ImageManager manager = new ImageManager(ctx);
ImageSessionContext session = new DefaultImageSessionContext(ctx, new File(OUT));

ImageInfo info = manager.preloadImage(uri, session);          // 预检:只读头部
System.out.println("   MIME=" + info.getMimeType());
System.out.println("   size=" + info.getSize());
System.out.println("   像素=" + info.getSize().getWidthPx() + "x" + info.getSize().getHeightPx()
        + " DPI=" + info.getSize().getDpiHorizontal() + "x" + info.getSize().getDpiVertical()
        + " pt=" + info.getSize().getDimensionPt().getWidth() + "x" + info.getSize().getDimensionPt().getHeight());

Image image = manager.getImage(info, ImageFlavor.BUFFERED_IMAGE, session);  // 解码
BufferedImage decoded = ((ImageBuffered) image).getBufferedImage();

运行回显:

== ImageInfo / ImageLoader(ImageManager 预检与解码) ==
DefaultImageContext.getSourceResolution() = 72.0
自定义 ImageContext(300dpi).getSourceResolution() = 300.0
-- demo.png (uri=file:/opt/xgc-demo/output/demo.png)
   MIME=image/png
   size=Size: 76800x48000 mpt (320x200 px at 299.9994201660156x299.9994201660156 dpi)
   像素=320x200 DPI=299.9994201660156x299.9994201660156 pt=76.8x48.0
   解码 flavor=BufferedImage 实现类=org.apache.xmlgraphics.image.loader.impl.ImageBuffered
   BufferedImage 尺寸=320x200 类型=6
-- demo-packbits.tiff (uri=file:/opt/xgc-demo/output/demo-packbits.tiff)
   MIME=image/tiff
   size=Size: 76800x48000 mpt (320x200 px at 300.0x300.0 dpi)
   像素=320x200 DPI=300.0x300.0 pt=76.8x48.0
   解码 flavor=BufferedImage 实现类=org.apache.xmlgraphics.image.loader.impl.ImageBuffered
   BufferedImage 尺寸=320x200 类型=0
-- demo-multi.tiff (uri=file:/opt/xgc-demo/output/demo-multi.tiff)
   MIME=image/tiff
   size=Size: 76800x48000 mpt (320x200 px at 300.0x300.0 dpi)
   像素=320x200 DPI=300.0x300.0 pt=76.8x48.0
   解码 flavor=BufferedImage 实现类=org.apache.xmlgraphics.image.loader.impl.ImageBuffered
   BufferedImage 尺寸=320x200 类型=0

5.2 结果解读

  • preloadImage 只读头部:三个文件(含 512829 字节的多帧 TIFF)预检瞬间完成,返回的 ImageInfo 已含 MIME、像素尺寸、DPI 与 pt 尺寸(320px@300dpi = 76.8pt,计算一致)。
  • DPI 精度差异是真实锚点:PNG 的 300 DPI 读回为 299.9994201660156——PNG 物理分辨率按「像素/米」存储(11811 像素/米 ≈ 299.9994 DPI),有量化误差;TIFF 按「像素/英寸」有理数存储,精确还原 300.0。对尺寸敏感的排版逻辑请用容差比较。
  • mpt 是毫点(1/1000 pt):76800x48000 mpt 即 76.8x48.0 pt,与 getDimensionPt() 一致。
  • 类型值:PNG 解码后 getType()=6(TYPE_INT_ARGB),TIFF 解码后 getType()=0(TYPE_CUSTOM,JDK TIFF 读入器的原生色彩模型)。
  • DefaultImageContext 默认源分辨率 72.0;ImageSessionContext 构造第二参数是相对 URI 的基准目录。

💡 技术拓展:图像「读入/写出」方案对比——

方案读入格式覆盖写出参数控制多帧适用场景
javax.imageio(JDK 原生)PNG/JPEG/GIF/BMP(TIFF/WMF 需 JDK 9+ 插件)弱(裸 ImageWriteParam)TIFF 支持简单读写
XML Graphics Commons ImageWriter—(读入走 ImageManager 管线)强(分辨率/压缩/条带/JPEG 质量统一封装)TIFF 官方支持打印/排版输出链路
TwelveMonkeysPNG/PSD/WebP/ICNS 等大量读入同 ImageIO有限只补读入格式

本文采用 XML Graphics Commons:写出侧的 ImageWriterParams(DPI、压缩、条带、JPEG 质量)把跨格式参数统一成一个类,且与 FOP 的图像管线同源,ImageInfo 预检能力(不落内存先拿尺寸)在版面计算场景不可替代。

六、选型与生态定位

  1. 与 FOP/Batik 的关系:xmlgraphics-commons 是 Apache FOP(XSL-FO 转 PDF/PS)与 Batik 共用的底层库,图像加载管线与 Graphics2D 实现直接服务打印输出;用 FOP 的项目通常已隐式依赖它,直接使用同版本(2.11 配 FOP 2.9+)可避免冲突。
  2. 什么场景选它:需要「带物理分辨率的图像写出」「多帧 TIFF」「PostScript/EPS 生成」「PS 常用编码流(Base64/ASCII85/Flate)」时是 Java 生态的标准答案。
  3. 什么场景不选它:只做 PDF 文档交付选 PDFBox;只补 ImageIO 读入格式选 TwelveMonkeys;纯 HTTP 服务端缩略图用 JDK ImageIO 足矣。

七、参考链接

  1. Apache XML Graphics Commons 官方主页:https://xmlgraphics.apache.org/commons/
  2. 2.11 Javadoc(image.loader/image.writer 包):https://xmlgraphics.apache.org/commons/javadoc/2.11/
  3. Maven 中央仓库坐标页:https://repo1.maven.org/maven2/org/apache/xmlgraphics/xmlgraphics-commons/
  4. 官方图像加载管线说明(ImageLoader User Guide):https://xmlgraphics.apache.org/commons/image-loader.html
  5. Apache FOP:https://xmlgraphics.apache.org/fop/