One obvious question resulting from the previous discussion is how do the ImageIO static methods know which ImageReader(s) can decode the image data? Theoretically, one way this can be done is to have each of the ImageReaders contain a set of methods that would return a list of image formats, file suffixes, and MIME types that it can decode. Another method could take an ImageInputStream and return true or false if the ImageReader can decode it. Using these techniques, it would be up to the plug-in developers to write these methods and therefore provide this information. Although this idea has its merits, there is one problem: In order to find out an ImageReader's functionality, it needs to be registered; and to register each of the ImageReaders, an object of each ImageReader class would need to be instantiated. This would be a waste of time and memory because not all of the ImageReaders will be needed. For this reason, service provider interfaces (spi) are used. Spis are small classes (such as ImageReaderSpi and ImageWriterSpi) that are used to describe the functionality of larger classes (such as ImageReader and ImageWriter). Thus, in practice the JVM can instantiate an object of each ImageReaderSpi, and these objects can be used to decide which ImageReader(s) can decode an image format. Similarly, the JVM can instantiate an object of each ImageWriterSpi, and these objects can be used to decide which ImageWriter(s) can encode an image format.
Consider Listing 5.3, which implements an ImageReaderSpi. The purpose of this listing is to illustrate how the ImageReaderSpi passes information about its corresponding ImageReader to the ImageIO's static methods. Note that the ImageReader that corresponds to this ImageReaderSpi will be developed in a later section entitled “ImageReadParam.”
package ch5.imageio.plugins; import java.io.*; import java.util.*; import javax.imageio.ImageReader; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; /** * Simple, non-functional ImageReaderSpi used to understand how * information regarding format name, suffices and mime types * get passed to ImageIO static methods */ public class ch5v1ImageReaderSpi extends ImageReaderSpi { static final String[] suffixes = { "ch5", "CH5"}; static final String[] names = {"ch5"}; static final String[] MIMETypes = { "image/ch5" }; static final String version = "0.50"; static final String readerCN="ch5.imageio.plugins.ch5v1ImageReader"; static final String vendorName = "Company Name"; //writerSpiNames static final String[] wSN={"ch5.imageio.plugins.ch5v1ImageWriterSpi"}; //StreamMetadataFormatNames and StreamMetadataFormatClassNames static final boolean supportedStandardStreamMetadataFormat = false; static final String nativeStreamMFN = null; static final String nativeStreamMFCN = null; static final String[] extraStreamMFN = null; static final String[] extraStreamMFCN = null; //ImageMetadataFormatNames and ImageMetadataFormatClassNames static final boolean supportedStandardImageMetadataFormat = false; static final String nativeImageMFN = null; static final String nativeImageMFCN = null; static final String[] extraImageMFN = {null}; static final String[] extraImageMFCN = {null}; public ch5v1ImageReaderSpi() { super(vendorName, version, names, suffixes, MIMETypes, readerCN, // reader class name STANDARD_INPUT_TYPE, wSN, // writer spi names supportedStandardStreamMetadataFormat, nativeStreamMFN, nativeStreamMFCN, extraStreamMFN, extraStreamMFCN, supportedStandardImageMetadataFormat, nativeImageMFN, nativeImageMFCN, extraImageMFN, extraImageMFCN); } public String getDescription(Locale locale) { return "Demo ch5 image reader, version " + version; } /** * We haven't created the corresponding ImageReader class yet, * so we'll just return null for now. */ public ImageReader createReaderInstance(Object extension) { return new ch5v1ImageReader(this); } /** * This method gets called when an application wants to see if * the input image's format can be decoded by this ImageReader. * In this case, we'll simply check the first line of data to * see if it is a 5 which is the format type's magic number. * Note that we initially make sure the input object is of * type ImageInputStream so we know it is compatible with * mark and reset methods. */ public boolean canDecodeInput(Object input) { boolean reply = false; if (!(input instanceof ImageInputStream)) return reply; ImageInputStream iis = (ImageInputStream)input; iis.mark(); // mark where we are in ImageInputStream try { String magicNumber = iis.readLine().trim(); iis.reset(); // reset stream back to marked location if (magicNumber.equals("5")) reply = true; } catch (IOException exception) { } return reply; } /** * This method gets called when the set of file suffices is * requested by the ImageIO's getImageReadersBySuffix method * It doesn't need to be redefined here, but is done for * illustrative purposes */ public String[] getFileSuffixes() { return super.getFileSuffixes(); } /** * This method gets called when the set of file mime types is * requested by the ImageIO's getImageReadersByMIMEType method * It doesn't need to be redefined here, but is done for * illustrative purposes */ public String[] getMIMETypes() { return super.getMIMETypes(); } /** * This method gets called when the set of format names is * requested by the ImageIO's getImageReadersByFormatName method * It doesn't need to be redefined here, but is done for * illustrative purposes */ public String[] getFormatNames() { return super.getFormatNames(); } } |
In general, the explanation of the ImageWriterSpi class is similar to that of the ImageReaderSpi class, except that instead of having a
public boolean canDecodeImage(Object source)
it has a
public boolean canEncodeImage(ImageTypeSpecifier its)
where, as previously mentioned, the ImageTypeSpecifier class is simply a container class for holding an image's ColorModel and SampleModel. Thus, while an ImageReader can be chosen using the input image's suffix, MIME type, format, or by examining the input stream, an ImageWriter can be chosen using the output image's suffix, MIME type, format, or by considering the image's ColorModel and SampleModel pair. Sample code for an ImageWriterSpi is shown in Listing 5.4. In this listing, all metadata will be given null values. In the final section, “Final Plug-in Code,” it will be redone using metadata.
package ch5.imageio.plugins; import java.io.*; import java.util.*; import java.awt.image.*; import javax.imageio.ImageWriter; import javax.imageio.ImageTypeSpecifier; import javax.imageio.spi.ImageWriterSpi; import javax.imageio.stream.ImageInputStream; /** * Simple, non-functional ImageWriterSpi used to understand how * information regarding format name, suffices and mime types * get passed to ImageIO static methods */ public class ch5v1ImageWriterSpi extends ImageWriterSpi { static final String[] suffixes = {"ch5", "CH5"}; static final String[] names = {"ch5"}; static final String[] MIMETypes = {"image/ch5" }; static final String version = "0.50"; static final String writerCN = "ch5.imageio.plugins.ch5v1ImageWriter"; static final String vendorName = "Company Name"; static final String[] rdrSpiNames={"ch5.imageio.ch5v1ImageReaderSpi"}; static final boolean supportsStandardStreamMetadataFormat = false; static final String nativeStreamMetadataFormatName = null; static final String nativeStreamMetadataFormatClassName = null; static final String[] extraStreamMetadataFormatNames = null; static final String[] extraStreamMetadataFormatClassNames = null; static final boolean supportsStandardImageMetadataFormat = false; static final String nativeImageMetadataFormatName = null; static final String nativeImageMetadataFormatClassName = null; static final String[] extraImageMetadataFormatNames = null; static final String[] extraImageMetadataFormatClassNames = null; public ch5v1ImageWriterSpi() { super(vendorName, version, names, suffixes, MIMETypes, writerCN, //writer class name STANDARD_OUTPUT_TYPE, rdrSpiNames, //reader spi names supportsStandardStreamMetadataFormat, nativeStreamMetadataFormatName, nativeStreamMetadataFormatClassName, extraStreamMetadataFormatNames, extraStreamMetadataFormatClassNames, supportsStandardImageMetadataFormat, nativeImageMetadataFormatName, nativeImageMetadataFormatClassName, extraImageMetadataFormatNames, extraImageMetadataFormatClassNames); } public String getDescription(Locale locale) { return "Demo ch5 image writer, version " + version; } /** * We haven't created the corresponding ImageWriter class yet, * so we'll just return null for now. */ public ImageWriter createWriterInstance(Object extension) { return new ch5v1ImageWriter(this); } /** * This method gets called when an application wants to see if * the corresponding ImageWriter can encode an image with * a ColorModel and SampleModel specified by the ImageTypeSpecifier. * For this example, we will only advertise that we can encode * gray scale images with 8 bit pixels. */ public boolean canEncodeImage(ImageTypeSpecifier its) { if (its.getBufferedImageType() == BufferedImage.TYPE_BYTE_GRAY) return true; else return false; } /** * This method gets called when the set of file suffices is * requested by the ImageIO's getImageWritersBySuffix method * It doesn't need to be redefined here, but is done for * illustrative purposes */ public String[] getFileSuffixes() { return super.getFileSuffixes(); } /** * This method gets called when the set of file mime types is * requested by the ImageIO's getImageWritersByMIMEType method * It doesn't need to be redefined here, but is done for * illustrative purposes */ public String[] getMIMETypes() { return super.getMIMETypes(); } /** * This method gets called when the set of format names is * requested by the ImageIO's getImageWritersByFormatName method * It doesn't need to be redefined here, but is done for * illustrative purposes */ public String[] getFormatNames() { return super.getFormatNames(); } } |
In order for the JVM to discover the ImageReader and ImageWriter plug-ins, they must be contained in a properly formatted JAR file. Furthermore, the JAR file must contain a META-INF/services directory for listing the service providers contained in that JAR file. For each service provider interface that is implemented by a class stored in this JAR file, a file whose name is the fully qualified class name of the SPI should be placed in the services directory. Inside each of these files should be the fully qualified names of the implementation classes contained in the JAR file (one per line). For example, in Listing 5.3 the SPI is javax.imageio.spi.ImageReaderSpi, so that will be the name of a file in the META-INF/services directory. The name of the class implementing this interface is ch5.imageio.ch5v1ReaderSpi, so that name will go inside that file. Using Listings 5.1–5.4, the contents of their JAR files would show the following:
META-INF/ META-INF/MANIFEST.MF META-INF/services/ META-INF/services/javax.imageio.spi.ImageReaderSpi META-INF/services/javax.imageio.spi.ImageWriterSpi ch5/ ch5/imageio/ ch5/imageio/RWtypes.class ch5/imageio/plugins/ ch5/imageio/plugins/ch5v1ImageReaderSpi.class ch5/imageio/plugins/ch5v1ImageWriterSpi.class ch5/imageio/displayImage.class
If you examine the contents of the file META-INF/services/javax.imageio.spi.ImageReaderSpi, you will see the text ch5.imageio.plugins.ch5ImageReaderSpi.
Tip
One way to format your JAR file is to create an appropriate services directory, and then use the following commands:
(for UNIX)
jar cf ch5.jar ch5 jar xf ch5.jar META-INF mv services META-INF/services rm ch5.jar jar cfM ch5.jar ch5 META-INF
(for DOS)
jar cf ch5.jar ch5 jar xf ch5.jar META-INF move services META-INFservices del ch5.jar jar cfM ch5.jar ch5 META-INF
The last step in getting the application to acknowledge these SPI classes is to make sure that this JAR file is located somewhere on the application classpath. If we run Listing 5.1 with this JAR file located on the application classpath, the new output is as follows:
For Reading: By format: png jpeg JPEG gif jpg JPG ch5 By MIME Types: image/jpeg image/ch5 image/png image/x-png image/gif For Writing: By format: PNG png jpeg JPEG jpg JPG ch5 By MIME Types: image/jpeg image/ch5 image/png image/x-png