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 ImageReaders and ImageWriters. In Part 2, we looked at how one goes about retrieving metadata from an image as it is read. In Part 3, we looked at writing metadata into a file. In this final part, we will look at how one can (potentially) automatically retrieve information about the format of the metadata.

All the code associated with this tutorial is available at GitHub: https://github.com/SilverBayTech/imageIoMetadata.

Handling the standard image metadata format is pretty straightforward – the DTD for the XML has been published, and it’s the same for any image format that supports it. Suppose, however, that you were trying to write a generic image handling program with a GUI, and wanted to offer the user the ability to fiddle with plugins’ native image metadata. Further, suppose that you wanted this to work regardless of the plugin involved. In this case, you’d need a way of asking the plugin for the data that it supports, potential values for the various settings, etc.

In theory, ImageIO provides you a way to do this via the IIOMetadataFormat class. The steps are as follows:

  1. Locate the ImageReaderSpi or ImageWriterSpi for the plugin in question.
  2. Retrieve the IIOMetadataFormat object for the native image format or native stream format.
  3. Use the methods supported by IIOMetadataFormat to interrogate the various XML elements, their children and their attributes.
  4. Dynamically build a GUI that reflects these values.

I say “in theory,” because this depends on the accuracy and completeness of the information that is returned by this interface.

Although we won’t be building a GUI, the GitHub project includes a program named DumpMetadataFormat that exercises this interface and allows you to see the information provided for any supported graphics format.

public class DumpMetadataFormat
{
	private static Set<String> ALREADY_DUMPED;
	
	private static void indent(int level)
	{
		for (int i = 0; i < level; i++)
		{
			System.out.print("    ");
		}
	}
	
	private static void dumpEnumeration(IIOMetadataFormat format, String elementName, String attributeName)
	{
		String[] values = format.getAttributeEnumerations(elementName, attributeName);
		System.out.print(":");
		System.out.print(values[0]);
		for (int i = 1; i < values.length; i++)
		{
			System.out.print("|");
			System.out.print(values[i]);
		}
	}
	
	private static void dumpRange(IIOMetadataFormat format, String elementName, String attributeName, int valueType)
	{
		String minValue = format.getAttributeMinValue(elementName, attributeName);
		String maxValue = format.getAttributeMaxValue(elementName, attributeName);
		
		System.out.print(":");
		System.out.print(minValue);
		
		if ((valueType & IIOMetadataFormat.VALUE_RANGE_MIN_INCLUSIVE_MASK) != 0)
		{
			System.out.print("<=");
		}
		else
		{
			System.out.print("<");
		}
		
		System.out.print("x");
		
		if ((valueType & IIOMetadataFormat.VALUE_RANGE_MAX_INCLUSIVE_MASK) != 0)
		{
			System.out.print("<=");
		}
		else
		{
			System.out.print("<");
		}
		System.out.print(maxValue);
	}
	
	private static void dumpList(IIOMetadataFormat format, String elementName, String attributeName)
	{
		int minLength = format.getAttributeListMinLength(elementName, attributeName);
		int maxLength = format.getAttributeListMaxLength(elementName, attributeName);
		System.out.print("[");
		System.out.print(minLength);
		System.out.print(",");
		System.out.print(maxLength);
		System.out.print("]");
	}
	
	private static void dumpAttributes(IIOMetadataFormat format, String elementName)
	{
		String[] attributeNames = format.getAttributeNames(elementName);
		if (attributeNames != null && attributeNames.length > 0)
		{
			for (String attributeName : attributeNames)
			{
				System.out.print(" ");
				System.out.print(attributeName);
				System.out.print("='");
				
				int dataType = format.getAttributeDataType(elementName, attributeName);
				switch(dataType)
				{
				case IIOMetadataFormat.DATATYPE_BOOLEAN:
					System.out.print("(BOOLEAN)");
					break;
				case IIOMetadataFormat.DATATYPE_DOUBLE:
					System.out.print("(DOUBLE)");
					break;
				case IIOMetadataFormat.DATATYPE_FLOAT:
					System.out.print("(FLOAT)");
					break;
				case IIOMetadataFormat.DATATYPE_INTEGER:
					System.out.print("(INTEGER)");
					break;
				case IIOMetadataFormat.DATATYPE_STRING:
					System.out.print("(STRING)");
					break;
				}
				
				int valueType = format.getAttributeValueType(elementName, attributeName);
				switch(valueType)
				{
				case IIOMetadataFormat.VALUE_ARBITRARY:
				case IIOMetadataFormat.VALUE_NONE:
					break;
				case IIOMetadataFormat.VALUE_ENUMERATION:
					dumpEnumeration(format, elementName, attributeName);
					break;
				case IIOMetadataFormat.VALUE_LIST:
					dumpList(format, elementName, attributeName);
					break;
				case IIOMetadataFormat.VALUE_RANGE:
				case IIOMetadataFormat.VALUE_RANGE_MAX_INCLUSIVE:
				case IIOMetadataFormat.VALUE_RANGE_MIN_INCLUSIVE:
				case IIOMetadataFormat.VALUE_RANGE_MIN_MAX_INCLUSIVE:
					dumpRange(format, elementName, attributeName, valueType);
					break;
				}
				
				String defaultValue = format.getAttributeDefaultValue(elementName, attributeName);
				if (defaultValue != null)
				{
					System.out.print("=");
					System.out.print(defaultValue);
				}
				
				System.out.print("'");
				if (format.isAttributeRequired(elementName, attributeName))
				{
					System.out.print("*");
				}
			}
		}
	}
	
	private static void dumpElement(IIOMetadataFormat format, String elementName, int indent)
	{
		indent(indent);
		System.out.print("<");
		System.out.print(elementName);
		
		/*
		 * Handle possible recursion in element children.  (TIFF does this)
		 */
		if (ALREADY_DUMPED.contains(elementName))
		{
			System.out.println("> (see above)");
			return;
		}
		
		ALREADY_DUMPED.add(elementName);
		
		dumpAttributes(format, elementName);
		
		String[] children = format.getChildNames(elementName);
		if (children == null || children.length == 0)
		{
			System.out.println("/>");
			return;
		}
		
		System.out.print("> ");
		
		int childPolicy = format.getChildPolicy(elementName);
		switch(childPolicy)
		{
		case IIOMetadataFormat.CHILD_POLICY_ALL:
			System.out.println("(single instance of all children required)");
			break;
		case IIOMetadataFormat.CHILD_POLICY_CHOICE:
			System.out.println("(0 or 1 instance of legal child elements)");
			break;
		case IIOMetadataFormat.CHILD_POLICY_EMPTY:
			System.out.println("");
			break;
		case IIOMetadataFormat.CHILD_POLICY_REPEAT:
			System.out.println("(zero or more instances of child element)");
			break;
		case IIOMetadataFormat.CHILD_POLICY_SEQUENCE:
			System.out.println("(sequence of instances of any of its legal child elements)");
			break;
		case IIOMetadataFormat.CHILD_POLICY_SOME:
			System.out.println("(zero or one instance of each of its legal child elements, in order)");
			break;
		}
		
		for (String child : children)
		{
			dumpElement(format, child, indent + 1);
		}
		
		indent(indent);
		System.out.print("</");
		System.out.print(elementName);
		System.out.println(">");
		
		ALREADY_DUMPED.remove(elementName);
	}
	
	private static void dumpFormat(IIOMetadataFormat format)
	{
		ALREADY_DUMPED = new HashSet<String>();
		String rootNodeName = format.getRootName();
		dumpElement(format, rootNodeName, 3);
	}
	
