How to write Exif to a JPEG with TwelveMonkey's ExifWriter class - java

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 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.)
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).

Related

How to get image's (in a file) number of channels (color depth)?

Here we have a good example on how to get image's dimensions from file: https://stackoverflow.com/a/12164026/258483
The method uses ImageReader which tries not to read entire image if it is not required.
Is there a similar method to obtain image's color depth, which is 3 for colored image and 1 for b/w image?
I found it is probably ImageReader#getRawImageType(int) method. Is this correct way?
Yes,
You can use imageReader.getRawImageType(imageNo). This method will work most of the time. Unfortunately, it will in some cases return null, most notably for JPEG images encoded as YCbCr (instead of RGB), and this is probably the most common case for JPEG...
Another way to get the same information, is to use the image meta data object, and look at the standard metadata format, to get this information:
IIOMetadata metadata = imageReader.getImageMetadata(imageNo);
if (metadata.isStandardFormatSupported()) { // true for all bundled formats
IIOMetadataNode root = (IIOMetadataNode) imageMetadata.getAsTree("javax_imageio_1.0");
// Get either (as pseudo-xpath):
// /Chroma/NumChannels[#value], which is just number of channels, 3 for RGB
// /Data/BitsPerSample[#value], bits per sample, typically 8,8,8 for 24 bit RGB
}
You can look at the standard format documentation and IIOMetadataNode API doc for more information.
Took me some time to figure this out and like to share with others. It's Jruby call Java method, but logic is same. Metadata format is defined at here.
iis = ImageIO.createImageInputStream(ByteArrayInputStream.new(document_data.to_java_bytes))
itrs = ImageIO.getImageReaders(iis)
if itrs.has_next
reader = itrs.next
reader.setInput(iis)
metadata = reader.getImageMetadata(0)
if metadata.is_standard_metadata_format_supported
color_depth = metadata.getAsTree('javax_imageio_1.0')
.getElementsByTagName('Chroma').item(0)
.get_elements_by_tag_name('NumChannels').item(0)
.getAttribute('value')
pdf_image.setBlackWhite(true) if color_depth == '1'
end

Update Tiff-Metadata using Apache Commons-Imaging/Sanselan

I will modify and add Tiff-Tags to existing tif-files with java. JAI imageio crashed, because it could not deal with certain tags from Tiff 6.0. Apache Commons-Imaging seems to be able to deal with these tags. But I have no idea, how to do that. I found a post here, I used for beginning (How to embed ICC_Profile in TiffOutputSet).
Using the example code creates an image, which I can't open because of an LZW error. If I use the Imaging.writeImage(...) methods, It changes the color model from 8Bit to 24Bit and the Exif metadata hase gone.
What i have done is:
bufferedImage = Imaging.getBufferedImage(srcTiff);
byte[] imageBytes = Imaging.writeImageToBytes(tifFile, imageFormat, optional_params)
exifDirectory = tiffOutputSet.getOrCreateRootDirectory();
...
TiffImageWriterLossLess lossLessWriter = new TiffImageWriterLossless(imageBytes);
os = new FileOutputStream(tmpFile);
os = new BufferedOutputStream(os);
lossLessWriter.writeImage(bufferedImage, os, image_params);
Playing around with image_params, like compression or defining the outputset as params, results in different issues. But one is constant, the destImage is bigger then the src image, even when the source image is 24 bit like the dest image.
How could I get Commons-Imaging work for me?
I can respond to the destImage bigger than the src, it is because TIFF images have a compression that is not carried over when the image is read into memory. On writing the image back to storage, you must apply the compression explicitly.

Keeping Encoding when Reading Image File

