TwelveMonkeys Exif 包(EXIFReader/EXIFWriter
)非常低级,旨在为实现高效使用ImageReader/ImageWriter
。它仍然可以完全用作通用元数据包,但您可能需要做更多的工作,并且需要了解用于承载 Exif 数据的容器格式。
要将 Exif 数据写入 JPEG,您需要编写一个APP1/Exif
段作为正常 JIF 结构的一部分。将只EXIFWriter
写入您应该放在此段内的数据。其他一切都必须由您提供。
有多种方法可以实现这一目标。您可以在二进制/流级别使用 JPEG,或者您可以修改图像数据并使用 ImageIO 元数据来编写 Exif。我将概述使用IIOMetadata
该类编写 Exif 的过程。
来自JPEG 元数据格式规范和使用说明:
(请注意,在给定元数据树结构的格式中,希望解释 Exif 元数据的应用程序javax_imageio_jpeg_image_1.0
必须检查unknown
具有指示标记的APP1
标记并包含将其标识为 Exif 标记段的数据的标记段。然后它可以使用特定于应用程序的代码来解释标记段中的数据。如果这样的应用程序遇到根据 JPEG 元数据格式的未来版本格式化的元数据树,则 Exif 标记段在该格式中可能不是未知的 - 它可能被构造为子节点JPEGvariety
因此,应用程序通过将标识版本的字符串传递给用于获取对象的方法/构造函数来指定要使用的版本非常重要。IIOMetadata
)
这EXIFReader
将是您的“用于解释数据的应用程序特定代码”。以同样的方式,您应该能够插入一个unknown
标记段节点APP1
(通常为0xFFE1
,但在 ImageIO 元数据中,仅使用最后一个八位字节的十进制表示为字符串,因此值为"225"
)。使用 aByteArrayOutputStream
并将 Exif 数据写入其中,并将生成的字节数组作为“用户对象”传递给元数据节点。
IIOMetadata metadata = ...;
IIOMetadataNode root = new IIOMetadataNode("javax_imageio_jpeg_image_1.0");
IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence");
root.appendChild(markerSequence);
Collection<Entry> entries = ...; // Your original Exif entries
// Write the full Exif segment data
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
// APPn segments are prepended with a 0-terminated ASCII identifer
bytes.write("Exif".getBytes(StandardCharsets.US_ASCII));
bytes.write(new byte[2]); // Exif uses 0-termination + 0 pad for some reason
// Write the Exif data (note that Exif is a TIFF structure)
new TIFFWriter().write(entries, new MemoryCacheImageOutputStream(bytes));
// Wrap it all in a meta data node
IIOMetadataNode exif = new IIOMetadataNode("unknown");
exif.setAttribute("MarkerTag", String.valueOf(0xE1)); // APP1 or "225"
exif.setUserObject(bytes.toByteArray());
// Append Exif node
markerSequence.appendChild(exif);
// Merge with original data
metadata.mergeTree("javax_imageio_jpeg_image_1.0", root);
如果您的原始元数据已经包含一个 Exif 段,则可能更好地使用:
// Start out with the original tree
IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree("javax_imageio_jpeg_image_1.0");
IIOMetadataNode markerSequence = (IIOMetadataNode) root.getElementsByTagName("markerSequence").item(0); // Should always a single markerSequence
...
// Remove any existing Exif, or make sure you update the node,
// to avoid having two Exif nodes
// Logic for creating the node as above
...
// Replace the tree, instead of merging
metadata.setFromTree("javax_imageio_jpeg_image_1.0", root);
我特别不喜欢 ImageIO 元数据 API,因为代码极其冗长,但我希望您了解如何实现您的目标。:-)
PS:图像查看者认为您的图像是 TIFF 的原因是 Exif 数据是TIFF 结构。如果您只将 JPEG 中的 Exif 数据写入其他空文件,您将拥有一个 TIFF 文件,其中没有图像数据IFD0
(并且可能在 中包含缩略图IFD1
)。