	private static void dumpProvider(ImageReaderSpi provider)
	{
		indent(1);
		System.out.println(provider.getPluginClassName());
		
		if (provider.isStandardImageMetadataFormatSupported())
		{
			indent(2);
			System.out.print("Image format: ");
			System.out.println(IIOMetadataFormatImpl.standardMetadataFormatName);
			IIOMetadataFormat format = provider.getImageMetadataFormat(IIOMetadataFormatImpl.standardMetadataFormatName);
			dumpFormat(format);
		}
		
		String formatName = provider.getNativeImageMetadataFormatName();
		if (formatName != null)
		{
			indent(2);
			System.out.print("Image format: ");
			System.out.println(formatName);
			IIOMetadataFormat format = provider.getImageMetadataFormat(formatName);
			dumpFormat(format);
		}
		
		if (provider.isStandardStreamMetadataFormatSupported())
		{
			indent(2);
			System.out.print("Stream format: ");
			System.out.println(IIOMetadataFormatImpl.standardMetadataFormatName);
			IIOMetadataFormat format = provider.getStreamMetadataFormat(IIOMetadataFormatImpl.standardMetadataFormatName);
			dumpFormat(format);
		}
		
		formatName = provider.getNativeStreamMetadataFormatName();
		if (formatName != null)
		{
			indent(2);
			System.out.print("Stream format: ");
			System.out.println(formatName);
			IIOMetadataFormat format = provider.getStreamMetadataFormat(formatName);
			dumpFormat(format);
		}
	}
	
	private static boolean supportsFormat(ImageReaderSpi provider, String format)
	{
		String[] formats = provider.getFormatNames();
		for (String supportedFormat : formats)
		{
			if (supportedFormat.equalsIgnoreCase(format))
			{
				return true;
			}
		}
		
		return false;
	}
	
	private static void dumpFormat(String format)
	{
		System.out.print("Format: ");
		System.out.println(format);
		
		IIORegistry registry = IIORegistry.getDefaultInstance();
		Iterator<ImageReaderSpi> providers = registry.getServiceProviders(ImageReaderSpi.class, true);
		while(providers.hasNext())
		{
			ImageReaderSpi provider = providers.next();
			if (supportsFormat(provider, format))
			{
				dumpProvider(provider);
			}
		}
	}
	
	public static void main(String[] args)
	{
		if (args.length == 0)
		{
			System.out.println("Usage: DumpMetadataFormat graphicsFormat [...graphicsFormat]");
			return;
		}
		
		for (int i = 0; i < args.length; i++)
		{
			dumpFormat(args[i]);
		}
		System.out.println("Done");
	}
}

dumpProvider, starting at line 204, extracts the IIOMetadataFormat objects (if they exist) for the standard and native image and stream formats. dumpFormat, on line 197, extracts the root XML element name, and then starts a recursive element-by-element dump procedure by calling dumpElement.

Each element in the XML has the following:

  • A possibly-empty list of child element names, which may be retrieved via IIOMetadataFormat.getChildNames
  • A “child policy,” which may be retrieved via IIOMetadataFormat.getChildPolicy There are a set of CHILD_POLICY_* constants that describe whether:
    • All of the children of this element must be present
    • Child elements are optional
    • Exactly one of the listed child elements must be present
    • etc.

    This is similar to the information that a DTD might provide, however the choices here are less flexible than the full suite that could be represented in a DTD.

  • A possibly-empty list of attribute names, which may be retrieved via IIOMetadataFormat.getAttributeNames

Each attribute that is defined has the following information:

  • An indication as to whether or not this attribute is required
  • A data type – boolean, integer, string, etc.
  • A value type – this provides information on what the legal values are for this attribute.
  • For attributes that are not required, a possibly-null default value, indicating the value if this attribute is omitted.

Value types are one of the following:

VALUE_* Meaning
VALUE_ARBITRARY There is essentially no restriction on the nature of the value.
VALUE_ENUM There is a specific set of legal values. This set may be retrieved using IIOMetadataFormat.getAttributeEnumerations
VALUE_RANGE There are a range of legal values. This typically applies to numeric values – integers, floats and doubles. The minimum and maximum values may be obtained using IIOMetadataFormat.getAttributeMinValue and IIOMetadataFormat.getAttributeMaxValue. Ranges may either include or exclude their upper and lower bounds – whether or not the bounds are “inclusive” or “exclusive” are represented by a set of VALUE_RANGE_* values.
VALUE_LIST This indicates that there may be more than one value in the attribute, typically separated by spaces.IIOMetadataFormat.getAttributeListMinLength and IIOMetadataFormat.getAttributeListMaxLength allow you to determine the minimum and maximum number of values in the list.

As an example, here is the result if the program is run for the “PNG” format:

Format: png
    com.sun.imageio.plugins.png.PNGImageReader
        Image format: javax_imageio_1.0
            <javax_imageio_1.0> (zero or one instance of each of its legal child elements, in order)
                <Chroma> (zero or one instance of each of its legal child elements, in order)
                    <ColorSpaceType name='(STRING):XYZ|Lab|Luv|YCbCr|Yxy|YCCK|PhotoYCC|RGB|GRAY|HSV|HLS|CMYK|CMY|2CLR|3CLR|4CLR|5CLR|6CLR|7CLR|8CLR|9CLR|ACLR|BCLR|CCLR|DCLR|ECLR|FCLR'*/>
                    <NumChannels value='(INTEGER)[0,2147483647]'*/>
                    <Gamma value='(FLOAT)'*/>
                    <BlackIsZero value='(BOOLEAN):TRUE|FALSE=TRUE'*/>
                    <Palette> (zero or more instances of child element)
                        <PaletteEntry index='(INTEGER)'* red='(INTEGER)'* green='(INTEGER)'* blue='(INTEGER)'* alpha='(INTEGER)=255'/>
                    </Palette>
                    <BackgroundIndex value='(INTEGER)'*/>
                    <BackgroundColor red='(INTEGER)'* green='(INTEGER)'* blue='(INTEGER)'*/>
                </Chroma>
                <Compression> (zero or one instance of each of its legal child elements, in order)
                    <CompressionTypeName value='(STRING)'*/>
                    <Lossless value='(BOOLEAN):TRUE|FALSE=TRUE'*/>
                    <NumProgressiveScans value='(INTEGER)'*/>
                    <BitRate value='(FLOAT)'*/>
                </Compression>
                <Data> (zero or one instance of each of its legal child elements, in order)
                    <PlanarConfiguration value='(STRING):PixelInterleaved|PlaneInterleaved|LineInterleaved|TileInterleaved'*/>
                    <SampleFormat value='(STRING):SignedIntegral|UnsignedIntegral|Real|Index'*/>
                    <BitsPerSample value='(INTEGER)[1,2147483647]'*/>
                    <SignificantBitsPerSample value='(INTEGER)[1,2147483647]'*/>
                    <SampleMSB value='(INTEGER)[1,2147483647]'*/>
                </Data>
                <Dimension> (zero or one instance of each of its legal child elements, in order)
                    <PixelAspectRatio value='(FLOAT)'*/>
                    <ImageOrientation value='(STRING):Normal|Rotate90|Rotate180|Rotate270|FlipH|FlipV|FlipHRotate90|FlipVRotate90'*/>
                    <HorizontalPixelSize value='(FLOAT)'*/>
                    <VerticalPixelSize value='(FLOAT)'*/>
                    <HorizontalPhysicalPixelSpacing value='(FLOAT)'*/>
                    <VerticalPhysicalPixelSpacing value='(FLOAT)'*/>
                    <HorizontalPosition value='(FLOAT)'*/>
                    <VerticalPosition value='(FLOAT)'*/>
                    <HorizontalPixelOffset value='(INTEGER)'*/>
                    <VerticalPixelOffset value='(INTEGER)'*/>
                    <HorizontalScreenSize value='(INTEGER)'*/>
                    <VerticalScreenSize value='(INTEGER)'*/>
                </Dimension>
                <Document> (zero or one instance of each of its legal child elements, in order)
                    <FormatVersion value='(STRING)'*/>
                    <SubimageInterpretation value='(STRING):Standalone|SinglePage|FullResolution|ReducedResolution|PyramidLayer|Preview|VolumeSlice|ObjectView|Panorama|AnimationFrame|TransparencyMask|CompositingLayer|SpectralSlice|Unknown'*/>
                    <ImageCreationTime year='(INTEGER)'* month='(INTEGER):1<=x<=12'* day='(INTEGER):1<=x<=31'* hour='(INTEGER):0<=x<=23=0' minute='(INTEGER):0<=x<=59=0' second='(INTEGER):0<=x<=60=0'/>
                    <ImageModificationTime year='(INTEGER)'* month='(INTEGER):1<=x<=12'* day='(INTEGER):1<=x<=31'* hour='(INTEGER):0<=x<=23=0' minute='(INTEGER):0<=x<=59=0' second='(INTEGER):0<=x<=60=0'/>
                </Document>
                <Text> (zero or more instances of child element)
                    <TextEntry keyword='(STRING)' value='(STRING)'* language='(STRING)' encoding='(STRING)' compression='(STRING):none|lzw|zip|bzip|other=none'/>
                </Text>
                <Transparency> (zero or one instance of each of its legal child elements, in order)
                    <Alpha value='(STRING):none|premultiplied|nonpremultiplied=none'/>
                    <TransparentIndex value='(INTEGER)'*/>
                    <TransparentColor value='(INTEGER)[0,2147483647]'*/>
                    <TileTransparencies> (zero or more instances of child element)
                        <TransparentTile x='(INTEGER)'* y='(INTEGER)'*/>
                    </TileTransparencies>
                    <TileOpacities> (zero or more instances of child element)
                        <OpaqueTile x='(INTEGER)'* y='(INTEGER)'*/>
                    </TileOpacities>
                </Transparency>
            </javax_imageio_1.0>
        Image format: javax_imageio_png_1.0
            <javax_imageio_png_1.0> (zero or one instance of each of its legal child elements, in order)
                <IHDR width='(INTEGER):1<=x<=2147483647'* height='(INTEGER):1<=x<=2147483647'* bitDepth='(INTEGER):1|2|4|8|16'* colorType='(STRING):Grayscale|RGB|Palette|GrayAlpha|RGBAlpha'* compressionMethod='(STRING):deflate'* filterMethod='(STRING):adaptive'* interlaceMethod='(STRING):none|adam7'*/>
                <PLTE> (zero or more instances of child element)
                    <PLTEEntry index='(INTEGER):0<=x<=255'* red='(INTEGER):0<=x<=255'* green='(INTEGER):0<=x<=255'* blue='(INTEGER):0<=x<=255'*/>
                </PLTE>
                <bKGD> (0 or 1 instance of legal child elements)
                    <bKGD_Grayscale gray='(INTEGER):0<=x<=65535'*/>
                    <bKGD_RGB red='(INTEGER):0<=x<=65535'* green='(INTEGER):0<=x<=65535'* blue='(INTEGER):0<=x<=65535'*/>
                    <bKGD_Palette index='(INTEGER):0<=x<=255'*/>
                </bKGD>
                <cHRM whitePointX='(INTEGER):0<=x<=65535'* whitePointY='(INTEGER):0<=x<=65535'* redX='(INTEGER):0<=x<=65535'* redY='(INTEGER):0<=x<=65535'* greenX='(INTEGER):0<=x<=65535'* greenY='(INTEGER):0<=x<=65535'* blueX='(INTEGER):0<=x<=65535'* blueY='(INTEGER):0<=x<=65535'*/>
                <gAMA value='(INTEGER):0<=x<=2147483647'*/>
                <hIST> (zero or more instances of child element)
                    <hISTEntry index='(INTEGER):0<=x<=255'* value='(INTEGER):0<=x<=65535'*/>
                </hIST>
                <iCCP profileName='(STRING)'* compressionMethod='(STRING):deflate'*/>
                <iTXt> (zero or more instances of child element)
                    <iTXtEntry keyword='(STRING)'* compressionFlag='(BOOLEAN):TRUE|FALSE'* compressionMethod='(STRING)'* languageTag='(STRING)'* translatedKeyword='(STRING)'* text='(STRING)'*/>
                </iTXt>
                <pHYS pixelsPerUnitXAxis='(INTEGER):0<=x<=2147483647'* pixelsPerUnitYAxis='(INTEGER):0<=x<=2147483647'* unitSpecifier='(STRING):unknown|meter'*/>
                <sBIT> (0 or 1 instance of legal child elements)
                    <sBIT_Grayscale gray='(INTEGER):0<=x<=255'*/>
                    <sBIT_GrayAlpha gray='(INTEGER):0<=x<=255'* alpha='(INTEGER):0<=x<=255'*/>
                    <sBIT_RGB red='(INTEGER):0<=x<=255'* green='(INTEGER):0<=x<=255'* blue='(INTEGER):0<=x<=255'*/>
                    <sBIT_RGBAlpha red='(INTEGER):0<=x<=255'* green='(INTEGER):0<=x<=255'* blue='(INTEGER):0<=x<=255'* alpha='(INTEGER):0<=x<=255'*/>
                    <sBIT_Palette red='(INTEGER):0<=x<=255'* green='(INTEGER):0<=x<=255'* blue='(INTEGER):0<=x<=255'*/>
                </sBIT>
                <sPLT> (zero or more instances of child element)
                    <sPLTEntry index='(INTEGER):0<=x<=255'* red='(INTEGER):0<=x<=255'* green='(INTEGER):0<=x<=255'* blue='(INTEGER):0<=x<=255'* alpha='(INTEGER):0<=x<=255'*/>
                </sPLT>
                <sRGB renderingIntent='(STRING):Perceptual|Relative colorimetric|Saturation|Absolute colorimetric'*/>
                <tEXt> (zero or more instances of child element)
                    <tEXtEntry keyword='(STRING)'* value='(STRING)'*/>
                </tEXt>
                <tIME year='(INTEGER):0<=x<=65535'* month='(INTEGER):1<=x<=12'* day='(INTEGER):1<=x<=31'* hour='(INTEGER):0<=x<=23'* minute='(INTEGER):0<=x<=59'* second='(INTEGER):0<=x<=60'*/>
                <tRNS> (0 or 1 instance of legal child elements)
                    <tRNS_Grayscale gray='(INTEGER):0<=x<=65535'*/>
                    <tRNS_RGB red='(INTEGER):0<=x<=65535'* green='(INTEGER):0<=x<=65535'* blue='(INTEGER):0<=x<=65535'*/>
                    <tRNS_Palette index='(INTEGER):0<=x<=255'* alpha='(INTEGER):0<=x<=255'*/>
                </tRNS>
                <zTXt> (zero or more instances of child element)
                    <zTXtEntry keyword='(STRING)'* compressionMethod='(STRING):deflate'* text='(STRING)'*/>
                </zTXt>
                <UnknownChunks> (zero or more instances of child element)
                    <UnknownChunk type='(STRING)'*/>
                </UnknownChunks>
            </javax_imageio_png_1.0>
