Java tutorial
/* Java Media APIs: Cross-Platform Imaging, Media and Visualization Alejandro Terrazas Sams, Published November 2002, ISBN 0672320940 */ import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; import java.awt.image.WritableRaster; import java.io.IOException; import java.util.Iterator; import java.util.Locale; import javax.imageio.IIOException; import javax.imageio.ImageReadParam; import javax.imageio.ImageReader; import javax.imageio.ImageTypeSpecifier; import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadataFormat; import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; import org.w3c.dom.Node; /** * Simple, functional ImageReaderSpi used to understand how information * regarding format name, suffices and mime types get passed to ImageIO static * methods */ public class ch5ImageReaderSpi extends ImageReaderSpi { static final String[] suffixes = { "ch5", "CH5" }; static final String[] names = { "ch5" }; static final String[] MIMETypes = { "image/ch5" }; static final String version = "1.00"; static final String readerCN = "ch5.imageio.plugins.ch5ImageReader"; static final String vendorName = "CompanyName"; //writerSpiNames static final String[] wSN = { "ch5.imageio.plugins.ch5ImageWriterSpi" }; //StreamMetadataFormatNames and StreamMetadataFormatClassNames static final boolean supportedStandardStreamMetadataFormat = false; static final String nativeStreamMFN = "ch5.imageio.ch5stream_1.00"; static final String nativeStreamMFCN = "ch5.imageio.ch5stream"; static final String[] extraStreamMFN = null; static final String[] extraStreamMFCN = null; //ImageMetadataFormatNames and ImageMetadataFormatClassNames static final boolean supportedStandardImageMetadataFormat = false; static final String nativeImageMFN = "ch5.imageio.ch5image1.00"; static final String nativeImageMFCN = "ch5.imageio.ch5image"; static final String[] extraImageMFN = null; static final String[] extraImageMFCN = null; public ch5ImageReaderSpi() { super(vendorName, version, names, suffixes, MIMETypes, readerCN, //readerClassName STANDARD_INPUT_TYPE, wSN, //writerSpiNames false, nativeStreamMFN, nativeStreamMFCN, extraStreamMFN, extraStreamMFCN, false, nativeImageMFN, nativeImageMFCN, extraImageMFN, extraImageMFCN); } public String getDescription(Locale locale) { return "Demo ch5 image reader, version " + version; } public ImageReader createReaderInstance(Object extension) { return new ch5ImageReader(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 byte of data to see if its a 5 which is the format * type's magic number */ public boolean canDecodeInput(Object input) { boolean reply = false; 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; } } class ch5ImageReader extends ImageReader { private ImageInputStream iis; private ch5ImageMetadata[] imagemd; private ch5StreamMetadata streammd; public ch5ImageReader(ImageReaderSpi originatingProvider) { super(originatingProvider); } /** * return the ch5StreamMetadata object instantiated in the setStreamMetadata * method */ public IIOMetadata getStreamMetadata() { return streammd; } /** * return the ch5ImageMetadata object instantiated in the setImageMetadata * method */ public IIOMetadata getImageMetadata(int imageIndex) { return imagemd[imageIndex]; } /** * this method sets the input for this ImageReader and also calls the * setStreamMetadata method so that the numberImages field is available */ public void setInput(Object object, boolean seekForwardOnly) { super.setInput(object, seekForwardOnly); if (object == null) throw new IllegalArgumentException("input is null"); if (!(object instanceof ImageInputStream)) { String argString = "input not an ImageInputStream"; throw new IllegalArgumentException(argString); } iis = (ImageInputStream) object; setStreamMetadata(iis); } /** * this method provides suggestions for possible image types that will be * used to decode the image specified by index imageIndex. By default, the * first image type returned by this method will be the image type of the * BufferedImage returned by the ImageReader's getDestination method. In * this case, we are suggesting using an 8 bit grayscale image with no alpha * component. */ public Iterator getImageTypes(int imageIndex) { java.util.List l = new java.util.ArrayList(); ; int bits = 8; /* * can convert ch5 format into 8 bit grayscale image with no alpha */ l.add(ImageTypeSpecifier.createGrayscale(bits, DataBuffer.TYPE_BYTE, false)); return l.iterator(); } /** * read in the input image specified by index imageIndex using the * parameters specified by the ImageReadParam object param */ public BufferedImage read(int imageIndex, ImageReadParam param) { checkIndex(imageIndex); if (isSeekForwardOnly()) minIndex = imageIndex; else minIndex = 0; BufferedImage bimage = null; WritableRaster raster = null; /* * this method sets the image metadata so that we can use the getWidth * and getHeight methods */ setImageMetadata(iis, imageIndex); int srcWidth = getWidth(imageIndex); int srcHeight = getHeight(imageIndex); // initialize values to -1 int dstWidth = -1; int dstHeight = -1; int srcRegionWidth = -1; int srcRegionHeight = -1; int srcRegionXOffset = -1; int srcRegionYOffset = -1; int xSubsamplingFactor = -1; int ySubsamplingFactor = -1; if (param == null) param = getDefaultReadParam(); Iterator imageTypes = getImageTypes(imageIndex); try { /* * get the destination BufferedImage which will be filled using the * input image's pixel data */ bimage = getDestination(param, imageTypes, srcWidth, srcHeight); /* * get Rectangle object which will be used to clip the source * image's dimensions. */ Rectangle srcRegion = param.getSourceRegion(); if (srcRegion != null) { srcRegionWidth = (int) srcRegion.getWidth(); srcRegionHeight = (int) srcRegion.getHeight(); srcRegionXOffset = (int) srcRegion.getX(); srcRegionYOffset = (int) srcRegion.getY(); /* * correct for overextended source regions */ if (srcRegionXOffset + srcRegionWidth > srcWidth) dstWidth = srcWidth - srcRegionXOffset; else dstWidth = srcRegionWidth; if (srcRegionYOffset + srcRegionHeight > srcHeight) dstHeight = srcHeight - srcRegionYOffset; else dstHeight = srcRegionHeight; } else { dstWidth = srcWidth; dstHeight = srcHeight; srcRegionXOffset = srcRegionYOffset = 0; } /* * get subsampling factors */ xSubsamplingFactor = param.getSourceXSubsampling(); ySubsamplingFactor = param.getSourceYSubsampling(); /** * dstWidth and dstHeight should be equal to bimage.getWidth() and * bimage.getHeight() after these next two instructions */ dstWidth = (dstWidth - 1) / xSubsamplingFactor + 1; dstHeight = (dstHeight - 1) / ySubsamplingFactor + 1; } catch (IIOException e) { System.err.println("Can't create destination BufferedImage"); } raster = bimage.getWritableTile(0, 0); /* * using the parameters specified by the ImageReadParam object, read the * image image data into the destination BufferedImage */ byte[] srcBuffer = new byte[srcWidth]; byte[] dstBuffer = new byte[dstWidth]; int jj; int index; try { for (int j = 0; j < srcHeight; j++) { iis.readFully(srcBuffer, 0, srcWidth); jj = j - srcRegionYOffset; if (jj % ySubsamplingFactor == 0) { jj /= ySubsamplingFactor; if ((jj >= 0) && (jj < dstHeight)) { for (int i = 0; i < dstWidth; i++) { index = srcRegionXOffset + i * xSubsamplingFactor; dstBuffer[i] = srcBuffer[index]; } raster.setDataElements(0, jj, dstWidth, 1, dstBuffer); } } } } catch (IOException e) { bimage = null; } return bimage; } /** * this method sets the image metadata for the image indexed by index * imageIndex. This method is specific for the ch5 format and thus only sets * the image width and image height */ private void setImageMetadata(ImageInputStream iis, int imageIndex) { imagemd[imageIndex] = new ch5ImageMetadata(); try { String s; s = iis.readLine(); while (s.length() == 0) s = iis.readLine(); imagemd[imageIndex].imageWidth = Integer.parseInt(s.trim()); s = iis.readLine(); imagemd[imageIndex].imageHeight = Integer.parseInt(s.trim()); } catch (IOException exception) { } } /** * this method sets the stream metadata for the images represented by the * ImageInputStream iis. This method is specific for the ch5 format and thus * only sets the numberImages field. */ private void setStreamMetadata(ImageInputStream iis) { streammd = new ch5StreamMetadata(); try { String magicNumber = iis.readLine(); int numImages = Integer.parseInt(iis.readLine().trim()); streammd.numberImages = numImages; imagemd = new ch5ImageMetadata[streammd.numberImages]; } catch (IOException exception) { } } /** * This method can only be used after the stream metadata has been set * (which occurs in the setInput method). Else it will return a -1 */ public int getNumImages(boolean allowSearch) { return streammd.numberImages; } /** * This method can only be used after the stream metadata has been set * (which occurs in the setInput method). Else it will return a -1 */ public int getHeight(int imageIndex) { if (imagemd == null) return -1; checkIndex(imageIndex); return imagemd[imageIndex].imageHeight; } /** * This method can only be used after the stream metadata has been set * (which occurs in the setInput method). Else it will return a -1 */ public int getWidth(int imageIndex) { if (imagemd == null) return -1; checkIndex(imageIndex); return imagemd[imageIndex].imageWidth; } private void checkIndex(int imageIndex) { if (imageIndex >= streammd.numberImages) { String argString = "imageIndex >= number of images"; throw new IndexOutOfBoundsException(argString); } if (imageIndex < minIndex) { String argString = "imageIndex < minIndex"; throw new IndexOutOfBoundsException(argString); } } } /** * ch5ImageMetadata.java -- holds image metadata for the ch5 format. The * internal tree for holding this metadata is read only */ class ch5ImageMetadata extends IIOMetadata { static final String nativeMetadataFormatName = "ch5.imageio.ch5image_1.00"; static final String nativeMetadataFormatClassName = "ch5.imageio.ch5image"; static final String[] extraMetadataFormatNames = null; static final String[] extraMetadataFormatClassNames = null; static final boolean standardMetadataFormatSupported = false; public int imageWidth; public int imageHeight; public ch5ImageMetadata() { super(standardMetadataFormatSupported, nativeMetadataFormatName, nativeMetadataFormatClassName, extraMetadataFormatNames, extraMetadataFormatClassNames); imageWidth = -1; imageHeight = -1; } public boolean isReadOnly() { return true; } /** * IIOMetadataFormat objects are meant to describe the structure of metadata * returned from the getAsTree method. In this case, no such description is * available */ public IIOMetadataFormat getMetadataFormat(String formatName) { if (formatName.equals(nativeMetadataFormatName)) { return null; } else { throw new IllegalArgumentException("Unrecognized format!"); } } /** * returns the image metadata in a tree corresponding to the provided * formatName */ public Node getAsTree(String formatName) { if (formatName.equals(nativeMetadataFormatName)) { return getNativeTree(); } else { throw new IllegalArgumentException("Unrecognized format!"); } } /** * returns the image metadata in a tree using the following format <!ELEMENT * ch5.imageio.ch5image_1.00 (imageDimensions)> <!ATTLIST imageDimensions * imageWidth CDATA #REQUIRED imageHeight CDATA #REQUIRED */ private Node getNativeTree() { IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName); IIOMetadataNode node = new IIOMetadataNode("imageDimensions"); node.setAttribute("imageWidth", Integer.toString(imageWidth)); node.setAttribute("imageHeight", Integer.toString(imageHeight)); root.appendChild(node); return root; } public void setFromTree(String formatName, Node root) { throw new IllegalStateException("Metadata is read-only!"); } public void mergeTree(String formatName, Node root) { throw new IllegalStateException("Metadata is read-only!"); } public void reset() { throw new IllegalStateException("Metadata is read-only!"); } /** * initialize the image metadata elements width and height */ public void initialize(int width, int height) { imageWidth = width; imageHeight = height; } } /** * ch5StreamMetadata.java -- holds stream metadata for the ch5 format. The * internal tree for holding this metadata is read only */ class ch5StreamMetadata extends IIOMetadata { static final String nativeMetadataFormatName = "ch5.imageio.ch5stream_1.00"; static final String nativeMetadataFormatClassName = "ch5.imageio.ch5stream"; static final String[] extraMetadataFormatNames = null; static final String[] extraMetadataFormatClassNames = null; static final boolean standardMetadataFormatSupported = false; public int numberImages; public ch5StreamMetadata() { super(standardMetadataFormatSupported, nativeMetadataFormatName, nativeMetadataFormatClassName, extraMetadataFormatNames, extraMetadataFormatClassNames); numberImages = -1; } public boolean isReadOnly() { return true; } /** * IIOMetadataFormat objects are meant to describe the structure of metadata * returned from the getAsTree method. In this case, no such description is * available */ public IIOMetadataFormat getMetadataFormat(String formatName) { if (formatName.equals(nativeMetadataFormatName)) { return null; } else { throw new IllegalArgumentException("Unrecognized format!"); } } /** * returns the stream metadata in a tree corresponding to the provided * formatName */ public Node getAsTree(String formatName) { if (formatName.equals(nativeMetadataFormatName)) { return getNativeTree(); } else { throw new IllegalArgumentException("Unrecognized format!"); } } /** * returns the stream metadata in a tree using the following format * <!ELEMENT ch5.imageio.ch5stream_1.00 (imageDimensions)> <!ATTLIST * imageDimensions numberImages CDATA #REQUIRED */ private Node getNativeTree() { IIOMetadataNode node; // scratch node IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName); // Image descriptor node = new IIOMetadataNode("imageDimensions"); node.setAttribute("numberImages", Integer.toString(numberImages)); root.appendChild(node); return root; } public void setFromTree(String formatName, Node root) { throw new IllegalStateException("Metadata is read-only!"); } public void mergeTree(String formatName, Node root) { throw new IllegalStateException("Metadata is read-only!"); } public void reset() { throw new IllegalStateException("Metadata is read-only!"); } /** * initialize the stream metadata element numberImages */ public void initialize(int numberImages) { this.numberImages = numberImages; } }