如何使用TwelveMonkey的ExifWriter类将Exif写入JPEG

时间:2016-03-16 07:32:45

标签: java jpeg exif twelvemonkeys

我使用TwelveMonkey's lib从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阅读器似乎有效,写作者当然不是)

1 个答案:

答案 0 :(得分:1)

TwelveMonkeys Exif包(EXIFReader/EXIFWriter)非常低级,旨在高效地供ImageReader/ImageWriter实现使用。它仍然可以完全用作通用元数据包,但是您可能需要更多的工作,以及用于携带Exif数据的容器格式的一些知识。

要将Exif数据写入JPEG,您需要将APP1/Exif段写为normal JIF structure的一部分。 EXIFWriter只会将内的数据写入此段。其他一切都必须由您提供。

实现这一目标有多种方法。您可以在二进制/流级别上使用JPEG,或者可以修改图像数据并使用ImageIO元数据来编写Exif。我将概述使用IIOMetadata类编写Exif的过程。

来自JPEG Metadata Format Specification and Usage Notes

  

(请注意,希望以javax_imageio_jpeg_image_1.0格式提供元数据树结构来解释Exif元数据的应用程序必须检查unknown标记段,其中标记指示APP1标记并包含数据将其标识为Exif标记段。然后它可以使用特定于应用程序的代码来解释标记段中的数据。如果这样的应用程序遇到根据未来版本格式化的元数据树在JPEG元数据格式中,Exif标记段可能不是那种格式的未知 - 它可能被构造为JPEGvariety节点的子节点。因此,应用程序通过传递指定使用哪个版本很重要标识用于获取IIOMetadata对象的方法/构造函数的版本的字符串。)

EXIFReader将是您的应用程序特定代码,用于解释数据&#34;。以同样的方式,您应该能够插入unknown标记段节点和APP1(通常是0xFFE1,但在ImageIO元数据中,只有十进制表示使用最后一个八位字节作为字符串,因此值为"225")。使用ByteArrayOutputStream并将Exif数据写入其中,并将生成的字节数组作为&#34; user object&#34;传递给元数据节点。

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
// Finally write the EXIF data
new EXIFWriter().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 data 
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,因为代码非常冗长,但我希望你能够了解如何实现目标。 : - )

PS:图像查看者认为您的图像是TIFF的原因是Exif数据 TIFF结构。如果您只将JPEG中的Exif数据写入其他空文件,那么您将在IFD0中找到一个没有图像数据的TIFF文件(可能还有IFD1中的缩略图)。