Apache XML Graphics Commons 2.11 图像编解码与 IO API 详解

系统讲解 Apache XML Graphics Commons 2.11 的图像写入 API(ImageWriterRegistry/ImageWriterParams/PNG/TIFF/多帧)、ImageManager 预检与解码管线(ImageInfo/ImageSize/ImageFlavor/ImageBuffered/ImageContext)与 util.io 编码流(Base64 回环及五种编码大小对比),逐项代码配真实输出,全部已在 Ubuntu 22.04 实测。

Apache XML Graphics CommonsApache XML Graphics Commons2026/10/15

Apache XML Graphics Commons 2.11 图像编解码与 IO API 详解

目标:系统掌握 Apache XML Graphics Commons 2.11 的两大 API 面——图像写入(ImageWriterRegistry、ImageWriterParams、PNG/TIFF 与多帧编码)与图像读取预检(ImageManager、ImageInfo、ImageFlavor、ImageContext),外加 util.io 编码流族(Base64/ASCIIHex/ASCII85/Flate/RunLength);每个方法都配完整示例代码与实测输出。以下全部命令与回显均已在 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)
  • 传递依赖:commons-io 2.17.0、commons-logging 1.3.0
  • Maven 示例工程目录:/opt/xgc-demo,产物目录 /opt/xgc-demo/output

确认版本基线:

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

输出:

openjdk version "17.0.20.1" 2026-08-18
Apache Maven 3.9.16 (2bdd9fddda4b155ebf8000e807eb73fd829a51d5)

💡 说明:本文所有运行命令统一为 java -Dfile.encoding=UTF-8 -Djava.awt.headless=true -cp target/classes:$CP demo.XgcDemo <组名> 形式($CP 为编译输出与三件套 jar 拼接的 classpath,见第 3 篇 2.3 节);-Djava.awt.headless=true 在无图形界面的服务器上声明 AWT 运行模式,实测图像编码与预检全链路可用。

二、API 总览

2.11 的编解码与 IO 能力按「写入、读取预检、编码流」三块组织(均经本文实测):

API包角色关键方法/常量详解章节
ImageWriterRegistryimage.writer按 MIME 查找写出器的注册表getInstance、getWriterFor3.1
ImageWriterimage.writer图像写出器接口writeImage、getMIMEType、isFunctional、createMultiImageWriter3.1、3.3
ImageWriterParamsimage.writer跨格式写出参数setResolution、setCompressionMethod、setJPEGQuality、setRowsPerStrip、setEndianness3.2
ImageIOPNGImageWriter / ImageIOTIFFImageWriterimage.writer.imageio内置 PNG/TIFF 实现(writeImage)3.3、3.4
MultiImageWriterimage.writer多帧写出接口writeImage、close3.5
ImageManagerimage.loader加载管线门面preloadImage、getImage4.1
ImageInfo / ImageSizeimage.loader预检结果(MIME+尺寸)getMimeType、getSize、getWidthPx、getDpiHorizontal、getDimensionPt4.2
ImageFlavor / Imageimage.loader解码目标形态ImageFlavor.BUFFERED_IMAGE、getFlavor4.3
ImageBufferedimage.loader.implBufferedImage 形态实现getBufferedImage4.3
ImageContext / ImageSessionContextimage.loader环境分辨率与会话上下文getSourceResolution、DefaultImageSessionContext4.4
Base64EncodeStream / Base64DecodeStreamutil.ioBase64 编解码流write、close、read5.1
ASCIIHex/ASCII85/Flate/RunLength EncodeOutputStreamutil.ioPS 常用编码流write、close5.2

⚠️ 注意:ImageIOImageWriter 是通用实现,ImageIOPNGImageWriter/ImageIOTIFFImageWriter 是其 MIME 专用子类;jar tf 实测 2.11 内置服务发现配置,getWriterFor("image/png") 实测返回 ImageIOPNGImageWriter。同名网传类 ImageBufferedImage 在 2.11 中不存在,解码实现类是 ImageBuffered(实测编译错误复现见第 3 篇 3.2 节)。

三、写入 API 详解

写出链路的骨架:ImageWriterRegistry 按 MIME 选实现 → ImageWriterParams 携带参数 → writeImage(RenderedImage, OutputStream, params) 编码落盘。

3.1 ImageWriterRegistry:按 MIME 选写出器

注册表是单例,getWriterFor(String mimeType) 返回实现。实测 PNG 与 TIFF 各自返回专用类,并检查功能可用性:

ImageWriter pngWriter = ImageWriterRegistry.getInstance().getWriterFor("image/png");
System.out.println("PNG  writer: " + pngWriter.getClass().getName()
        + " mime=" + pngWriter.getMIMEType()
        + " isFunctional=" + pngWriter.isFunctional()
        + " multi=" + pngWriter.supportsMultiImageWriter());
ImageWriter tiffWriter = ImageWriterRegistry.getInstance().getWriterFor("image/tiff");
System.out.println("TIFF writer: " + tiffWriter.getClass().getName()
        + " mime=" + tiffWriter.getMIMEType()
        + " isFunctional=" + tiffWriter.isFunctional()
        + " multi=" + tiffWriter.supportsMultiImageWriter());

输出(png/tiff 组首行,实测):

writer 实现: org.apache.xmlgraphics.image.writer.imageio.ImageIOPNGImageWriter
writer MIME: image/png, isFunctional=true
writer 实现: org.apache.xmlgraphics.image.writer.imageio.ImageIOTIFFImageWriter, supportsMultiImageWriter=true

解读:isFunctional() 反映实现是否在当前 JVM 可用(ImageIO 实现总是 true);supportsMultiImageWriter() 只有 TIFF 为 true,PNG 单帧。接口还有 getMIMEType() 标识目标格式;自定义写出器可用 register(ImageWriter, int) 以优先级插入注册表。

3.2 ImageWriterParams:跨格式统一参数

参数类把 PNG/TIFF/JPEG 的共性参数收敛到一个类(javap 实测的全部 setter):setResolution(int)(统一写入物理分辨率)、setCompressionMethod(String)(TIFF 压缩名,透传 JDK)、setJPEGQuality(float, boolean)(JPEG 质量与基线化)、setRowsPerStrip(int) 与 setSingleStrip(boolean)(TIFF 条带布局)、setXResolution/setYResolution(TIFF 双向分辨率)、setResolutionUnit(ResolutionUnit)、setEndianness(Endianness)(TIFF 字节序)。实测最常用的是分辨率与压缩:

ImageWriterParams params = new ImageWriterParams();
params.setResolution(300);                    // 300 DPI
params.setCompressionMethod("PackBits");      // 仅 TIFF 有意义
writer.writeImage(img, out, params);

分辨率写进文件后的读回行为见 4.2 节(PNG 量化误差 299.9994、TIFF 精确 300.0——这是参数落盘后的实测验证)。压缩名只接受 JDK TIFF writer 认可的值,实测 PackBits/LZW/Deflate 可用、"None" 抛 IllegalArgumentException: Unknown compression type!(复现回显见第 3 篇 3.3 节),不压缩就不设置该项。

3.3 PNG 写出全流程

png() 组完整代码(样张绘制略,见第 3 篇 2.2 节):

BufferedImage img = drawSample(320, 200, "XML Graphics PNG");
ImageWriter writer = ImageWriterRegistry.getInstance().getWriterFor("image/png");
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());

运行回显(实测):

writer 实现: org.apache.xmlgraphics.image.writer.imageio.ImageIOPNGImageWriter
writer MIME: image/png, isFunctional=true
已写入 /opt/xgc-demo/output/demo.png 字节数=14114
file /opt/xgc-demo/output/demo.png
/opt/xgc-demo/output/demo.png:           PNG image data, 320 x 200, 8-bit/color RGBA, non-interlaced

验证点:file 识别出 320x200 8 位 RGBA,与 BufferedImage.TYPE_INT_ARGB 的编码输入一致;14114 字节含渐变、矩形、椭圆、线条与文字的完整栅格。

3.4 TIFF 压缩矩阵

同一 320x200 样张在三种压缩下的实测(tiff() 组回显):

PackBits 压缩: demo-packbits.tiff 字节数=256377
Deflate  压缩: demo-deflate.tiff 字节数=82104
LZW      压缩: demo-lzw.tiff 字节数=156333
压缩名字节数(实测)相对大小file 识别的压缩标识适用
PackBits256377100%compression=PackBits (Macintosh RLE)老解释器兼容、扫描件
LZW15633361%compression=LZW通用
Deflate8210432%compression=Deflate (PKZIP)渐变/照片类体积最优
None抛异常——不可用:Unknown compression type!(实测)

file 全量回显验证压缩标识确实写入文件头:

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

3.5 多帧 TIFF(MultiImageWriter)

TIFF 支持单文件多帧;writer.createMultiImageWriter(out) 产出多帧接口(注意:非 AutoCloseable,close 必须手动调用,实测编译坑见第 3 篇 3.1 节):

OutputStream mout = new FileOutputStream(new File(OUT, "demo-multi.tiff"));
MultiImageWriter miw = tiffWriter.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

