Im using the TwelveMonkey's lib to read Exif data from jpeg like:
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);
}
}
therefore I have a CompoundDirectory
with a bunch of Entry
elements. But how do I use the ExifWriter
to a jpeg. Using it to write to the outputstream just corrupts the jpeg (image viewers think it is a broken tiff).
Update:
What I like to achieve is reading a jpeg to a BufferedImage
, also reading exif data, scaling it and then compressing it to jpeg again retaining the exif data (ie. writing the previously read data to the scaled out jpeg). For this I currently use some verbose version of ImageIO
methods. Here is the basic code to do this currently: https://gist.github.com/patrickfav/5a51566f31c472d02884 (exif reader seems to work, writer not of course)
The TwelveMonkeys Exif package (the EXIFReader/EXIFWriter
) is quite low-level, and is designed to be efficient for use by ImageReader/ImageWriter
implementations. It's still fully usable as a general purpose meta data package, but it might require more work on your part, and some knowledge of the container format used to carry the Exif data.
To write Exif data to a JPEG, you need to write an APP1/Exif
segment as part of the normal JIF structure. The EXIFWriter
will write the data you should put inside this segment only. Everything else must be provided by you.
There are multiple ways of achieving this. You can work with a JPEG on binary/stream level, or you could modify the image data and use ImageIO meta data to write the Exif. I'll outline the process of writing Exif using the IIOMetadata
class.
From 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.)
The EXIFReader
will be your "application specific code to interpret the data". In the same way, you should be able to insert an unknown
marker segment node with an APP1
(normally, that would be 0xFFE1
, but in the ImageIO metadata, only the decimal representation of the last octet as as string is used, thus the value is "225"
). Use a ByteArrayOutputStream
and write the Exif data to that, and pass the resulting byte array to meta data node as "user object".
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);
If your original meta data already contains an Exif segment, it's probably better use:
// 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);
I don't like the ImageIO metadata API particularly, because of the extreme verboseness of the code, but I hope you get the idea of how to achieve your goal. :-)
PS: The reason image viewers think your image is a TIFF, is that the Exif data is a TIFF structure. If you only write the Exif data from a JPEG to an otherwise empty file, you will have a TIFF file with no image data in IFD0
(and possibly a thumbnail in IFD1
).