In Part 1 of this tutorial, we went over some background related to the Java ImageIO
subsystem and how one could retrieve information about the supported ImageReader
s and ImageWriter
s. In Part 2, we looked at how one goes about retrieving metadata from an image as it is read. In this third part, we will do the converse – look at how one goes about setting and writing metadata for a file.
All the code associated with this tutorial is available at GitHub: https://github.com/SilverBayTech/imageIoMetadata.
Frequently, one wants to include some metadata when using ImageIO
to write a file. The most common metadata required (in my experience) is resolution, but there are a wide variety of other information that may need to be included.
The basic process for setting any metadata on an image is as follows:
- Create an appropriate XML tree containing the values you wish to set.
- Locate an
ImageWriter
that supports writing the metadata format you need. - Retrieve the default metadata that
ImageIO
will use when writing your particular image. - Merge your XML tree into the default metadata.
- Pass the metadata into the
ImageWriter
as part of writing the image.
Here is some code that does this:
public class ChangeImageResolution { private static String getFileExtension(File file) { String fileName = file.getName(); int lastDot = fileName.lastIndexOf('.'); return fileName.substring(lastDot + 1); } private static BufferedImage readImage(File file) throws IOException { ImageInputStream stream = null; BufferedImage image = null; try { stream = ImageIO.createImageInputStream(file); Iterator<ImageReader> readers = ImageIO.getImageReaders(stream); if (readers.hasNext()) { ImageReader reader = readers.next(); reader.setInput(stream); image = reader.read(0); } } finally { if (stream != null) { stream.close(); } } return image; } private static IIOMetadataNode createResolutionMetadata(double resolutionDPI) { String pixelSize = Double.toString(25.4 / resolutionDPI); IIOMetadataNode horizontal = new IIOMetadataNode("HorizontalPixelSize"); horizontal.setAttribute("value", pixelSize); IIOMetadataNode vertical = new IIOMetadataNode("VerticalPixelSize"); vertical.setAttribute("value", pixelSize); IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); dimension.appendChild(horizontal); dimension.appendChild(vertical); IIOMetadataNode root = new IIOMetadataNode(IIOMetadataFormatImpl.standardMetadataFormatName); root.appendChild(dimension); return root; } private static void writeImage(File outputFile, BufferedImage image, IIOMetadataNode newMetadata) throws IOException { String extension = getFileExtension(outputFile); ImageTypeSpecifier imageType = ImageTypeSpecifier.createFromBufferedImageType(image.getType()); ImageOutputStream stream = null; try { Iterator<ImageWriter> writers = ImageIO.getImageWritersBySuffix(extension); while(writers.hasNext()) { ImageWriter writer = writers.next(); ImageWriteParam writeParam = writer.getDefaultWriteParam(); IIOMetadata imageMetadata = writer.getDefaultImageMetadata(imageType, writeParam); if (!imageMetadata.isStandardMetadataFormatSupported()) { continue; } if (imageMetadata.isReadOnly()) { continue; } imageMetadata.mergeTree(IIOMetadataFormatImpl.standardMetadataFormatName, newMetadata); IIOImage imageWithMetadata = new IIOImage(image, null, imageMetadata); stream = ImageIO.createImageOutputStream(outputFile); writer.setOutput(stream); writer.write(null, imageWithMetadata, writeParam); } } finally { if (stream != null) { stream.close(); } } } public static void main(String[] args) { if (args.length != 3) { System.out .println("Usage: ChangeImageResolution inputFile newResolutionDPI outputFile"); return; } try { File inputFile = new File(args[0]); double resolutionDPI = Double.parseDouble(args[1]); File outputFile = new File(args[2]); BufferedImage image = readImage(inputFile); IIOMetadataNode newMetadata = createResolutionMetadata(resolutionDPI); writeImage(outputFile, image, newMetadata); } catch (Exception e) { e.printStackTrace(); } } }
Reviewing the steps:
- Create an appropriate XML tree containing the values you wish to set.
This is done bycreateResolutionMetadata
. Note that although an XML tree is desired, you need to useIIOMetadataNode
s instead of rawElement
s when building your tree. - Locate an
ImageWriter
that supports writing the metadata format you need.
This is handled in lines 67-77. This program wants anImageWriter
that supports the standard image metadata format. In addition, it is theoretically possible to find anImageWriter
that doesn’t support setting metadata – in this caseisReadOnly
will returntrue
. - Retrieve the default metadata that
ImageIO
will use when writing your particular image.
Line 69 handles this. Note that the default metadata is dependent on the particular type ofBufferedImage
that we’re using – we saw in Part 2 how images with palettes had different metadata from those without, for example. - Merge your XML tree into the default metadata.
Line 79. - Pass the metadata into the
ImageWriter
as part of writing the image.
To do this, we create anIIOImage
that will hold both theBufferedImage
and also our metadata. We then pass this, rather than just theBufferedImage
, to theImageWriter
.
Unfortunately, if you experiment with this, it doesn’t work with all the file formats. It works fine with TIFF, but not with PNG. The PNG plugin appears to have a bug in it – although the HorizontalPixelSize
and VerticalPixelSize
are properly returned (in dimensions of millimeters) when a PNG is read, when you try to set the resolution via the standard image metadata format, the PNG plugin wants the value in pixels per millimeter – the inverse of the “correct” value. If you try this with a BMP file, an exception is thrown inside the BMP plugin.
Chalk up a failure for “common approaches to doing format-specific things.” That being said, this still does show the basic approach. It’s perfectly possible to attack this in a format-specific manner using the format-specific “native” image format, however – you simply build the native image format’s XML tree and merge that tree into that format instead of the “standard” one.
This completes the bulk of this tutorial. In Part 4, we’ll examine a little-used (at least in my experience) feature of IIOMetadata – the ability to programmatically examine the XML structure of the metadata.
IIOMetadata Tutorial – Part 3 – Writing Metadata originally appeared on www.silverbaytech.com/blog/.