Apache Commons Imaging 快速入门与集成示例
Apache Commons Imaging 1.0.0-alpha6 快速上手:依赖引入、核心 API、读 EXIF/DPI、格式互转、缩略图、EXIF 写入五组可独立运行示例,全部附实测回显与注意事项。
Apache Commons Imaging 快速入门与集成示例
目标:以可独立编译运行的示例代码演示 Apache Commons Imaging 1.0.0-alpha6 的五大核心能力(读元数据、读 EXIF、格式互转、缩略图、EXIF 写入),全部示例已在 Ubuntu 22.04.5 LTS(JDK 17)实测运行并附真实回显。
一、环境信息
本节列出本文实测环境与代码基线。
| 项目 | 实测值 |
|---|---|
| 操作系统 | Ubuntu 22.04.5 LTS(x86_64) |
| JDK | OpenJDK 17.0.20.1 |
| Maven | 3.6.3 |
| 组件 | commons-imaging 1.0.0-alpha6 |
| 项目目录 | /opt/imaging-lab/maven |
| 测试图片 | test.png(800x600、300DPI)、test.jpg(带 EXIF)、test.bmp / test.gif / test.tiff |
💡 说明:测试图片由 Pillow 生成(RGB 渐变 PNG、带 EXIF 的 JPEG、纯色 BMP、调色板 GIF、JPEG 转存的 TIFF),覆盖五种格式。
二、前置条件
- JDK 17 + Maven 就绪(见《Ubuntu 22.04 安装 Apache Commons Imaging 指南》);
- 生成测试图片:
cd /opt/imaging-lab && python3 - <<'EOF'
from PIL import Image, ImageDraw
img = Image.new("RGB", (800, 600))
for y in range(600):
for x in range(800):
img.putpixel((x, y), (int(x/800*255), int(y/600*255), 128))
d = ImageDraw.Draw(img)
d.rectangle([50, 50, 250, 150], outline=(255, 255, 255), width=3)
img.save("test.png", dpi=(300, 300))
exif = Image.Exif()
exif[271] = "TestCamera Inc"
exif[272] = "TC-9000"
exif[306] = "2026:10:14 10:30:00"
exif[274] = 1
img2 = Image.new("RGB", (640, 480), (30, 90, 200))
img2.save("test.jpg", quality=92, dpi=(300, 300), exif=exif)
img.convert("P", palette=Image.ADAPTIVE, colors=128).save("test.gif")
Image.new("RGB", (320, 240), (200, 40, 40)).save("test.bmp")
img2.save("test.tiff")
print("5 test images generated")
EOF
输出(实测):
5 test images generated
三、依赖引入
在 pom.xml 中声明坐标(关键配置说明见安装篇 4.3,此处直接给出完整最小 pom):
<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>com.example</groupId>
<artifactId>imaging-demo</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-imaging</artifactId>
<version>1.0.0-alpha6</version>
</dependency>
</dependencies>
</project>
编译并准备运行 classpath(后续示例共用):
cd /opt/imaging-lab/maven
mvn -q compile
mvn -q dependency:build-classpath -Dmdep.outputFile=cp.txt
💡 说明:示例统一用
java -Xmx256m -cp "target/classes:$(cat cp.txt)" 主类运行,堆限 256MB(256-512MB 建议区间)。
四、核心 API 概览
| API | 作用 | 关键返回 |
|---|---|---|
| Imaging.guessFormat(File) | 按文件头识别格式 | ImageFormat 枚举 |
| Imaging.getImageSize(File) | 获取像素尺寸 | java.awt.Dimension |
| Imaging.getImageInfo(File) | 获取格式/位深/色彩/DPI | ImageInfo |
| Imaging.getMetadata(File) | 获取 EXIF 等元数据 | ImageMetadata |
| Imaging.getBufferedImage(File) | 解码为图像对象 | BufferedImage |
| Imaging.writeImage(BufferedImage, File, ImageFormat) | 写出图像 | void |
| ExifRewriter.updateExifMetadataLossless | 无损改写 JPEG EXIF | void |
五、集成示例
5.1 读取图片元数据(格式/尺寸/DPI)
import org.apache.commons.imaging.*;
import java.io.File;
public class MetaReader {
public static void main(String[] args) throws Exception {
for (String name : new String[]{"test.png", "test.jpg", "test.bmp", "test.gif", "test.tiff"}) {
File f = new File("/opt/imaging-lab", name);
ImageFormat fmt = Imaging.guessFormat(f);
ImageInfo info = Imaging.getImageInfo(f);
System.out.printf("[%s] format=%s (%s), %dx%d px, %d bpp, colorType=%s, dpi=%dx%d%n",
name, fmt.getName(), info.getFormatName(), info.getWidth(), info.getHeight(),
info.getBitsPerPixel(), info.getColorType(),
info.getPhysicalWidthDpi(), info.getPhysicalHeightDpi());
}
}
}
运行与输出(实测):
[test.png] format=PNG (PNG Portable Network Graphics), 800x600 px, 24 bpp, colorType=RGB, dpi=300x300
[test.jpg] format=JPEG (JPEG (Joint Photographic Experts Group) Format), 640x480 px, 24 bpp, colorType=YCbCr, dpi=300x300
[test.bmp] format=BMP (BMP Windows Bitmap), 320x240 px, 24 bpp, colorType=RGB, dpi=96x96
[test.gif] format=GIF (Graphics Interchange Format), 800x600 px, 1 bpp, colorType=RGB, dpi=72x72
[test.tiff] format=TIFF (TIFF Tag-based Image File Format), 640x480 px, 24 bpp, colorType=RGB, dpi=-1x-1
要点:五种格式识别与尺寸/DPI 读取全部成功;BMP 默认 96DPI、GIF 默认 72DPI;TIFF 测试图无 DPI 元数据返回 -1。
5.2 读取 JPEG EXIF 元数据
import org.apache.commons.imaging.Imaging;
import org.apache.commons.imaging.common.ImageMetadata;
import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
import org.apache.commons.imaging.formats.tiff.TiffField;
import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
import java.io.File;
public class ExifReader {
public static void main(String[] args) throws Exception {
File f = new File("/opt/imaging-lab/test.jpg");
ImageMetadata md = Imaging.getMetadata(f);
System.out.println("metadata class: " + md.getClass().getName());
for (ImageMetadata.ImageMetadataItem item : md.getItems()) {
System.out.println(" " + item);
}
if (md instanceof JpegImageMetadata jmd) {
TiffField make = jmd.findExifValue(TiffTagConstants.TIFF_TAG_MAKE);
TiffField model = jmd.findExifValue(TiffTagConstants.TIFF_TAG_MODEL);
TiffField dt = jmd.findExifValue(TiffTagConstants.TIFF_TAG_DATE_TIME);
TiffField orient = jmd.findExifValue(TiffTagConstants.TIFF_TAG_ORIENTATION);
System.out.println(" Make = " + (make != null ? make.getValueDescription() : "null"));
System.out.println(" Model = " + (model != null ? model.getValueDescription() : "null"));
System.out.println(" DateTime = " + (dt != null ? dt.getValueDescription() : "null"));
System.out.println(" Orientation= " + (orient != null ? orient.getValueDescription() : "null"));
TiffImageMetadata exif = jmd.getExif();
for (ImageMetadata.ImageMetadataItem item : exif.getDirectories()) {
TiffImageMetadata.Directory dir = (TiffImageMetadata.Directory) item;
for (TiffField field : dir.getAllFields()) {
System.out.printf(" [dir=%d] %s = %s%n", field.getDirectoryType(),
field.getTagInfo().name, field.getValueDescription());
}
}
}
}
}
运行与输出节选(实测):
metadata class: org.apache.commons.imaging.formats.jpeg.JpegImageMetadata
Make: 'TestCamera Inc'
Model: 'TC-9000'
Orientation: 1
DateTime: '2026:10:14 10:30:00'
Unknown Tag (0x8827): 300
Opto - Electric Conv Factor: 300
Make = 'TestCamera Inc'
Model = 'TC-9000'
DateTime = '2026:10:14 10:30:00'
Orientation= 1
[dir=0] Make = 'TestCamera Inc'
[dir=0] Model = 'TC-9000'
[dir=0] Orientation = 1
[dir=0] DateTime = '2026:10:14 10:30:00'
[dir=0] Unknown Tag = 300
[dir=0] Opto - Electric Conv Factor = 300
要点:Pillow 写入的 Make/Model/Orientation/DateTime 全部被精确读回;findExifValue 适合取单字段,遍历 getDirectories() 的 getAllFields() 适合整目录导出。
5.3 格式互转(PNG/JPEG/BMP/GIF/TIFF)
import org.apache.commons.imaging.*;
import java.awt.image.BufferedImage;
import java.io.File;
public class Converter {
static BufferedImage toRgb(BufferedImage src) {
if (src.getType() == BufferedImage.TYPE_INT_RGB) return src;
BufferedImage rgb = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_INT_RGB);
rgb.getGraphics().drawImage(src, 0, 0, null);
return rgb;
}
public static void main(String[] args) throws Exception {
String[][] pairs = {
{"test.png", "out_png2bmp.bmp", "BMP"},
{"test.png", "out_png2gif.gif", "GIF"},
{"test.png", "out_png2tiff.tiff", "TIFF"},
{"test.jpg", "out_jpg2png.png", "PNG"},
{"test.jpg", "out_jpg2bmp.bmp", "BMP"},
{"test.gif", "out_gif2png.png", "PNG"},
{"test.gif", "out_gif2tiff.tiff", "TIFF"},
{"test.tiff", "out_tiff2png.png", "PNG"},
{"test.bmp", "out_bmp2png.png", "PNG"},
{"test.bmp", "out_bmp2tiff.tiff", "TIFF"},
};
for (String[] p : pairs) {
File in = new File("/opt/imaging-lab", p[0]);
File out = new File("/opt/imaging-lab", p[1]);
BufferedImage bi = toRgb(Imaging.getBufferedImage(in));
Imaging.writeImage(bi, out, ImageFormats.valueOf(p[2]));
System.out.printf("%s(%d bytes) -> %s(%d bytes) guess=%s%n",
p[0], in.length(), p[1], out.length(), Imaging.guessFormat(out).getName());
}
}
}
运行与输出(实测,10 组全部成功):
test.png(3931 bytes) -> out_png2bmp.bmp(1440054 bytes) guess=BMP
test.png(3931 bytes) -> out_png2gif.gif(34198 bytes) guess=GIF
test.png(3931 bytes) -> out_png2tiff.tiff(93436 bytes) guess=TIFF
test.jpg(15976 bytes) -> out_jpg2png.png(40646 bytes) guess=PNG
test.jpg(15976 bytes) -> out_jpg2bmp.bmp(921654 bytes) guess=BMP
test.gif(35887 bytes) -> out_gif2png.png(10903 bytes) guess=PNG
test.gif(35887 bytes) -> out_gif2tiff.tiff(40520 bytes) guess=TIFF
test.tiff(921740 bytes) -> out_tiff2png.png(3736 bytes) guess=PNG
test.bmp(230454 bytes) -> out_bmp2png.png(792 bytes) guess=PNG
test.bmp(230454 bytes) -> out_bmp2tiff.tiff(5236 bytes) guess=TIFF
要点:读任意格式 + 写 PNG/GIF/TIFF/BMP 均可;输出后用 guessFormat 复核文件头,10/10 与目标一致。BMP 为无压缩位图体积最大,PNG 无损压缩体积最小。
⚠️ 注意:JPEG 在 1.0.0-alpha6 只支持解码,
Imaging.writeImage(bi, file, ImageFormats.JPEG)实测抛ImagingException: This image format (Jpeg-Custom) cannot be written.。写 JPEG 请用 JDK 内置 ImageIO:ImageIO.write(bi, "jpg", outFile)(实测成功,20273 bytes,guess=JPEG)。
5.4 生成缩略图
import org.apache.commons.imaging.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
public class Thumbnailer {
public static void main(String[] args) throws Exception {
File src = new File("/opt/imaging-lab/test.png");
File dst = new File("/opt/imaging-lab/out_thumb.png");
BufferedImage srcImg = Imaging.getBufferedImage(src);
BufferedImage thumb = new BufferedImage(160, 120, BufferedImage.TYPE_INT_RGB);
Graphics2D g = thumb.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(srcImg, 0, 0, 160, 120, null);
g.dispose();
Imaging.writeImage(thumb, dst, ImageFormats.PNG);
ImageInfo info = Imaging.getImageInfo(dst);
System.out.println("thumbnail: " + dst.getName() + " (" + dst.length() + " bytes)");
System.out.println(" size=" + info.getWidth() + "x" + info.getHeight()
+ " format=" + info.getFormatName());
}
}
运行与输出(实测):
thumbnail: out_thumb.png (40365 bytes)
size=160x120 format=PNG Portable Network Graphics
要点:读原图 → Graphics2D 双线性插值缩放 → 写出,三步完成缩略图;本例缩到 160x120(原图 800x600 的 1/5),体积从 3931 增至 40365 字节是因为 PNG 对平滑渐变缩放后的高频内容压缩收益下降,改用 JPEG(ImageIO)通常更小。
5.5 EXIF 无损写入(ExifRewriter)
import org.apache.commons.imaging.Imaging;
import org.apache.commons.imaging.common.RationalNumber;
import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
import org.apache.commons.imaging.formats.jpeg.exif.ExifRewriter;
import org.apache.commons.imaging.formats.tiff.TiffField;
import org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants;
import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo;
import org.apache.commons.imaging.formats.tiff.write.TiffOutputDirectory;
import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class ExifWriter {
public static void main(String[] args) throws Exception {
File src = new File("/opt/imaging-lab/test.jpg");
File dst = new File("/opt/imaging-lab/test_exif_out.jpg");
TiffOutputSet set = new TiffOutputSet();
TiffOutputDirectory rootDir = set.getOrCreateRootDirectory();
TiffOutputDirectory exifDir = set.getOrCreateExifDirectory();
rootDir.add(TiffTagConstants.TIFF_TAG_MAKE, "NewCamera Ltd");
rootDir.add(TiffTagConstants.TIFF_TAG_SOFTWARE, "Apache Commons Imaging 1.0.0-alpha6");
rootDir.add(TiffTagConstants.TIFF_TAG_ARTIST, "QA Tester");
exifDir.add(ExifTagConstants.EXIF_TAG_EXPOSURE_TIME, RationalNumber.valueOf(0.125));
try (OutputStream os = new FileOutputStream(dst)) {
new ExifRewriter().updateExifMetadataLossless(src, os, set);
}
System.out.println("written: " + dst.getName() + " (" + dst.length() + " bytes)");
JpegImageMetadata md = (JpegImageMetadata) Imaging.getMetadata(dst);
for (TagInfo tag : new TagInfo[]{
TiffTagConstants.TIFF_TAG_MAKE,
TiffTagConstants.TIFF_TAG_SOFTWARE,
TiffTagConstants.TIFF_TAG_ARTIST,
ExifTagConstants.EXIF_TAG_EXPOSURE_TIME}) {
TiffField field = md.findExifValue(tag);
System.out.println(" " + tag.name + " = " + (field != null ? field.getValueDescription() : "null"));
}
}
}
运行与输出(实测,写入后立即回读验证):
written: test_exif_out.jpg (15994 bytes)
Make = 'NewCamera Ltd'
Software = 'Apache Commons Imaging 1.0.0-alpha6'
Artist = 'QA Tester'
ExposureTime = 1/8 (0.125)
要点:updateExifMetadataLossless 无损改写(JPEG 像素数据不变,仅重写元数据段),无 EXIF 的 JPEG 也会自动新建 EXIF;Make/Software/Artist 属 IFD0 主目录,ExposureTime 属 EXIF 子目录,须写对目录。
5.6 Web 集成提示(Servlet 场景)
上传解析 + 下载输出的典型调用形态(伪代码骨架,非完整工程):
// 上传:从 InputStream 解码并读元数据
BufferedImage img = Imaging.getBufferedImage(request.getInputStream(), "png");
ImageInfo info = Imaging.getImageInfo(request.getInputStream(), "png");
// 处理:缩放/转格式/生成缩略图
// 输出:写 PNG/TIFF 到 response.getOutputStream()
Imaging.writeImage(processed, response.getOutputStream(), ImageFormats.PNG);
要点:getBufferedImage(InputStream, String) 与 writeImage(BufferedImage, OutputStream, ImageFormat) 均支持流式输入输出,适配 Servlet/HTTP 上传下载;堆内存按《配置参数详解》篇 5.6 控制在 256-512m。
六、注意事项
- JPEG 不可写:1.0.0-alpha6 无 JPEG 编码器,写 JPEG 用
ImageIO.write(bi, "jpg", file);需要压缩质量控制的用 ImageWriter 参数; - TYPE_CUSTOM 转换:部分格式(如渐变 PNG)解码为 TYPE_CUSTOM,直接 writeImage 可能失败,先转 TYPE_INT_RGB(见 5.3 的 toRgb 工具方法,实测 10/10 成功);
- EXIF 目录归属:TIFF 标准标签写 root directory,EXIF 专属标签写 exif directory,写错回读为 null;
- classpath 完整:缺 commons-io 运行报 NoClassDefFoundError,用
mvn dependency:build-classpath生成; - 堆内存:批处理按队列逐张处理,
-Xmx256-512m 足够(实测 800x600 图解析仅数十 MB)。
七、参考链接
- 官方示例源码:https://github.com/apache/commons-imaging/tree/master/src/test/java/org/apache/commons/imaging
- Javadoc:https://commons.apache.org/proper/commons-imaging/apidocs/
- 格式支持矩阵:https://commons.apache.org/proper/commons-imaging/formatsupport.html
- Maven 坐标:https://repo1.maven.org/maven2/org/apache/commons/commons-imaging/
- RELEASE-NOTES:https://downloads.apache.org/commons/imaging/RELEASE-NOTES.txt