Final Plug-in Code

As discussed previously, ImageReaders and ImageWriters may be made known to the JVM through the use of plug-ins. The remainder of the chapter will be devoted to presenting the code listings for the ch5ImageReader and ch5ImageWriter classes, along with their corresponding service provider interfaces and metadata classes.

ch5ImageReader

Listing 5.6 is identical to Listing 5.5, except that the metadata formats have now been defined so the getMetadata and getImageData methods no longer return null.

The way metadata is used in this ImageReader class is that the setInput and read methods obtain the stream and image metadata respectively (see Table 5.2). This metadata is then available to be returned to an application that uses the ImageReader's getStreamMetadata and getImageMetadata methods.

Table 5.2. Relationship Between ImageReader Methods and Metadata in the ch5ImageReader Class
ImageReader Method Effect on Metadata
setInput Decodes stream metadata
read Decodes image metadata
getStreamMetadata Converts stream metadata to an IIOMetadata object that is returned
getImageMetadata Converts image metadata to an IIOMetadata object that is returned

Listing 5.6 ch5ImageReader.java
package ch5.imageio.plugins;

import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.image.*;
import javax.imageio.IIOException;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageReadParam;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import ch5.imageio.plugins.*;

/**
 * ch5ImageReader.java -- this class provides the functionality to
 * read an image of format ch5.
 */
public 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();
        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);
    }
    }
}

ch5ImageWriter

The way metadata is used in this ImageWriter class is that the write method writes both the stream and image metadata (see Table 5.3 and Listing 5.7). Because the write method might be called any number of times for different images, a boolean variable (StreamMetadataWritten) is used to ensure that the stream metadata is only written during the initial write method call.

The metadata that is being written must be obtained from the application and passed to the ImageWriter. The application gets this metadata by instantiating IIOMetadata objects for the stream and image metadata (ch5StreamMetadata and ch5ImageMetadata in this example), and then setting them to the correct state.

Table 5.3. Relationship Between ImageWriter Methods and Metadata
ImageWriter Method Effect on Metadata
Constructor Passes stream and image metadata into the ImageWriter.
write
  1. If stream metadata hasn't already been encoded, it encodes stream metadata.

  2. Encodes image metadata.


Listing 5.7 ch5ImageWriter.java
package ch5.imageio.plugins;

import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.image.*;
import org.w3c.dom.*;
import javax.imageio.IIOImage;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter;
import javax.imageio.ImageWriteParam;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;

/**
* ch5ImageWriter.java -- this class provides the functionality to
* write an image of format ch5.
*/
public class ch5ImageWriter extends ImageWriter {
   private ImageOutputStream ios;
   private boolean streamMetadataRead;

   public ch5ImageWriter(ImageWriterSpi originatingProvider) {
    super(originatingProvider);
    streamMetadataRead = false;
   }

   /**
    * this method is used to convert an ImageReader's image metadata
    * which is in a particular format into image metadata that can be
    * used for this ImageWriter.  Primarily this is used for
    * transcoding (format conversion).  This ImageWriter does not
    * support such conversions
    */
   public IIOMetadata convertImageMetadata(IIOMetadata metadata,
                        ImageTypeSpecifier specifier,
                        ImageWriteParam param) {
    return null;
   }

   /**
    * this method is used to convert an ImageReader's stream metadata
    * which is in a particular format into stream metadata that can
    * be used for this ImageWriter.  Primarily this is used for
    * transcoding (format conversion).  This ImageWriter does not
    * support such conversions
    */
   public IIOMetadata convertStreamMetadata(IIOMetadata metadata,
                         ImageWriteParam param) {
    return null;
   }

