The Java ImageIO
subsystem provides convenient methods for Java-based programs to read and write bitmap images. One can load an image in any supported format and then get access to the image data as a BufferedImage
, or one can create a BufferedImage
, draw to it using a Graphics
or Graphics2D
, and then save the image to any of the supported bitmap formats.
In addition to the pixels that make up the image, most image formats contain image “metadata” – data about the image itself. This includes items such as the resolution of the images (pixels per inch or pixels per centimeter), creator information, etc. The ImageIO
system allows this data to be read and written as well, via IIOMetadata
. IIOMetadata
is not as well described as it might be in the Java documents, however. Hence this tutorial.
All the code for this post is located on Github: https://github.com/SilverBayTech/imageIoMetadata.
First, a bit of background.
The ImageIO
system was designed to be extensible. Individual bitmap file formats are supported by “plugins” which implement the ImageReader
and ImageWriter
functionality. The core Java 7 distribution contains plugins for the following image formats:
- JPEG
- Windows BMP
- PNG
- GIF
- WBMP – Wireless Application Protocol Bitmap Format a rarely (now) used format that was originally intended for mobile devices and the WAP protocol.
The Java Advanced Imaging ImageIO package provides additional plugins for the following image formats:
- JPEG2000
- TIFF
- PNM
Past that, it is possible for anyone to write their own plugin, either for an unsupported image type, or to provide additional functionality for one of the types above.
ImageIO
provides a convenience layer that largely insulates the user from details of the plugins. If all you need is to read or write the image pixels, you don’t need to worry about them. But then you probably wouldn’t be reading this tutorial. If you need access to image metadata, you have to dig a little deeper into ImageIO
.
If you are trying to read an image, there are four different ways to get access to the available ImageReader
s:
- If you know the format of the data file, you can call
ImageIO.getImageReadersByFormatName(String formatName)
- If you know the MIME type of the data file, you can call
ImageIO.getImageReadersByMIMEType(String MIMEType)
- If you have the file name, and if it uses one of the standard extensions for the particular image format, you can call
ImageIO.getImageReadersBySuffix(String fileSuffix)
- Finally, you can call
ImageIO.getImageReaders(Object input)
. In this case, theObject
is frequently anImageInputStream
– the subsystem will inspect the image data and try to determine its type and the corresponding plugin(s) that service it.
Each of these methods returns an Iterator<ImageReader>
which will contain zero or more ImageReader
s. Using this Iterator
, you can see if you can find a reader that will provide the features you require.
Similar methods are available to obtain a list of ImageWriter
s. In addition, most ImageReader
s and ImageWriter
s come in pairs, and one can ask for the writer that corresponds to a particular reader.
Another option is to use the IIORegistry
.
The ImageIO
system is implemented using a “Service Provider” concept. Each plugin provides one or more “services” to the system, and is represented by one or more instances of javax.imageio.spi.IIOServiceProvider
in the IIORegistry
. ImageReader
s are services, and each has a corresponding ImageReaderSpi
instance. Similarly, ImageWriter
s have ImageWriterSpi
instances. One can ask the IIORegistry
for a list of all the service providers of a particular type as follows:
IIORegistry registry = IIORegistry.getDefaultInstance(); Iterator<ImageReaderSpi> readers = registry.getServiceProviders(ImageReaderSpi.class, true); Iterator<ImageWriterSpi> writers = registry.getServiceProviders(ImageWriterSpi.class, true);
One can use this information to look through all the available readers or writers to see what their capabilities are. Thus, for example, each service provider can return a list of the formats, MIME types and suffixes that it supports. The service provider classes provide methods to create actual readers or writers, while the readers or writers provide a method to get the corresponding service provider class.
More importantly to this tutorial series, however, is that the service provider classes also provide information on what types of metadata the individual reader or writer supports. Thus, if one needs a particularly metadata capability, one can search through the providers to find one that meets ones needs.
ImageIO
metadata comes in two “flavors” – “image” metadata and “stream” metadata. In most cases, “image” metadata contains the information we would expect about a particular image – resolution being one of them. “Stream” metadata is somewhat less used, but provides information about the data stream itself. One example relates to the TIFF file format. TIFF provides two different byte orderings – “big-endan” (also referred to as “Motorola” format) and “little-endian” (also referred to as “Intel” format). This isn’t really “image data” in the strict sense, since a particular image can be represented in either format. This is a property of the file itself. Thus, this is represented by “stream” metadata.
In addition, ImageIO
plugins can (and frequently do) support metadata in more than one way. ImageIO
provides a “standard” format that covers many of the common metadata items, but each plugin may then provide its own “native” metadata format that represents items particular to that image format, or to capabilities of that specific plugin. Thus, each service provider implements the following methods:
Method | Returns |
---|---|
isStandardImageMetadataFormatSupported() |
true if the plugin supports image metadata in the standard format |
isStandardStreamMetadataFormatSupported() |
true if the plugin supports stream metadata in the standard format |
getNativeImageMetadataFormatName() |
If the plugin supports a native image metadata type, returns a String with the name of that format. Otherwise, returns null . |
getNativeStreamMetadataFormatName() |
If the plugin supports a native stream metadata type, returns a String with the name of that format. Otherwise, returns null . |
getExtraImageMetadataFormatNames() |
If the plugin supports a any additional image metadata formats other than its native one, returns a String[] containing their names. Otherwise, returns null . |
getExtraStreamMetadataFormatNames() |
If the plugin supports a any additional stream metadata formats other than its native one, returns a String[] containing their names. Otherwise, returns null . |
To give you a feel for this, I’ve written a sample program that iterates through the available service providers, dumping out the information they provide. The program is named DumpImageIoPlugins
, and is replicated here:
public class DumpImageIoPlugins { private static void indent(int indent) { for (int i = 0; i < indent; i++) { System.out.print(" "); } } private static void dumpStrings(String title, String[] strings, int indent) { indent(indent); System.out.println(title); if (strings != null) { for (String string : strings) { indent(indent + 1); System.out.println(string); } } else { indent(indent + 1); System.out.println("(none)"); } } private static void dumpBoolean(String title, boolean value, int indent) { indent(indent); System.out.print(title); System.out.println(value ? " YES" : " NO"); } private static void dumpString(String title, String value, int indent) { indent(indent); System.out.print(title); System.out.print(" "); System.out.println(value != null ? value : "(null)"); } private static void dumpReaderWriter(ImageReaderWriterSpi object) { indent(1); System.out.println(object.getPluginClassName()); dumpStrings("File Suffixes:", object.getFileSuffixes(), 2); dumpStrings("Format Names:", object.getFormatNames(), 2); dumpStrings("MIME Types:", object.getMIMETypes(), 2); dumpBoolean("Standard Image Metadata Format Supported:", object.isStandardImageMetadataFormatSupported(), 2); dumpBoolean("Standard Stream Metadata Format Supported:", object.isStandardStreamMetadataFormatSupported(), 2); dumpString( "Native Image Metadata Format Name:", object.getNativeImageMetadataFormatName(), 2); dumpString( "Native Stream Metadata Format Name:", object.getNativeStreamMetadataFormatName(), 2); dumpStrings("Extra Image Metadata Format Names:", object.getExtraImageMetadataFormatNames(), 2); dumpStrings("Extra Stream Metadata Format Names:", object.getExtraStreamMetadataFormatNames(), 2); System.out.println(""); } public static void main(String[] args) { dumpStrings("File Suffixes:", ImageIO.getReaderFileSuffixes(), 0); dumpStrings("\nFormat Names:", ImageIO.getReaderFormatNames(), 0); dumpStrings("\nMIME Types:", ImageIO.getReaderMIMETypes(), 0); IIORegistry registry = IIORegistry.getDefaultInstance(); System.out.println("\nReaders:"); Iterator<ImageReaderSpi> readers = registry.getServiceProviders(ImageReaderSpi.class, true); while (readers.hasNext()) { ImageReaderSpi reader = readers.next(); dumpReaderWriter(reader); } System.out.println("\nWriters:"); Iterator<ImageWriterSpi> writers = registry.getServiceProviders(ImageWriterSpi.class, true); while (writers.hasNext()) { ImageWriterSpi writer = writers.next(); dumpReaderWriter(writer); } } }
As you can see, it iterates through the available ImageReaderSpi
and ImageWriterSpi
classes and simply outputs the information they provide. In addition, it displays the “global” lists of suffixes, formats and MIME types provided via the ImageIO
object, which is simply a collation of the information returned by the providers.
As one example, here is the output associated with the standard PNG ImageReader
that comes with Java 7:
com.sun.imageio.plugins.png.PNGImageReader File Suffixes: png Format Names: png PNG MIME Types: image/png image/x-png Standard Image Metadata Format Supported: YES Standard Stream Metadata Format Supported: NO Native Image Metadata Format Name: javax_imageio_png_1.0 Native Stream Metadata Format Name: (null) Extra Image Metadata Format Names: (none) Extra Stream Metadata Format Names: (none)
As you can see, this ImageReader
supports both the standard image metadata as well as a metadata format called javax_imageio_png_1.0
. It does not support stream metadata.
Here is the output from the TIFF reader available as part of the JAI ImageIO package:
com.sun.media.imageioimpl.plugins.tiff.TIFFImageReader File Suffixes: tif tiff Format Names: tif TIF tiff TIFF MIME Types: image/tiff Standard Image Metadata Format Supported: YES Standard Stream Metadata Format Supported: NO Native Image Metadata Format Name: com_sun_media_imageio_plugins_tiff_image_1.0 Native Stream Metadata Format Name: com_sun_media_imageio_plugins_tiff_stream_1.0 Extra Image Metadata Format Names: (none) Extra Stream Metadata Format Names: (none)
This reader supports the standard image metadata, an image metadata format of its own (com_sun_media_imageio_plugins_tiff_image_1.0
) as well as a stream metadata format of its own (com_sun_media_imageio_plugins_tiff_stream_1.0
).
Feel free to experiment with the code on your own.
In the next part of this tutorial, we will look at how we can use the metadata format names to actually obtain metadata from image files.
IIOMetadata Tutorial – Part 1 – Background originally appeared on the Silver Bay Tech blog.