Done

If you look at this, you will see that:

  • javax_imageio_1.0/Chroma allows either zero or one of each of its child elements. Thus, for example, the ColorSpaceType element is not required, but there cannot be two of them.
  • javax_imageio_1.0/Chroma/ColorSpaceType has an attribute name which is required, and is a enumeration.
  • javax_imageio_1.0/Chroma/Palette allows more than one child PaletteEntry instance, as one would expect. Palette is not required, however – it would not be present on a non-indexed image.
  • PaletteEntry requires index, red, green and blue attributes, however the alpha attribute is optional, defaulting to 255 if not present.
  • The attributes for javax_imageio_1.0/Document/ImageCreationTime and javax_imageio_1.0/Document/ImageModificationTime include ranges that restrict the values to legal month, day, year, hour, minute and second values.

Here is the output from DumpMetadataFormat for a PNG image, allowing you to compare actual data with the “schema” described above:

Reader: com.sun.imageio.plugins.png.PNGImageReader
    Image metadata
        Format name: javax_imageio_png_1.0
            <javax_imageio_png_1.0>
                <IHDR width='157' height='56' bitDepth='8' colorType='RGB' compressionMethod='deflate' filterMethod='adaptive' interlaceMethod='none'/>
                <gAMA value='45455'/>
                <pHYs pixelsPerUnitXAxis='11811' pixelsPerUnitYAxis='11811' unitSpecifier='meter'/>
                <tIME year='2014' month='5' day='14' hour='16' minute='31' second='22'/>
            </javax_imageio_png_1.0>
        Format name: javax_imageio_1.0
            <javax_imageio_1.0>
                <Chroma>
                    <ColorSpaceType name='RGB'/>
                    <NumChannels value='3'/>
                    <Gamma value='0.45455'/>
                    <BlackIsZero value='TRUE'/>
                </Chroma>
                <Compression>
                    <CompressionTypeName value='deflate'/>
                    <Lossless value='TRUE'/>
                    <NumProgressiveScans value='1'/>
                </Compression>
                <Data>
                    <PlanarConfiguration value='PixelInterleaved'/>
                    <SampleFormat value='UnsignedIntegral'/>
                    <BitsPerSample value='8 8 8'/>
                </Data>
                <Dimension>
                    <PixelAspectRatio value='1.0'/>
                    <ImageOrientation value='Normal'/>
                    <HorizontalPixelSize value='0.08466683'/>
                    <VerticalPixelSize value='0.08466683'/>
                </Dimension>
                <Document>
                    <ImageModificationTime year='2014' month='5' day='14' hour='16' minute='31' second='22'/>
                </Document>
                <Transparency>
                    <Alpha value='none'/>
                </Transparency>
            </javax_imageio_1.0>

