Write an image of a given format

/*

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.Raster;
import  java.io.IOException;

import  javax.imageio.IIOImage;
import  javax.imageio.ImageTypeSpecifier;
import  javax.imageio.ImageWriteParam;
import  javax.imageio.ImageWriter;
import  javax.imageio.metadata.IIOMetadata;
import  javax.imageio.metadata.IIOMetadataFormat;
import  javax.imageio.metadata.IIOMetadataNode;
import  javax.imageio.spi.ImageWriterSpi;
import  javax.imageio.stream.ImageOutputStream;

import  org.w3c.dom.Node;

/**
  * ch5ImageWriter.java -- this class provides the functionality to write an
  * image of format ch5.
  */
public class  ch5ImageWriter  extends  ImageWriter  {

   public  ch5ImageWriter ( ImageWriterSpi originatingProvider ) {
     super ( originatingProvider ) ;
     streamMetadataWritten =  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 () ;
     int  width = raster.getWidth () ;
     int  height = raster.getHeight () ;
     imagemd.initialize ( width, height ) // 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 ;

     if  ( iioimage.getRenderedImage ()  !=  null )
       raster = iioimage.getRenderedImage () .getData () ;
     else
       raster = iioimage.getRaster () ;

     /*
      * since this format allows you to write multiple images, the
      * streamMetadataWritten variable makes sure the stream metadata is
      * written only once
      */
     if  ( streamMetadataWritten ==  false ) {
       if  ( metadata ==  null )
         metadata = getDefaultStreamMetadata ( param ) ;
       root = metadata.getAsTree ( "ch5.imageio.ch5stream_1.00" ) ;
       dimensionsElementNode = root.getFirstChild () ;
       Node numberImagesAttributeNode = dimensionsElementNode
           .getAttributes () .getNamedItem ( "numberImages" ) ;
       String numberImages = numberImagesAttributeNode.getNodeValue () ;
       try  {
         ios.writeBytes ( "5\n" ) ;
         ios.writeBytes ( numberImages ) ;
         ios.flush () ;
       catch  ( IOException ioe ) {
         System.err.println ( "IOException "  + ioe.getMessage ()) ;
       }
       streamMetadataWritten =  true ;
     }

     String widthString;
     String heightString;
     IIOMetadata imageMetadata =  ( ch5ImageMetadata iioimage.getMetadata () ;
     /*
      * don't really need image metadata object here since raster knows
      * necessary information
      */
     if  ( imageMetadata ==  null )
       imageMetadata = getDefaultImageMetadata ( null, param ) ;

     root = imageMetadata.getAsTree ( "ch5.imageio.ch5image_1.00" ) ;
     dimensionsElementNode = root.getFirstChild () ;

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

     Node heightAttributeNode = dimensionsElementNode.getAttributes ()
         .getNamedItem ( "imageHeight" ) ;
     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 ( "\n" )) ;
       ios.writeBytes ( new  String ( destinationWidth +  "\n" )) ;
       ios.writeBytes ( new  String ( destinationHeight +  "\n" )) ;

       int  jj;
       int  index;
       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++ ) {
               index = sourceRegionXOffset + i
                   * xSubsamplingFactor;
               destinationBuffer [ i = sourceBuffer [ index ] ;
             }
             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;
     streamMetadataWritten =  false ;
   }

   private  ImageOutputStream ios;

   private  boolean  streamMetadataWritten;

   private  Raster raster;
}

/**
  * 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;
   }
}

/**
  * 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;
     }
}