Java Code:
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;
    }
}