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:

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 ImageReaders:

  • 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, the Object is frequently an ImageInputStream – 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 ImageReaders. 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 ImageWriters. In addition, most ImageReaders and ImageWriters 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. ImageReaders are services, and each has a corresponding ImageReaderSpi instance. Similarly, ImageWriters 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.