Done

Unfortunately, it has been my experience that the “list” information is largely useless – every instance I have seen specifies “between 1 and 2 billion entries in the list,” despite the fact that PNG files, for example, shouldn’t have more than four components (red, green, blue and alpha). Similarly, when we dump the data for the TIFF file format, the plugin that is provided by the JAI ImageIO jar has a recursion – the TIFFIFD element is defined to potentially contain another TIFFIFD element, which strikes me as odd.

Finally, it is not clear that even if one could build a GUI to support all the settings offered by a file format, it is not clear that one should. Take the PNG file format, for example. The metadata formats provide two different ways of specifying the palette – one via the standard image format, and one via the PNG-specific native format. What would happen if you set them differently, or if they didn’t correspond to the BufferedImage that you were writing? What would happen if you tried to provide palette metadata on a non-indexed image? As soon as you start dealing with exceptions like this, you are into format-specific code, at which point (if you’re going to be writing images out) it’s probably better to work with the DTD’s rather than with the enumerated format. It’s possible that you could use this information in some way for display-only purposes, but again I wonder…

Thus, although it is technically possible to extract format information, I have to chalk this up to “a nice idea, but not as useful as it initially sounds.” But it’s still interesting to have explored it a bit.

 

IIOMetadata Tutorial – Part 4 – Automatically Determining Metadata Format originally appeared on www.silverbaytech.com/blog/.