Extracting metadata from PNG image - java

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.

Related

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

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

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.

What is the difference between using IOUtils and ImageIO for writing an image file

I have a tiff image stored as Base64 encoded String in a file. My aim is to create a tiff file out of it. This is what I am doing:
String base64encodedTiff = IOUtils.toString(new FileInputStream("C:/tiff-attachment.txt"));
byte[] imgBytes = DatatypeConverter.parseBase64Binary(base64encodedTiff);
BufferedImage bufImg = ImageIO.read(new ByteArrayInputStream(imgBytes));
ImageIO.write(bufImg, "tiff", new File("c:/new-darksouls-imageIO-tiff.tiff"));
ImageIO.write() is throwing IllegalArgumentException because bufImg is null. I don't understand what am I doing wrong here.
On the contrary if I use IOUtils to write, it works fine:
IOUtils.write(imgBytes, new FileOutputStream("c:/new-darksouls-io-tiff.tiff"));
Please help me understand
Why ImageIO is throwing exception
What is the right API and way for what I am trying to achieve.
ImageIO would be useful if, for example, you wanted to convert a PNG to a JPEG. Since you don't need to manipulate the image or convert to another format, don't bother with ImageIO. Just use IOUtils.write() to save the TIFF data verbatim.
ImageIO.read() is returning a null image because it can't read the TIFF file, probably because TIFF isn't one of the standard ImageIO plugin formats. The standard supported image formats are listed here:
http://docs.oracle.com/javase/6/docs/api/javax/imageio/package-summary.html
An additional note -- the code you posted buffers the entire image in memory. If you're concerned about using memory efficiently, consider using some kind of Base64 decoding input stream to perform the decoding on the fly. That might look like this:
try (FileOutputStream out = new FileOutputStream("c:/new-darksouls-io-tiff.tiff");
FileInputStream in = new FileInputStream("C:/tiff-attachment.txt");
Base64InputStream decodedIn = new Base64InputStream(in)) {
IOUtils.copy(decodedIn, out);
}

PIL saved jpeg string cannot be read in java

I create a PIL image string on a python server:
frame = cv.CaptureFromCAM(0)
image = Image.fromstring('RGB', cv.GetSize(frame), frame.tostring(), 'raw', 'BGR')
buffer = cStringIO.stringIO()
image.save(buffer,'JPEG')
udptransmit(buffer.getvalue())
I have a java client trying to read the transmitted image string and reform the jpeg. This however doesn't seem to be working. I created a python client just to check, and I can reform the jpeg correctly using a call to pygame's load method.
The string being sent from python, contains characters 6:10 = JFIF, which is the correct format (also recognised by python's imghdr module.
In java, I ahve tried
simply writing the byte contents of the string received into a file and naming it with a .jpeg extension. The file isn't a valid jpeg.
Using ImageIO to read the bytes from the string. This produces a null image.
Tried to fetch ImageReaderByFormat('JPEG') and parse the bytes with this. This gives me an error stating 'Image is not a JPEG, starts with 0x...'
I really can't see why python recognises the string as a valid jpeg and java doesn't. Do the two use different jpeg decoders? Even if they do, shouldn't both either validate or reject the string?
Just found a solution to the problem
The problem was with the charset used in java while converting the string sent from my python server into bytes in java.
Here's the simple modification that was required in my java client code:
Charset charset = Charset.forName("ISO-8859-1");
Byte[] bytes: Array[Byte] = cam_data.getBytes(charset)
File f = new File("image.jpeg")
FileImageOutputStream fios = new FileImageOutputStream(f)
BufferedImage bim = ImageIO.read(new ByteArrayInputStream(bytes))
ImageIO.write(bim,"jpeg",fios)
The helpful link that lead me to the answer was http://www.java-forums.org/advanced-java/50516-reading-image-files-into-strings.html

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