I'm am currently reading through a file which contains meta-data and a tiff image like so:
private String readFile( String file ) throws IOException {
File file = new File(filename);
int size = (int) file.length();
byte[] bytes = new byte[size];
BufferedInputStream buf = new BufferedInputStream(new FileInputStream(file));
buf.read(bytes, 0, bytes.length);
buf.close();
...
}
I parse the meta-data + image content, then I try to output the tiff like this, where img is a String:
writer = new BufferedWriter( new FileWriter( "img.tiff"));
writer.write(img);
writer.close();
Why is the encoding being lost of the tiff image file?
Why are you trying to rewrite the file?
If the answer is "I'm trying to alter some metadata within the file." I strongly suggest that you use a set of tools that are specifically geared towards working with TIFF metadata, especially if you intend to manipulate/alter than metadata as there are several special case data elements in TIFF files that really don't like being moved around blithely.
My day-to-day job involves understanding the TIFF spec, so I always get a little antsy when I see people mucking around with the internals of TIFFs without first consulting the spec or being concerned with some of the bizarre special cases that exist in the wild that now need to be handled because of someone else who didn't fully grok the spec and created a commercial product that generated thousands of these beasts (I'm looking at you Microsoft for making "old style JPEG compression" TIFFs, but I've also seen a Java product that defined a type of image that used floating point numbers for the component values without bothering to (1) normalize them as the spec would have you do or (2) have a standard for defining what the expected min and max of the component values would be).
In my code base (and this is a commercial product), you can do your work like this:
TiffFile myTiff = new TiffFile();
myTiff.read(someImageInputStream);
for (TiffDirectory dir : myTiff.getImages())
{
// a TiffDirectory contains a collection of TiffTag objects, from which the
// metadata for each image in the document can be read/edited
// TiffTag definitions can be found [here][2].
}
myTiff.save(someImageOutputStream); // writes the whole TIFF back
and in general, we've found that it's really advanced customers who want to do this. For the most part, we find that customers are more concerned with higher-level operations like combining TIFF files into a single document or extracting out pages, for which we have a different API which is much lighter weight and doesn't require you to know the TIFF specification (as it should be).
Try specifying the encoding in your writer.
http://docs.oracle.com/javase/7/docs/api/java/io/OutputStreamWriter.html#OutputStreamWriter%28java.io.OutputStream,%20java.nio.charset.CharsetEncoder%29
Wrap your stream:
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
For images you should look into the ImageIO package.
http://docs.oracle.com/javase/7/docs/api/javax/imageio/ImageIO.html#getImageWriter%28javax.imageio.ImageReader%29

Extracting metadata from PNG image

I'm trying to extract metadata from a PNG image format. I'm using this library? http://code.google.com/p/metadata-extractor/
Even though it claims that PNG format is supported I get an error File format is not supported when I try it with a PNG image. From the source (in method readMetadata also it looks like that it doesn't support PNG format: http://code.google.com/p/metadata-extractor/source/browse/Source/com/drew/imaging/ImageMetadataReader.java?r=1aae00f3fe64388cd14401b2593b580677980884
I've also given this piece of code a try as well but it also doesn't extract the metadata on the PNG.
BTW, I'm adding metadata on PNG with imagemagick like this:
mogrify -comment "Test" Demo/myimage.png
Has anyone used this library for PNG format or are there other ways to extract metadata from PNG image?
You can try PNGJ (I'm the developer)
See eg here an example to dump all chunks.
If you want to read a particular text chunk (recall that in PNG each textual metadata has a key and a value), you could write
pngr.getMetadata().getTxtForKey("mykey")
A useful little Windows program to peek inside PNG chunk structure is TweakPNG
Update: If you want to check all textual chunks (bear in mind that there are three types with some differences, but...)
PngReader pngr = FileHelper.createPngReader(new File(file));
pngr.readSkippingAllRows();
for (PngChunk c : pngr.getChunksList().getChunks()) {
if (!ChunkHelper.isText(c)) continue;
PngChunkTextVar ct = (PngChunkTextVar) c;
String key = ct.getKey();
String val = ct.getVal();
// ...
}
Bear also in mind that textual chunks with repeated keys are allowed.

Library for writing XMP to a multipage TIFF

Can you recommend a library that lets me add XMP data to a TIFF file? Preferably a library that can be used with Java.
There is JempBox which is open source and allows the manipulation of XMP streams, but it doesn't look like it will read/write the XMP data in a TIFF file.
There is also Chilkat which is not open source, but does appear to do what you want.
It's been a while, but it may still be useful to someone: Apache Commons has a library called Sanselan suitable for this task. It's a bit dated and the documentation is sparse, but it does the job well nevertheless:
File file = new File("path/to/your/file");
// Get XMP xml data from a file
String xml = Sanselan.getXmpXml(file);
// Process the XML data
xml = processXml(xml);
// Write XMP xml data from a file
Map params = new HashMap();
params.put(Sanselan.PARAM_KEY_XMP_XML, xml);
BufferedImage image = Sanselan.getBufferedImage(file);
Sanselan.writeImage(image, file, Sanselan.guessFormat(file), params);
You may have to be careful with multipage TIFFs though, because Sanselan.getBufferedImage will probably only get the first (so only the first gets written back).

Categories

Resources