验证点:两帧 PackBits 合计 512829 字节 ≈ 单帧 256377×2 + 页目录开销,TIFF 无帧间去重,多页扫描件按此估算体积。

四、读取与预检 API 详解

读取侧的门面是 ImageManager:preloadImage 只读文件头产出 ImageInfo(不把像素载入内存),getImage 再按目标 ImageFlavor 解码。这条「先预检后解码」的管线是 FOP 版面计算的基础。

4.1 ImageManager:preload 与 getImage

info() 组核心代码:

ImageContext ctx = new DefaultImageContext();
ImageManager manager = new ImageManager(ctx);
ImageSessionContext session = new DefaultImageSessionContext(ctx, new File(OUT));
String uri = new File(OUT, name).toURI().toString();      // file:/opt/xgc-demo/output/demo.png

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);
System.out.println("   解码 flavor=" + image.getFlavor()
        + " 实现类=" + image.getClass().getName());
BufferedImage decoded = ((ImageBuffered) image).getBufferedImage();
System.out.println("   BufferedImage 尺寸=" + decoded.getWidth() + "x" + decoded.getHeight()
        + " 类型=" + decoded.getType());

完整运行回显(实测):

== 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

4.2 ImageInfo 与 ImageSize 字段解读

字段PNG 实测值TIFF 实测值说明
getMimeType()image/pngimage/tiff按文件头嗅探
getWidthPx()/getHeightPx()320/200320/200像素尺寸
getDpiHorizontal()/getDpiVertical()299.9994201660156300.0物理分辨率读回
getDimensionPt()76.8x48.076.8x48.0320px@300dpi = 76.8pt
size.toString()76800x48000 mpt同左mpt=毫点(1/1000 pt),76800 mpt=76.8pt

两个实测锚点:

  1. DPI 精度差异:PNG 的物理分辨率按「像素/米」整数存储(11811 px/m ≈ 299.9994 DPI),读回带量化误差;TIFF 用「像素/英寸」有理数存储,精确还原 300.0。比较 DPI 请用容差(如 Math.abs(dpi - 300) < 0.01)。
  2. 尺寸换算:ImageSize 同时维护 px、mpt、pt 三套值,calcSizeFromPixels()/calcPixelsFromSize() 手动触发换算;本文写出的 300 DPI 在读回时三套值自洽。

4.3 ImageFlavor 体系与 ImageBuffered

ImageFlavor 描述解码目标形态,javap 实测的静态常量包括 RENDERED_IMAGE、BUFFERED_IMAGE、XML_DOM、RAW、RAW_PNG、RAW_JPEG 等。管线会按 flavor 选择加载器/转换器(2.11 内置 ImageLoaderFactoryPNG、ImageLoaderFactoryInternalTIFF、imageio.ImageLoaderFactoryImageIO 等工厂,jar tf 实测)。BUFFERED_IMAGE 形态的实现类是 ImageBuffered,getBufferedImage() 取回标准 BufferedImage:

  • PNG 解码后 getType()=6(BufferedImage.TYPE_INT_ARGB);
  • TIFF 解码后 getType()=0(TYPE_CUSTOM,JDK TIFF 读入器的原生色彩模型),需要 ARGB 时再走 ImageConverterBuffered2Rendered 类转换器或手动转换。

类名锚点:解码实现类是 ImageBuffered;写 ImageBufferedImage 编译报 cannot find symbol(实测复现见第 3 篇 3.2 节)。

4.4 ImageContext 与 ImageSessionContext

ImageContext 接口只有一个方法 getSourceResolution(),表示「未标注物理分辨率的图像按多少 DPI 解释」:

  • DefaultImageContext 默认返回 72.0(实测);
  • 自定义实现返回 300.0(实测),FOP 就是用它把页面 DPI 配置注入管线;
  • DefaultImageSessionContext(parentContext, baseDir) 组合环境上下文与基准目录,preloadImage 解析相对 URI 时以 baseDir 为根;URI 形式为 file:/opt/xgc-demo/output/demo.png(File.toURI() 生成,实测可解析)。文件不存在时抛 FileNotFoundException: Image not found: <uri>(实测报错信息)。

五、util.io 编码流详解

org.apache.xmlgraphics.util.io 提供 PS/PDF 常用的二进制→文本编码流族,全部是「装饰器」用法:包住底层 OutputStream,close 时收尾。

5.1 Base64 编解码回环

base64() 组代码:

byte[] raw = "Apache XML Graphics Commons Base64 round-trip test. ".getBytes(StandardCharsets.UTF_8);
ByteArrayOutputStream enc = new ByteArrayOutputStream();
Base64EncodeStream encoder = new Base64EncodeStream(enc);
encoder.write(raw);
encoder.close();   // 必须关闭,否则尾部 4 字符块不落盘
byte[] encoded = enc.toByteArray();
System.out.println("原始字节数=" + raw.length + " 编码后字节数=" + encoded.length);
System.out.println("编码结果前 64 字符: "
        + new String(encoded, 0, Math.min(64, encoded.length), StandardCharsets.ISO_8859_1));
Base64DecodeStream decoder = new Base64DecodeStream(new ByteArrayInputStream(encoded));
byte[] decoded = decoder.readAllBytes();
System.out.println("解码后字节数=" + decoded.length
        + " 与原文一致=" + Arrays.equals(raw, decoded));

运行回显(实测):

== Base64 编解码(util.io) ==
原始字节数=52 编码后字节数=73
编码结果前 64 字符: QXBhY2hlIFhNTCBHcmFwaGljcyBDb21tb25zIEJhc2U2NCByb3VuZC10cmlwIHRl
解码后字节数=52 与原文一致=true

解读:52 字节原文编码为 73 字节(52/3×4=70 加 1 个换行,实测 Base64EncodeStream 每 76 字符换行但本例不足一行只落 1 个换行符);解码回环逐字节一致。close 语义是硬要求:不 close 会丢失尾部不满 3 字节的块(与 JDK java.util.Base64 静态 API 的最大差异)。

5.2 五种编码流大小对比

对同一 1024 字节样本(周期性文本)实测各编码流输出(base64() 组后半段回显):

-- 各编码流对 1024 字节样本的输出大小对比 --
Base64EncodeStream       -> 1389 字节
ASCIIHexOutputStream     -> 2074 字节
ASCII85OutputStream      -> 1298 字节
FlateEncodeOutputStream  -> 33 字节
RunLengthEncodeOutputStream -> 1034 字节
编码流实测输出膨胀率定位
ASCIIHexOutputStream2074 字节~203%每 byte 2 个 hex 字符,最简单也最大
Base64EncodeStream1389 字节~136%PS/PDF 标准文本编码
ASCII85OutputStream1298 字节~127%PS 高密度文本编码(4 字节→5 字符)
RunLengthEncodeOutputStream1034 字节~101%简单行程压缩,重复数据有效
FlateEncodeOutputStream33 字节~3%zlib 压缩,周期性样本压缩率极高

用法一致:以 new ASCII85OutputStream(underlying) 为例 → write(payload) → close()(close 触发终止符:ASCII85 的 ~>、Flate 的校验尾、RunLength 的 EOD 128)。PS 生成(第 5 篇)里嵌入图像数据正是这些流的组合应用。

5.3 与 Commons IO 的分工

xmlgraphics-commons 自带 commons-io 2.17.0 依赖:本库的 util.io 只做 PS/PDF 编码流,通用流拷贝/文件工具(IOUtils、FileUtils)直接用 commons-io,两者定位互补不重叠。

六、注意事项

  1. close 语义:Base64EncodeStream、MultiImageWriter、五种编码流都依赖 close() 收尾,漏 close 轻则文件截断、重则页目录缺失。
  2. MultiImageWriter 非 AutoCloseable:不能用于 try-with-resources(实测编译错误见第 3 篇 3.1 节)。
  3. TIFF 压缩名:只传实测可用的 PackBits/LZW/Deflate;"None" 抛 Unknown compression type!,不压缩就不设置。
  4. DPI 读回容差:PNG 的 300 DPI 读回为 299.9994201660156(像素/米量化),比较用容差。
  5. 解码实现类名:ImageBuffered,不是 ImageBufferedImage。
  6. 传递依赖:commons-io 与 commons-logging 是编译期依赖,jar 直跑必须带全(官方 bin 包 lib/ 自带)。
  7. 运行编码:POSIX/C locale 下加 -Dfile.encoding=UTF-8,无图形界面加 -Djava.awt.headless=true。

七、参考链接

  1. Apache XML Graphics Commons 官方主页:https://xmlgraphics.apache.org/commons/
  2. 2.11 Javadoc(image.writer/image.loader/util.io 包):https://xmlgraphics.apache.org/commons/javadoc/2.11/
  3. 图像加载管线说明:https://xmlgraphics.apache.org/commons/image-loader.html
  4. Maven 中央仓库坐标页:https://repo1.maven.org/maven2/org/apache/xmlgraphics/xmlgraphics-commons/
  5. JDK ImageIO TIFF 支持说明(压缩类型约束来源):https://docs.oracle.com/en/java/javase/17/docs/api/java.desktop/javax/imageio/ImageWriteParam.html