   /**
    * provide default values for the image metadata
    */
   public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier specifier,
                           ImageWriteParam param) {
    ch5ImageMetadata imagemd = new ch5ImageMetadata();
    imagemd.initialize(256, 256);  // default image size
    return imagemd;
   }

   /**
    * provide default values for the stream metadata
    */
   public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
    ch5StreamMetadata streammd = new ch5StreamMetadata();
    streammd.initialize(1);  // default number of images
    return streammd;
   }


   /**
    * write out the output image specified by index imageIndex using
    * the parameters specified by the ImageWriteParam object param
    */
   public void write(IIOMetadata metadata,
              IIOImage iioimage,
              ImageWriteParam param) {
    Node root = null;
    Node dimensionsElementNode = null;
    Raster raster = iioimage.getRaster();

    /*
     * Set stream metadata if it hasn't been set yet
     */
    if (streamMetadataRead == false) {
        root = metadata.getAsTree("ch5.imageio.ch5stream_1.0");
        dimensionsElementNode = root.getFirstChild();
        Node numberImagesAttributeNode
= dimensionsElementNode.getAttributes().getNamedItem("numberImages");
        String numberImages = numberImagesAttributeNode.getNodeValue();
        try {
        ios.writeBytes("5
");
        ios.writeBytes(numberImages+"
");
        streamMetadataRead = true;
        }
        catch (IOException exception) {
        }
    }

    ch5ImageMetadata imageMetadata = (ch5ImageMetadata)iioimage.getMetadata();
    root = imageMetadata.getAsTree("ch5.imageio.ch5image_1.0");
    dimensionsElementNode = root.getFirstChild();

    Node widthAttributeNode = dimensionsElementNode.getAttributes(). getNamedItem(
"imageWidth");
String widthString = widthAttributeNode.getNodeValue();

    Node heightAttributeNode = dimensionsElementNode.getAttributes(). getNamedItem(
"imageHeight");
    String heightString = heightAttributeNode.getNodeValue();

    int sourceWidth = Integer.parseInt(widthString);
    int sourceHeight = Integer.parseInt(heightString);
    int destinationWidth = -1;
    int destinationHeight = -1;
    int sourceRegionWidth = -1;
    int sourceRegionHeight = -1;
    int sourceRegionXOffset = -1;
    int sourceRegionYOffset = -1;
    int xSubsamplingFactor = -1;
    int ySubsamplingFactor = -1;

       if (param == null)
           param = getDefaultWriteParam();

    /*
     * get Rectangle object which will be used to clip
     * the source image's dimensions.
     */
    Rectangle sourceRegion = param.getSourceRegion();
    if (sourceRegion != null) {
        sourceRegionWidth = (int)sourceRegion.getWidth();
        sourceRegionHeight = (int)sourceRegion.getHeight();
        sourceRegionXOffset = (int)sourceRegion.getX();
        sourceRegionYOffset = (int)sourceRegion.getY();

        /*
         * correct for overextended source regions
         */
        if (sourceRegionXOffset + sourceRegionWidth > sourceWidth)
        destinationWidth = sourceWidth-sourceRegionXOffset;
        else
        destinationWidth = sourceRegionWidth;

        if (sourceRegionYOffset + sourceRegionHeight > sourceHeight)
        destinationHeight = sourceHeight-sourceRegionYOffset;
        else
        destinationHeight = sourceRegionHeight;
    }
    else {
        destinationWidth = sourceWidth;
        destinationHeight = sourceHeight;
        sourceRegionXOffset = sourceRegionYOffset = 0;
    }
    /*
     * get subsampling factors
     */
    xSubsamplingFactor = param.getSourceXSubsampling();
    ySubsamplingFactor = param.getSourceYSubsampling();

    destinationWidth = (destinationWidth-1)/xSubsamplingFactor + 1;
    destinationHeight = (destinationHeight-1)/ySubsamplingFactor + 1;

    byte[] sourceBuffer;
    byte[] destinationBuffer = new byte[destinationWidth];

       try {
        ios.writeBytes(new String(destinationWidth+ "
"));
        ios.writeBytes(new String(destinationHeight+ "
"));

        int jj;
        for (int j=0; j<sourceWidth; j++) {
        sourceBuffer= (byte[])raster.getDataElements(0, j, sourceWidth, 1, null);
jj = j - sourceRegionYOffset;
        if (jj % ySubsamplingFactor == 0) {
            jj /= ySubsamplingFactor;
            if ((jj >= 0) && (jj < destinationHeight)) {
            for (int i=0;i<destinationWidth;i++)
                destinationBuffer[i] = 
sourceBuffer[sourceRegionXOffset+i*xSubsamplingFactor];
            ios.write(destinationBuffer, 0, destinationWidth);
            ios.flush();
            }
        }
        }
       }
       catch (IOException e) {
        System.err.println("IOException: " + e.getMessage());
       }
   }

   public void setOutput(Object output) {
    super.setOutput(output);

       if (output == null)
        throw new IllegalArgumentException("output is null");

    if (!(output instanceof ImageOutputStream))
        throw new IllegalArgumentException("output not an ImageOutputStream");

    ios =  (ImageOutputStream)output;
    streamMetadataRead = false;
   }
}

ch5StreamMetadata

This is the class used to hold the stream metadata (see Listing 5.8). For reading, its values are taken from the input stream. For writing, its values must be set by the application. The document type definition (DTD) for this class is the following:

<!ELEMENT ch5.imageio.ch5stream_1.00 (imageDimensions)>
<!ATTLIST imageDimensions
   numberImages  CDATA  #REQUIRED
>

Clearly, this is a very minimal set of stream metadata used for illustrative purposes. In practice, these classes will be much more complicated.

Listing 5.8 ch5StreamMetadata.java
package ch5.imageio.plugins;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.metadata.IIOMetadataFormat;
import org.w3c.dom.Node;


/**
* ch5StreamMetadata.java -- holds stream metadata for the ch5 format.
* The internal tree for holding this metadata is read only
*/
public class ch5StreamMetadata extends IIOMetadata {
   static final String
       nativeMetadataFormatName = "ch5.imageio.ch5stream_1.0";

   static final String[] metadataFormatNames = {
       nativeMetadataFormatName
   };

   public int numberImages;

   public ch5StreamMetadata() {
       super(nativeMetadataFormatName, metadataFormatNames);
       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("Not a recognized 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("Not a recognized format!");
       }
   }

   /**
    * returns the stream metadata in a tree using the following format
    * <!ELEMENT ch5.imageio.ch5stream_1.0 (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;
   }
}

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset