如何使用 TwelveMonkey 的 ExifWriter 将 Exif 写入 JPEG class
How to write Exif to a JPEG with TwelveMonkey's ExifWriter class
我正在使用 TwelveMonkey's 库从 jpeg 中读取 Exif 数据,例如:
try (ImageInputStream stream = ImageIO.createImageInputStream(input)) {
List<JPEGSegment> exifSegment = JPEGSegmentUtil.readSegments(stream, JPEG.APP1, "Exif");
InputStream exifData = exifSegment.get(0).data();
exifData.read(); // Skip 0-pad for Exif in JFIF
try (ImageInputStream exifStream = ImageIO.createImageInputStream(exifData)) {
return new EXIFReader().read(exifStream);
}
}
因此我有一个 CompoundDirectory
和一堆 Entry
元素。但是我如何使用 ExifWriter
来生成 jpeg。使用它写入输出流只会损坏 jpeg(图像查看器认为它是损坏的 tiff)。
更新:
我想实现的是将 jpeg 读取到 BufferedImage
,还读取 exif 数据,对其进行缩放,然后再次将其压缩为 jpeg,同时保留 exif 数据(即,将先前读取的数据写入缩放后的 jpeg)。为此,我目前使用一些详细版本的 ImageIO
方法。这是当前执行此操作的基本代码:https://gist.github.com/patrickfav/5a51566f31c472d02884(exif reader 似乎有效,作者当然不行)
TwelveMonkeys Exif 包(EXIFReader/EXIFWriter
)非常低级,旨在为 ImageReader/ImageWriter
实现提供高效使用。它仍然可以完全用作通用元数据包,但它可能需要您做更多的工作,并且需要了解用于承载 Exif 数据的容器格式。
要将 Exif 数据写入 JPEG,您需要写入一个 APP1/Exif
段作为 normal JIF structure 的一部分。 EXIFWriter
只会写入您应该放在 里面 的数据。其他一切都必须由您提供。
有多种方法可以实现这一点。您可以在 binary/stream 级别上使用 JPEG,或者您可以修改图像数据并使用 ImageIO 元数据来编写 Exif。我将概述使用 IIOMetadata
class.
编写 Exif 的过程
来自 JPEG Metadata Format Specification and Usage Notes:
(Note that an application wishing to interpret Exif metadata given a metadata tree structure in the javax_imageio_jpeg_image_1.0
format must check for an unknown
marker segment with a tag indicating an APP1
marker and containing data identifying it as an Exif marker segment. Then it may use application-specific code to interpret the data in the marker segment. If such an application were to encounter a metadata tree formatted according to a future version of the JPEG metadata format, the Exif marker segment might not be unknown in that format - it might be structured as a child node of the JPEGvariety
node. Thus, it is important for an application to specify which version to use by passing the string identifying the version to the method/constructor used to obtain an IIOMetadata
object.)
EXIFReader
将是您的“解释数据的应用程序特定代码”。同样,您应该能够插入一个带有 APP1
的 unknown
标记段节点(通常是 0xFFE1
,但在 ImageIO 元数据中,只有使用最后一个八位字节作为字符串,因此值为 "225"
)。使用 ByteArrayOutputStream
并将 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 数据写入其他空文件,您将在 IFD0
中得到一个没有图像数据的 TIFF 文件(并且可能在 IFD1
中有一个缩略图)。
我正在使用 TwelveMonkey's 库从 jpeg 中读取 Exif 数据,例如:
try (ImageInputStream stream = ImageIO.createImageInputStream(input)) {
List<JPEGSegment> exifSegment = JPEGSegmentUtil.readSegments(stream, JPEG.APP1, "Exif");
InputStream exifData = exifSegment.get(0).data();
exifData.read(); // Skip 0-pad for Exif in JFIF
try (ImageInputStream exifStream = ImageIO.createImageInputStream(exifData)) {
return new EXIFReader().read(exifStream);
}
}
因此我有一个 CompoundDirectory
和一堆 Entry
元素。但是我如何使用 ExifWriter
来生成 jpeg。使用它写入输出流只会损坏 jpeg(图像查看器认为它是损坏的 tiff)。
更新:
我想实现的是将 jpeg 读取到 BufferedImage
,还读取 exif 数据,对其进行缩放,然后再次将其压缩为 jpeg,同时保留 exif 数据(即,将先前读取的数据写入缩放后的 jpeg)。为此,我目前使用一些详细版本的 ImageIO
方法。这是当前执行此操作的基本代码:https://gist.github.com/patrickfav/5a51566f31c472d02884(exif reader 似乎有效,作者当然不行)
TwelveMonkeys Exif 包(EXIFReader/EXIFWriter
)非常低级,旨在为 ImageReader/ImageWriter
实现提供高效使用。它仍然可以完全用作通用元数据包,但它可能需要您做更多的工作,并且需要了解用于承载 Exif 数据的容器格式。
要将 Exif 数据写入 JPEG,您需要写入一个 APP1/Exif
段作为 normal JIF structure 的一部分。 EXIFWriter
只会写入您应该放在 里面 的数据。其他一切都必须由您提供。
有多种方法可以实现这一点。您可以在 binary/stream 级别上使用 JPEG,或者您可以修改图像数据并使用 ImageIO 元数据来编写 Exif。我将概述使用 IIOMetadata
class.
来自 JPEG Metadata Format Specification and Usage Notes:
(Note that an application wishing to interpret Exif metadata given a metadata tree structure in the
javax_imageio_jpeg_image_1.0
format must check for anunknown
marker segment with a tag indicating anAPP1
marker and containing data identifying it as an Exif marker segment. Then it may use application-specific code to interpret the data in the marker segment. If such an application were to encounter a metadata tree formatted according to a future version of the JPEG metadata format, the Exif marker segment might not be unknown in that format - it might be structured as a child node of theJPEGvariety
node. Thus, it is important for an application to specify which version to use by passing the string identifying the version to the method/constructor used to obtain anIIOMetadata
object.)
EXIFReader
将是您的“解释数据的应用程序特定代码”。同样,您应该能够插入一个带有 APP1
的 unknown
标记段节点(通常是 0xFFE1
,但在 ImageIO 元数据中,只有使用最后一个八位字节作为字符串,因此值为 "225"
)。使用 ByteArrayOutputStream
并将 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 数据写入其他空文件,您将在 IFD0
中得到一个没有图像数据的 TIFF 文件(并且可能在 IFD1
中有一个缩略图)。