/* ImageReader.java -- Decodes raster images.
   Copyright (C) 2004, 2005  Free Software Foundation, Inc.

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */


package javax.imageio;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.MissingResourceException;
import java.util.Set;

import javax.imageio.event.IIOReadProgressListener;
import javax.imageio.event.IIOReadUpdateListener;
import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;

/**
 * A class for decoding images within the ImageIO framework.
 *
 * An ImageReader for a given format is instantiated by an
 * ImageReaderSpi for that format.  ImageReaderSpis are registered
 * with the IIORegistry.
 *
 * The ImageReader API supports reading animated images that may have
 * multiple frames; to support such images many methods take an index
 * parameter.
 *
 * Images may also be read in multiple passes, where each successive
 * pass increases the level of detail in the destination image.
 */
public abstract class ImageReader
{
  private boolean aborted;

  /**
   * All locales available for localization of warning messages, or
   * null if localization is not supported.
   */
  protected Locale[] availableLocales = null;

  /**
   * true if the input source does not require metadata to be read,
   * false otherwise.
   */
  protected boolean ignoreMetadata = false;

  /**
   * An ImageInputStream from which image data is read.
   */
  protected Object input = null;

  /**
   * The current locale used to localize warning messages, or null if
   * no locale has been set.
   */
  protected Locale locale = null;

  /**
   * The minimum index at which data can be read.  Constantly 0 if
   * seekForwardOnly is false, always increasing if seekForwardOnly is
   * true.
   */
  protected int minIndex = 0;

  /**
   * The image reader SPI that instantiated this reader.
   */
  protected ImageReaderSpi originatingProvider = null;

  /**
   * A list of installed progress listeners.  Initially null, meaning
   * no installed listeners.
   */
  protected List<IIOReadProgressListener> progressListeners = null;

  /**
   * true if this reader should only read data further ahead in the
   * stream than its current location.  false if it can read backwards
   * in the stream.  If this is true then caching can be avoided.
   */
  protected boolean seekForwardOnly = false;

  /**
   * A list of installed update listeners.  Initially null, meaning no
   * installed listeners.
   */
  protected List<IIOReadUpdateListener> updateListeners = null;

  /**
   * A list of installed warning listeners.  Initially null, meaning
   * no installed listeners.
   */
  protected List<IIOReadWarningListener> warningListeners = null;

  /**
   * A list of warning locales corresponding with the list of
   * installed warning listeners.  Initially null, meaning no locales.
   */
  protected List<Locale> warningLocales = null;

  /**
   * Construct an image reader.
   *
   * @param originatingProvider the provider that is constructing this
   * image reader, or null
   */
  protected ImageReader(ImageReaderSpi originatingProvider)
  {
    this.originatingProvider = originatingProvider;
  }

  /**
   * Request that reading be aborted.  The unread contents of the
   * image will be undefined.
   *
   * Readers should clear the abort flag before starting a read
   * operation, then poll it periodically during the read operation.
   */
  public void abort()
  {
    aborted = true;
  }

  /**
   * Check if the abort flag is set.
   *
   * @return true if the current read operation should be aborted,
   * false otherwise
   */
  protected boolean abortRequested()
  {
    return aborted;
  }

  /**
   * Install a read progress listener.  This method will return
   * immediately if listener is null.
   *
   * @param listener a read progress listener or null
   */
  public void addIIOReadProgressListener(IIOReadProgressListener listener)
  {
    if (listener == null)
      return;
    if (progressListeners == null)
      progressListeners = new ArrayList ();
    progressListeners.add(listener);
  }

  /**
   * Install a read update listener.  This method will return
   * immediately if listener is null.
   *
   * @param listener a read update listener
   */
  public void addIIOReadUpdateListener(IIOReadUpdateListener listener)
  {
    if (listener == null)
      return;
    if (updateListeners == null)
      updateListeners = new ArrayList ();
    updateListeners.add(listener);
  }

  /**
   * Install a read warning listener.  This method will return
   * immediately if listener is null.  Warning messages sent to this
   * listener will be localized using the current locale.  If the
   * current locale is null then this reader will select a sensible
   * default.
   *
   * @param listener a read warning listener
   */
  public void addIIOReadWarningListener(IIOReadWarningListener listener)
  {
    if (listener == null)
      return;
    if (warningListeners == null)
      warningListeners = new ArrayList ();
    warningListeners.add(listener);
  }

  /**
   * Check if this reader can handle raster data.  Determines whether
   * or not readRaster and readTileRaster throw
   * UnsupportedOperationException.
   *
   * @return true if this reader supports raster data, false if not
   */
  public boolean canReadRaster()
  {
    return false;
  }

  /**
   * Clear the abort flag.
   */
  protected void clearAbortRequest()
  {
    aborted = false;
  }

  /**
   * Releases any resources allocated to this object.  Subsequent
   * calls to methods on this object will produce undefined results.
   *
   * The default implementation does nothing; subclasses should use
   * this method ensure that native resources are released.
   */
  public void dispose()
  {
    // The default implementation does nothing.
  }

  /**
   * Returns the aspect ratio of this image, the ration of its width
   * to its height.  The aspect ratio is useful when resizing an image
   * while keeping its proportions constant.
   *
   * @param imageIndex the frame index
   *
   * @return the image's aspect ratio
   *
   * @exception IllegalStateException if input is null
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds
   * @exception IOException if a read error occurs
   */
  public float getAspectRatio(int imageIndex)
    throws IOException
  {
    if (input == null)
      throw new IllegalStateException("input is null");

    return (float) (getWidth(imageIndex) / getHeight(imageIndex));
  }

  /**
   * Retrieve the available locales.  Return null if no locales are
   * available or a clone of availableLocales.
   *
   * @return an array of locales or null
   */
  public Locale[] getAvailableLocales()
  {
    if (availableLocales == null)
      return null;
    
    return (Locale[]) availableLocales.clone();
  }

  /**
   * Retrieve the default read parameters for this reader's image
   * format.
   *
   * The default implementation returns new ImageReadParam().
   *
   * @return image reading parameters
   */
  public ImageReadParam getDefaultReadParam()
  {
    return new ImageReadParam();
  }

  /**
   * Retrieve the format of the input source.
   *
   * @return the input source format name
   *
   * @exception IOException if a read error occurs
   */
  public String getFormatName()
    throws IOException
  {
    return originatingProvider.getFormatNames()[0];
  }

  /**
   * Get the height of the input image in pixels.  If the input image
   * is resizable then a default height is returned.
   *
   * @param imageIndex the frame index
   *
   * @return the height of the input image
   *
   * @exception IllegalStateException if input has not been set
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds
   * @exception IOException if a read error occurs
   */
  public abstract int getHeight(int imageIndex)
    throws IOException;

  /**
   * Get the metadata associated with this image.  If the reader is
   * set to ignore metadata or does not support reading metadata, or
   * if no metadata is available then null is returned.
   *
   * @param imageIndex the frame index
   *
   * @return a metadata object, or null
   *
   * @exception IllegalStateException if input has not been set
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds
   * @exception IOException if a read error occurs
   */
  public abstract IIOMetadata getImageMetadata(int imageIndex)
    throws IOException;

  /**
   * Get an iterator over the collection of image types into which
   * this reader can decode image data.  This method is guaranteed to
   * return at least one valid image type specifier.
   *
   * The elements of the iterator should be ordered; the first element
   * should be the most appropriate image type for this decoder,
   * followed by the second-most appropriate, and so on.
   *
   * @param imageIndex the frame index
   *
   * @return an iterator over a collection of image type specifiers
   *
   * @exception IllegalStateException if input has not been set
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds
   * @exception IOException if a read error occurs
   */
  public abstract Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex)
    throws IOException;

  /**
   * Set the input source to the given object, specify whether this
   * reader should be allowed to read input from the data stream more
   * than once, and specify whether this reader should ignore metadata
   * in the input stream.  The input source must be set before many
   * methods can be called on this reader. (see all ImageReader
   * methods that throw IllegalStateException).  If input is null then
   * the current input source will be removed.
   *
   * Unless this reader has direct access with imaging hardware, input
   * should be an ImageInputStream.
   *
   * @param input the input source object
   * @param seekForwardOnly true if this reader should be allowed to
   * read input from the data stream more than once, false otherwise
   * @param ignoreMetadata true if this reader should ignore metadata
   * associated with the input source, false otherwise
   *
   * @exception IllegalArgumentException if input is not a valid input
   * source for this reader and is not an ImageInputStream
   */
  public void setInput(Object input,
                       boolean seekForwardOnly,
                       boolean ignoreMetadata)
  {
    Class[] okClasses = originatingProvider.getInputTypes();
    if (okClasses == null)
      {
        if (!(input instanceof ImageInputStream))
          throw new IllegalArgumentException();
      }
    else
      {
        boolean classOk = false;
        for (int i = 0; i < okClasses.length; ++i)
          if (okClasses[i].isInstance(input))
            classOk = true;
        if (!classOk)
          throw new IllegalArgumentException();
      }

    this.input = input;
    this.seekForwardOnly = seekForwardOnly;
    this.ignoreMetadata = ignoreMetadata;
    this.minIndex = 0;
  }

  /**
   * Set the input source to the given object and specify whether this
   * reader should be allowed to read input from the data stream more
   * than once.  The input source must be set before many methods can
   * be called on this reader. (see all ImageReader methods that throw
   * IllegalStateException).  If input is null then the current input
   * source will be removed.
   *
   * @param input the input source object
   * @param seekForwardOnly true if this reader should be allowed to
   * read input from the data stream more than once, false otherwise
   *
   * @exception IllegalArgumentException if input is not a valid input
   * source for this reader and is not an ImageInputStream
   */
  public void setInput(Object in, boolean seekForwardOnly)
  {
    setInput(in, seekForwardOnly, false);
  }

  /**
   * Set the input source to the given object.  The input source must
   * be set before many methods can be called on this reader. (see all
   * ImageReader methods that throw IllegalStateException).  If input
   * is null then the current input source will be removed.
   *
   * @param input the input source object
   *
   * @exception IllegalArgumentException if input is not a valid input
   * source for this reader and is not an ImageInputStream
   */
  public void setInput(Object input)
  {
    setInput(input, false, false);
  }

  /**
   * Get this reader's image input source.  null is returned if the
   * image source has not been set.
   *
   * @return an image input source object, or null
   */
  public Object getInput()
  {
    return input;
  }

  /**
   * Get this reader's locale.  null is returned if the locale has not
   * been set.
   *
   * @return this reader's locale, or null
   */
  public Locale getLocale()
  {
    return locale;
  }

  /**
   * Return the number of images available from the image input
   * source, not including thumbnails.  This method will return 1
   * unless this reader is reading an animated image.
   *
   * Certain multi-image formats do not encode the total number of
   * images.  When reading images in those formats it may be necessary
   * to repeatedly call read, incrementing the image index at each
   * call, until an IndexOutOfBoundsException is thrown.
   *
   * The allowSearch parameter determines whether all images must be
   * available at all times.  When allowSearch is false, getNumImages
   * will return -1 if the total number of images is unknown.
   * Otherwise this method returns the number of images.
   *
   * @param allowSearch true if all images should be available at
   * once, false otherwise
   *
   * @return -1 if allowSearch is false and the total number of images
   * is currently unknown, or the number of images
   *
   * @exception IllegalStateException if input has not been set, or if
   * seekForwardOnly is true
   * @exception IOException if a read error occurs
   */
  public abstract int getNumImages(boolean allowSearch)
    throws IOException;

  /**
   * Get the number of thumbnails associated with an image.
   *
   * @param imageIndex the frame index
   *
   * @return the number of thumbnails associated with this image
   */
  public int getNumThumbnails(int imageIndex)
    throws IOException
  {
    return 0;
  }

  /**
   * Get the ImageReaderSpi that created this reader or null.
   *
   * @return an ImageReaderSpi, or null
   */
  public ImageReaderSpi getOriginatingProvider()
  {
    return originatingProvider;
  }

  /**
   * Get the metadata associated with the image being read.  If the
   * reader is set to ignore metadata or does not support reading
   * metadata, or if no metadata is available then null is returned.
   * This method returns metadata associated with the entirety of the
   * image data, whereas getImageMetadata(int) returns metadata
   * associated with a frame within a multi-image data stream.
   *
   * @return metadata associated with the image being read, or null
   *
   * @exception IOException if a read error occurs
   */
  public abstract IIOMetadata getStreamMetadata()
    throws IOException;

  /**
   * Get the height of a thumbnail image.
   *
   * @param imageIndex the frame index
   * @param thumbnailIndex the thumbnail index
   *
   * @return the height of the thumbnail image
   *
   * @exception UnsupportedOperationException if this reader does not
   * support thumbnails
   * @exception IllegalStateException if input is null
   * @exception IndexOutOfBoundsException if either index is
   * out-of-bounds
   * @exception IOException if a read error occurs
   */
  public int getThumbnailHeight(int imageIndex, int thumbnailIndex)
    throws IOException
  {
    return readThumbnail(imageIndex, thumbnailIndex).getHeight();
  }

  /**
   * Get the width of a thumbnail image.
   *
   * @param imageIndex the frame index
   * @param thumbnailIndex the thumbnail index
   *
   * @return the width of the thumbnail image
   *
   * @exception UnsupportedOperationException if this reader does not
   * support thumbnails
   * @exception IllegalStateException if input is null
   * @exception IndexOutOfBoundsException if either index is
   * out-of-bounds
   * @exception IOException if a read error occurs
   */
  public int getThumbnailWidth(int imageIndex, int thumbnailIndex)
    throws IOException
  {
    return readThumbnail(imageIndex, thumbnailIndex).getWidth();
  }

  /**
   * Get the X coordinate in pixels of the top-left corner of the
   * first tile in this image.
   *
   * @param imageIndex the frame index
   *
   * @return the X coordinate of this image's first tile
   *
   * @exception IllegalStateException if input is needed but the input
   * source is not set
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds
   * @exception IOException if a read error occurs
   */
  public int getTileGridXOffset(int imageIndex)
    throws IOException
  {
    return 0;
  }

  /**
   * Get the Y coordinate in pixels of the top-left corner of the
   * first tile in this image.
   *
   * @param imageIndex the frame index
   *
   * @return the Y coordinate of this image's first tile
   *
   * @exception IllegalStateException if input is needed but the input
   * source is not set
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds
   * @exception IOException if a read error occurs
   */
  public int getTileGridYOffset(int imageIndex)
    throws IOException
  {
    return 0;
  }

  /**
   * Get the height of an image tile.
   *
   * @param imageIndex the frame index
   *
   * @return the tile height for the given image
   *
   * @exception IllegalStateException if input is null
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds
   * @exception IOException if a read error occurs
   */
  public int getTileHeight(int imageIndex)
    throws IOException
  {
    return getHeight(imageIndex);
  }

  /**
   * Get the width of an image tile.
   *
   * @param imageIndex the frame index
   *
   * @return the tile width for the given image
   *
   * @exception IllegalStateException if input is null
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds
   * @exception IOException if a read error occurs
   */
  public int getTileWidth(int imageIndex)
    throws IOException
  {
    return getWidth(imageIndex);
  }

  /**
   * Get the width of the input image in pixels.  If the input image
   * is resizable then a default width is returned.
   *
   * @param imageIndex the image's index
   *
   * @return the width of the input image
   *
   * @exception IllegalStateException if input has not been set
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds
   * @exception IOException if a read error occurs
   */
  public abstract int getWidth(int imageIndex)
    throws IOException;

  /**
   * Check whether or not the given image has thumbnails associated
   * with it.
   *
   * @return true if the given image has thumbnails, false otherwise
   *
   * @exception IllegalStateException if input is null
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds
   * @exception IOException if a read error occurs
   */
  public boolean hasThumbnails(int imageIndex)
    throws IOException
  {
    return getNumThumbnails(imageIndex) > 0;
  }

  /**
   * Check if this image reader ignores metadata.  This method simply
   * returns the value of ignoreMetadata.
   *
   * @return true if metadata is being ignored, false otherwise
   */
  public boolean isIgnoringMetadata()
  {
    return ignoreMetadata;
  }

  /**
   * Check if the given image is sub-divided into equal-sized
   * non-overlapping pixel rectangles.
   *
   * A reader may expose tiling in the underlying format, hide it, or
   * simulate tiling even if the underlying format is not tiled.
   *
   * @return true if the given image is tiled, false otherwise
   *
   * @exception IllegalStateException if input is null
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds
   * @exception IOException if a read error occurs
   */
  public boolean isImageTiled(int imageIndex)
    throws IOException
  {
    return false;
  }

  /**
   * Check if all pixels in this image are readily accessible.  This
   * method should return false for compressed formats.  The return
   * value is a hint as to the efficiency of certain image reader
   * operations.
   *
   * @param imageIndex the frame index
   *
   * @return true if random pixel access is fast, false otherwise
   *
   * @exception IllegalStateException if input is null and it is
   * needed to determine the return value
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds but the frame data must be accessed to determine
   * the return value
   * @exception IOException if a read error occurs
   */
  public boolean isRandomAccessEasy(int imageIndex)
    throws IOException
  {
    return false;
  }

  /**
   * Check if this image reader may only seek forward within the input
   * stream.
   *
   * @return true if this reader may only seek forward, false
   * otherwise
   */
  public boolean isSeekForwardOnly()
  {
    return seekForwardOnly;
  }

  /**
   * Notifies all installed read progress listeners that image loading
   * has completed by calling their imageComplete methods.
   */
  protected void processImageComplete()
  {
    if (progressListeners != null)
      {
	Iterator it = progressListeners.iterator();

	while (it.hasNext())
	  {
	    IIOReadProgressListener listener =
	      (IIOReadProgressListener) it.next();
	    listener.imageComplete (this);
	  }
      }
  }

  /**
   * Notifies all installed read progress listeners that a certain
   * percentage of the image has been loaded, by calling their
   * imageProgress methods.
   *
   * @param percentageDone the percentage of image data that has been
   * loaded
   */
  protected void processImageProgress(float percentageDone)
  {
     if (progressListeners != null)
      {
	Iterator it = progressListeners.iterator();

	while (it.hasNext())
	  {
	    IIOReadProgressListener listener =
	      (IIOReadProgressListener) it.next();
	    listener.imageProgress(this, percentageDone);
	  }
      }
  }
  /**
   * Notifies all installed read progress listeners, by calling their
   * imageStarted methods, that image loading has started on the given
   * image.
   *
   * @param imageIndex the frame index of the image that has started
   * loading
   */
  protected void processImageStarted(int imageIndex)
  {
     if (progressListeners != null)
      {
	Iterator it = progressListeners.iterator();

	while (it.hasNext())
	  {
	    IIOReadProgressListener listener =
	      (IIOReadProgressListener) it.next();
	    listener.imageStarted(this, imageIndex);
	  }
      }
  }

  /**
   * Notifies all installed read update listeners, by calling their
   * imageUpdate methods, that the set of samples has changed.
   *
   * @param image the buffered image that is being updated
   * @param minX the X coordinate of the top-left pixel in this pass
   * @param minY the Y coordinate of the top-left pixel in this pass
   * @param width the total width of the rectangle covered by this
   * pass, including skipped pixels
   * @param height the total height of the rectangle covered by this
   * pass, including skipped pixels
   * @param periodX the horizontal sample interval
   * @param periodY the vertical sample interval
   * @param bands the affected bands in the destination
   */
  protected void processImageUpdate(BufferedImage image, int minX, int minY,
				    int width, int height, int periodX,
				    int periodY, int[] bands)
  {
    if (updateListeners != null)
      {
	Iterator it = updateListeners.iterator();

	while (it.hasNext())
	  {
	    IIOReadUpdateListener listener = (IIOReadUpdateListener) it.next();
	    listener.imageUpdate(this, image, minX, minY, width, height,
				 periodX, periodY, bands);
	  }
      }
  }

  /**
   * Notifies all installed update progress listeners, by calling
   * their passComplete methods, that a progressive pass has
   * completed.
   *
   * @param image the image that has being updated
   */
  protected void processPassComplete(BufferedImage image)
  {
    if (updateListeners != null)
      {
	Iterator it = updateListeners.iterator();

	while (it.hasNext())
	  {
	    IIOReadUpdateListener listener = (IIOReadUpdateListener) it.next();
	    listener.passComplete(this, image);
	  }
      }
  }

  /**
   * Notifies all installed read update listeners, by calling their
   * passStarted methods, that a new pass has begun.
   *
   * @param image the buffered image that is being updated
   * @param pass the current pass number
   * @param minPass the pass at which decoding will begin
   * @param maxPass the pass at which decoding will end
   * @param minX the X coordinate of the top-left pixel in this pass
   * @param minY the Y coordinate of the top-left pixel in this pass
   * @param width the total width of the rectangle covered by this
   * pass, including skipped pixels
   * @param height the total height of the rectangle covered by this
   * pass, including skipped pixels
   * @param periodX the horizontal sample interval
   * @param periodY the vertical sample interval
   * @param bands the affected bands in the destination
   */
  protected void processPassStarted(BufferedImage image, int pass, int minPass,
				    int maxPass, int minX, int minY,
				    int periodX, int periodY, int[] bands)
  {
    if (updateListeners != null)
      {
	Iterator it = updateListeners.iterator();

	while (it.hasNext())
	  {
	    IIOReadUpdateListener listener = (IIOReadUpdateListener) it.next();
	    listener.passStarted(this, image, pass, minPass, maxPass, minX,
				 minY, periodX, periodY, bands);
	  }
      }
  }

  /**
   * Notifies all installed read progress listeners that image loading
   * has been aborted by calling their readAborted methods.
   */
  protected void processReadAborted()
  {
     if (progressListeners != null)
      {
	Iterator it = progressListeners.iterator();

	while (it.hasNext())
	  {
	    IIOReadProgressListener listener =
	      (IIOReadProgressListener) it.next();
	    listener.readAborted(this);
	  }
      }
  }
  /**
   * Notifies all installed read progress listeners, by calling their
   * sequenceComplete methods, that a sequence of images has completed
   * loading.
   */
  protected void processSequenceComplete()
  {
     if (progressListeners != null)
      {
	Iterator it = progressListeners.iterator();

	while (it.hasNext())
	  {
	    IIOReadProgressListener listener =
	      (IIOReadProgressListener) it.next();
	    listener.sequenceComplete(this);
	  }
      }
  }

  /**
   * Notifies all installed read progress listeners, by calling their
   * sequenceStarted methods, a sequence of images has started
   * loading.
   *
   * @param minIndex the index of the first image in the sequence
   */
  protected void processSequenceStarted(int minIndex)
  {

    if (progressListeners != null)
      {
	Iterator it = progressListeners.iterator();

	while (it.hasNext())
	  {
	    IIOReadProgressListener listener =
	      (IIOReadProgressListener) it.next();
	    listener.sequenceStarted(this, minIndex);
	  }
      }
  }

  /**
   * Notifies all installed read progress listeners, by calling their
   * thumbnailComplete methods, that a thumbnail has completed
   * loading.
   */
  protected void processThumbnailComplete()
  {
    if (progressListeners != null)
      {
	Iterator it = progressListeners.iterator();

	while (it.hasNext())
	  {
	    IIOReadProgressListener listener =
	      (IIOReadProgressListener) it.next();
	    listener.thumbnailComplete(this);
	  }
      }
  }

  /**
   * Notifies all installed update progress listeners, by calling
   * their thumbnailPassComplete methods, that a progressive pass has
   * completed on a thumbnail.
   *
   * @param thumbnail the thumbnail that has being updated
   */
  protected void processThumbnailPassComplete(BufferedImage thumbnail)
  {
    if (updateListeners != null)
      {
	Iterator it = updateListeners.iterator();

	while (it.hasNext())
	  {
	    IIOReadUpdateListener listener = (IIOReadUpdateListener) it.next();
	    listener.thumbnailPassComplete(this, thumbnail);
	  }
      }
  }

  /**
   * Notifies all installed read update listeners, by calling their
   * thumbnailPassStarted methods, that a new pass has begun.
   *
   * @param thumbnail the thumbnail that is being updated
   * @param pass the current pass number
   * @param minPass the pass at which decoding will begin
   * @param maxPass the pass at which decoding will end
   * @param minX the X coordinate of the top-left pixel in this pass
   * @param minY the Y coordinate of the top-left pixel in this pass
   * @param width the total width of the rectangle covered by this
   * pass, including skipped pixels
   * @param height the total height of the rectangle covered by this
   * pass, including skipped pixels
   * @param periodX the horizontal sample interval
   * @param periodY the vertical sample interval
   * @param bands the affected bands in the destination
   */
  protected void processThumbnailPassStarted(BufferedImage thumbnail, int pass,
					     int minPass, int maxPass, int minX,
					     int minY, int periodX, int periodY,
					     int[] bands)
  {
    if (updateListeners != null)
      {
	Iterator it = updateListeners.iterator();

	while (it.hasNext())
	  {
	    IIOReadUpdateListener listener = (IIOReadUpdateListener) it.next();
	    listener.thumbnailPassStarted(this, thumbnail, pass, minPass,
					  maxPass, minX, minY, periodX,
					  periodY, bands);
	  }
      }
  }

  /**
   * Notifies all installed read progress listeners that a certain
   * percentage of a thumbnail has been loaded, by calling their
   * thumbnailProgress methods.
   *
   * @param percentageDone the percentage of thumbnail data that has
   * been loaded
   */
  protected void processThumbnailProgress(float percentageDone)
  {
    if (progressListeners != null)
      {
	Iterator it = progressListeners.iterator();

	while (it.hasNext())
	  {
	    IIOReadProgressListener listener =
	      (IIOReadProgressListener) it.next();
	    listener.thumbnailProgress(this, percentageDone);
	  }
      }
  }

  /**
   * Notifies all installed read progress listeners, by calling their
   * imageStarted methods, that thumbnail loading has started on the
   * given thumbnail of the given image.
   *
   * @param imageIndex the frame index of the image one of who's
   * thumbnails has started loading
   * @param thumbnailIndex the index of the thumbnail that has started
   * loading
   */
  protected void processThumbnailStarted(int imageIndex, int thumbnailIndex)
  {
    if (progressListeners != null)
      {
	Iterator it = progressListeners.iterator();

	while (it.hasNext())
	  {
	    IIOReadProgressListener listener =
	      (IIOReadProgressListener) it.next();
	    listener.thumbnailStarted(this, imageIndex, thumbnailIndex);
	  }
      }
  }

  /**
   * Notifies all installed read update listeners, by calling their
   * thumbnailUpdate methods, that the set of samples has changed.
   *
   * @param image the buffered image that is being updated
   * @param minX the X coordinate of the top-left pixel in this pass
   * @param minY the Y coordinate of the top-left pixel in this pass
   * @param width the total width of the rectangle covered by this
   * pass, including skipped pixels
   * @param height the total height of the rectangle covered by this
   * pass, including skipped pixels
   * @param periodX the horizontal sample interval
   * @param periodY the vertical sample interval
   * @param bands the affected bands in the destination
   */
  protected void processThumbnailUpdate(BufferedImage image, int minX, int minY,
					int width, int height, int periodX,
					int periodY, int[] bands)
  {
    if (updateListeners != null)
      {
	Iterator it = updateListeners.iterator();

	while (it.hasNext())
	  {
	    IIOReadUpdateListener listener = (IIOReadUpdateListener) it.next();
	    listener.thumbnailUpdate(this, image, minX, minY, width, height,
				     periodX, periodY, bands);
	  }
      }
  }

  /**
   * Notifies all installed warning listeners, by calling their
   * warningOccurred methods, that a warning message has been raised.
   *
   * @param warning the warning message
   *
   * @exception IllegalArgumentException if warning is null
   */
  protected void processWarningOccurred(String warning)
  {
    if (warning == null)
      throw new IllegalArgumentException ("null argument");
    if (warningListeners != null)
      {
	Iterator it = warningListeners.iterator();

	while (it.hasNext())
	  {
	    IIOReadWarningListener listener =
	      (IIOReadWarningListener) it.next();
	    listener.warningOccurred(this, warning);
	  }
      }
  }

  /**
   * Notify all installed warning listeners, by calling their
   * warningOccurred methods, that a warning message has been raised.
   * The warning message is retrieved from a resource bundle, using
   * the given basename and keyword.
   *
   * @param baseName the basename of the resource from which to
   * retrieve the warning message
   * @param keyword the keyword used to retrieve the warning from the
   * resource bundle
   *
   * @exception IllegalArgumentException if either baseName or keyword
   * is null
   * @exception IllegalArgumentException if no resource bundle is
   * found using baseName
   * @exception IllegalArgumentException if the given keyword produces
   * no results from the resource bundle
   * @exception IllegalArgumentException if the retrieved object is
   * not a String
   */
  protected void processWarningOccurred(String baseName,
					String keyword)
  {
    if (baseName == null || keyword == null)
      throw new IllegalArgumentException ("null argument");

    ResourceBundle b = null;

    try
      {
	b = ResourceBundle.getBundle(baseName, getLocale());
      }
    catch (MissingResourceException e)
      {
	throw new IllegalArgumentException ("no resource bundle found");
      }

    Object str = null;

    try
      {
	str = b.getObject(keyword);
      }
    catch (MissingResourceException e)
      {
	throw new IllegalArgumentException ("no results found for keyword");
      }

    if (! (str instanceof String))
      throw new IllegalArgumentException ("retrieved object not a String");

    String warning = (String) str;

    if (warningListeners != null)
      {
	Iterator it = warningListeners.iterator();

	while (it.hasNext())
	  {
	    IIOReadWarningListener listener =
	      (IIOReadWarningListener) it.next();
	    listener.warningOccurred(this, warning);
	  }
      }
  }

  /**
   * Read the given frame into a buffered image using the given read
   * parameters.  Listeners will be notified of image loading progress
   * and warnings.
   *
   * @param imageIndex the index of the frame to read
   * @param param the image read parameters to use when reading
   *
   * @return a buffered image
   *
   * @exception IllegalStateException if input is null
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds
   * @exception IOException if a read error occurs
   */
  public abstract BufferedImage read(int imageIndex, ImageReadParam param)
    throws IOException;

  /**
   * Check if this reader supports reading thumbnails.
   *
   * @return true if this reader supports reading thumbnails, false
   * otherwise
   */
  public boolean readerSupportsThumbnails()
  {
    return false;
  }

  /**
   * Read raw raster data.  The image type specifier in param is
   * ignored but all other parameters are used.  Offset parameters are
   * translated into the raster's coordinate space.  This method may
   * be implemented by image readers that want to provide direct
   * access to raw image data.
   *
   * @param imageIndex the frame index
   * @param param the image read parameters
   *
   * @return a raster containing the read image data
   *
   * @exception UnsupportedOperationException if this reader doesn't
   * support rasters
   * @exception IllegalStateException if input is null
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds
   * @exception IOException if a read error occurs
   */
  public Raster readRaster(int imageIndex, ImageReadParam param)
    throws IOException
  {
    throw new UnsupportedOperationException();
  }

  /**
   * Read a thumbnail.
   *
   * @param imageIndex the frame index
   * @param thumbnailIndex the thumbnail index
   *
   * @return a buffered image of the thumbnail
   *
   * @exception UnsupportedOperationException if this reader doesn't
   * support thumbnails
   * @exception IllegalStateException if input is null
   * @exception IndexOutOfBoundsException if either the frame index or
   * the thumbnail index is out-of-bounds
   * @exception IOException if a read error occurs
   * 
   */
  public BufferedImage readThumbnail(int imageIndex, int thumbnailIndex)
    throws IOException
  {
    throw new UnsupportedOperationException();
  }

  /**
   * Uninstall all read progress listeners.
   */
  public void removeAllIIOReadProgressListeners()
  {
    progressListeners = null;
  }

  /**
   * Uninstall all read update listeners.
   */
  public void removeAllIIOReadUpdateListeners()
  {
    updateListeners = null;
  }

  /**
   * Uninstall all read warning listeners.
   */
  public void removeAllIIOReadWarningListeners()
  {
    warningListeners = null;
  }

  /**
   * Uninstall the given read progress listener.
   *
   * @param listener the listener to remove
   */
  public void removeIIOReadProgressListener(IIOReadProgressListener listener) 
  {
    if (listener == null)
      return;
    if (progressListeners != null)
      {
	progressListeners.remove(listener);
      }
  }

  /**
   * Uninstall the given read update listener.
   *
   * @param listener the listener to remove
   */
  public void removeIIOReadUpdateListener(IIOReadUpdateListener listener) 
  {
    if (listener == null)
      return;

    if (updateListeners != null)
      {
	updateListeners.remove(listener);
      }
  }

  /**
   * Uninstall the given read warning listener.
   *
   * @param listener the listener to remove
   */
  public void removeIIOReadWarningListener(IIOReadWarningListener listener)
  {
    if (listener == null)
      return;
    if (warningListeners != null)
      {
	warningListeners.remove(listener);
      }
  }

  /**
   * Set the current locale or use the default locale.
   *
   * @param locale the locale to set, or null
   */
  public void setLocale(Locale locale)
  {
    if (locale != null)
      {
	// Check if its a valid locale.
	boolean found = false;

	if (availableLocales != null)
	  for (int i = availableLocales.length - 1; i >= 0; --i)
	    if (availableLocales[i].equals(locale))
	      found = true;

	if (! found)
	  throw new IllegalArgumentException("looale not available");
      }

    this.locale = locale;
  }

  /**
   * Check that the given read parameters have valid source and
   * destination band settings.  If the param.getSourceBands() returns
   * null, the array is assumed to include all band indices, 0 to
   * numSrcBands - 1; likewise if param.getDestinationBands() returns
   * null, it is assumed to be an array containing indices 0 to
   * numDstBands - 1.  A failure will cause this method to throw
   * IllegalArgumentException.
   *
   * @param param the image parameters to check
   * @param numSrcBands the number of input source bands
   * @param numDstBands the number of ouput destination bands
   *
   * @exception IllegalArgumentException if either the given source or
   * destination band indices are invalid
   */
  protected static void checkReadParamBandSettings(ImageReadParam param,
						   int numSrcBands,
						   int numDstBands)
  {
    int[] srcBands = param.getSourceBands();
    int[] dstBands = param.getDestinationBands();
    boolean lengthsDiffer = false;
    boolean srcOOB = false;
    boolean dstOOB = false;

    if (srcBands == null)
      {
        if (dstBands == null)
          {
            if (numSrcBands != numDstBands)
              lengthsDiffer = true;
          }
        else
          {
            if (numSrcBands != dstBands.length)
              lengthsDiffer = true;

            for (int i = 0; i < dstBands.length; i++)
              if (dstBands[i] > numSrcBands - 1)
                {
                  dstOOB = true;
                  break;
                }
          }
      }
    else
      {
        if (dstBands == null)
          {
            if (srcBands.length != numDstBands)
              lengthsDiffer = true;

            for (int i = 0; i < srcBands.length; i++)
              if (srcBands[i] > numDstBands - 1)
                {
                  srcOOB = true;
                  break;
                }
          }
        else
          {
            if (srcBands.length != dstBands.length)
              lengthsDiffer = true;

            for (int i = 0; i < srcBands.length; i++)
              if (srcBands[i] > numDstBands - 1)
                {
                  srcOOB = true;
                  break;
                }

            for (int i = 0; i < dstBands.length; i++)
              if (dstBands[i] > numSrcBands - 1)
                {
                  dstOOB = true;
                  break;
                }
          }
      }

    if (lengthsDiffer)
      throw new IllegalArgumentException ("array lengths differ");

    if (srcOOB)
      throw new IllegalArgumentException ("source band index"
                                          + " out-of-bounds");

    if (dstOOB)
      throw new IllegalArgumentException ("destination band index"
                                          + " out-of-bounds");
  }

  /**
   * Calcluate the source and destination regions that will be read
   * from and written to, given image parameters and/or a destination
   * buffered image.  The source region will be clipped if any of its
   * bounds are outside the destination region.  Clipping will account
   * for subsampling and destination offsets.  Likewise, the
   * destination region is clipped to the given destination image, if
   * it is not null, using the given image parameters, if they are not
   * null.  IllegalArgumentException is thrown if either region will
   * contain 0 pixels after clipping.
   *
   * @param image read parameters, or null
   * @param srcWidth the width of the source image
   * @param srcHeight the height of the source image
   * @param image the destination image, or null
   * @param srcRegion a rectangle whose values will be set to the
   * clipped source region
   * @param destRegion a rectangle whose values will be set to the
   * clipped destination region
   *
   * @exception IllegalArgumentException if either srcRegion or
   * destRegion is null
   * @exception IllegalArgumentException if either of the calculated
   * regions is empty
   */
  protected static void computeRegions (ImageReadParam param,
					int srcWidth,
					int srcHeight,
					BufferedImage image,
					Rectangle srcRegion,
					Rectangle destRegion)
  {
    if (srcRegion == null || destRegion == null)
      throw new IllegalArgumentException ("null region");

    if (srcWidth == 0 || srcHeight == 0)
      throw new IllegalArgumentException ("zero-sized region");

    srcRegion = getSourceRegion(param, srcWidth, srcHeight);
    if (image != null)
      destRegion = new Rectangle (0, 0, image.getWidth(), image.getHeight());
    else
      destRegion = new Rectangle (0, 0, srcWidth, srcHeight);

    if (param != null)
      {
        Point offset = param.getDestinationOffset();

        if (offset.x < 0)
          {
            srcRegion.x -= offset.x;
            srcRegion.width += offset.x;
          }
        if (offset.y < 0)
          {
            srcRegion.y -= offset.y;
            srcRegion.height += offset.y;
          }

        srcRegion.width = srcRegion.width > destRegion.width
          ? destRegion.width : srcRegion.width;
        srcRegion.height = srcRegion.height > destRegion.height
          ? destRegion.height : srcRegion.height;

        if (offset.x >= 0)
          {
            destRegion.x += offset.x;
            destRegion.width -= offset.x;
          }
        if (offset.y >= 0)
          {
            destRegion.y += offset.y;
            destRegion.height -= offset.y;
          }
      }

    if (srcRegion.isEmpty() || destRegion.isEmpty())
      throw new IllegalArgumentException ("zero-sized region");
  }

  /**
   * Return a suitable destination buffered image.  If
   * param.getDestination() is non-null, then it is returned,
   * otherwise a buffered image is created using
   * param.getDestinationType() if it is non-null and also in the
   * given imageTypes collection, or the first element of imageTypes
   * otherwise.
   *
   * @param param image read parameters from which a destination image
   * or image type is retrieved, or null
   * @param imageTypes a collection of legal image types
   * @param width the width of the source image
   * @param height the height of the source image
   *
   * @return a suitable destination buffered image
   *
   * @exception IIOException if param.getDestinationType() does not
   * return an image type in imageTypes
   * @exception IllegalArgumentException if imageTypes is null or
   * empty, or if a non-ImageTypeSpecifier object is retrieved from
   * imageTypes
   * @exception IllegalArgumentException if the resulting destination
   * region is empty
   * @exception IllegalArgumentException if the product of width and
   * height is greater than Integer.MAX_VALUE
   */
  protected static BufferedImage getDestination (ImageReadParam param,
						 Iterator<ImageTypeSpecifier> imageTypes,
						 int width,
						 int height)
    throws IIOException
  {
    if (imageTypes == null || !imageTypes.hasNext())
      throw new IllegalArgumentException ("imageTypes null or empty");

    if (width < 0 || height < 0)
      throw new IllegalArgumentException ("negative dimension");

    // test for overflow
    if (width * height < Math.min (width, height))
      throw new IllegalArgumentException ("width * height > Integer.MAX_VALUE");

    BufferedImage dest = null;
    ImageTypeSpecifier destType = null;

    if (param != null)
      {
        dest = param.getDestination ();
        if (dest == null)
          {
            ImageTypeSpecifier type = param.getDestinationType();
            if (type != null)
              {
                Iterator it = imageTypes;

                while (it.hasNext())
                  {
                    Object o = it.next ();
                    if (! (o instanceof ImageTypeSpecifier))
                      throw new IllegalArgumentException ("non-ImageTypeSpecifier object");

                    ImageTypeSpecifier t = (ImageTypeSpecifier) o;
                    if (t.equals (type))
                      {
                        dest = t.createBufferedImage (width, height);
                        break;
                      }
                    if (destType == null)
                      throw new IIOException ("invalid destination type");

                  }
              }
          }
      }
    if (dest == null)
      {
        Rectangle srcRegion = new Rectangle ();
        Rectangle destRegion = new Rectangle ();

        computeRegions (param, width, height, null, srcRegion, destRegion);

        if (destRegion.isEmpty())
          throw new IllegalArgumentException ("destination region empty");

        if (destType == null)
          {
            Object o = imageTypes.next();
            if (! (o instanceof ImageTypeSpecifier))
              throw new IllegalArgumentException ("non-ImageTypeSpecifier"
                                                  + " object");

            dest = ((ImageTypeSpecifier) o).createBufferedImage
              (destRegion.width, destRegion.height);
          }
        else
          dest = destType.createBufferedImage
            (destRegion.width, destRegion.height);
      }
    return dest;
  }

  /**
   * Get the metadata associated with this image.  If the reader is
   * set to ignore metadata or does not support reading metadata, or
   * if no metadata is available then null is returned.
   *
   * This more specific version of getImageMetadata(int) can be used
   * to restrict metadata retrieval to specific formats and node
   * names, which can limit the amount of data that needs to be
   * processed.
   *
   * @param imageIndex the frame index
   * @param formatName the format of metadata requested
   * @param nodeNames a set of Strings specifiying node names to be
   * retrieved
   *
   * @return a metadata object, or null
   *
   * @exception IllegalStateException if input has not been set
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds
   * @exception IllegalArgumentException if formatName is null
   * @exception IllegalArgumentException if nodeNames is null
   * @exception IOException if a read error occurs
   */
  public IIOMetadata getImageMetadata (int imageIndex,
                                       String formatName,
                                       Set<String> nodeNames)
    throws IOException
  {
    if (formatName == null || nodeNames == null)
      throw new IllegalArgumentException ("null argument");

    return getImageMetadata (imageIndex);
  }

  /**
   * Get the index at which the next image will be read.  If
   * seekForwardOnly is true then the returned value will increase
   * monotonically each time an image frame is read.  If
   * seekForwardOnly is false then the returned value will always be
   * 0.
   *
   * @return the current frame index
   */
  public int getMinIndex()
  {
    return minIndex;
  }

  /**
   * Get the image type specifier that most closely represents the
   * internal data representation used by this reader.  This value
   * should be included in the return value of getImageTypes.
   *
   * @param imageIndex the frame index
   *
   * @return an image type specifier
   *
   * @exception IllegalStateException if input has not been set
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds
   * @exception IOException if a read error occurs
   */
  public ImageTypeSpecifier getRawImageType (int imageIndex)
    throws IOException
  {
    return (ImageTypeSpecifier) getImageTypes(imageIndex).next();
  }

  /**
   * Calculate a source region based on the given source image
   * dimensions and parameters.  Subsampling offsets and a source
   * region are taken from the given image read parameters and used to
   * clip the given image dimensions, returning a new rectangular
   * region as a result.
   *
   * @param param image parameters, or null
   * @param srcWidth the width of the source image
   * @param srcHeight the height of the source image
   *
   * @return a clipped rectangle
   */
  protected static Rectangle getSourceRegion (ImageReadParam param,
					      int srcWidth,
					      int srcHeight)
  {
    Rectangle clippedRegion = new Rectangle (0, 0, srcWidth, srcHeight);

    if (param != null)
      {
        Rectangle srcRegion = param.getSourceRegion();

        if (srcRegion != null)
          {
            clippedRegion.x = srcRegion.x > clippedRegion.x
              ? srcRegion.x : clippedRegion.x;
            clippedRegion.y = srcRegion.y > clippedRegion.y
              ? srcRegion.y : clippedRegion.y;
            clippedRegion.width = srcRegion.width > clippedRegion.width
              ? srcRegion.width : clippedRegion.width;
            clippedRegion.height = srcRegion.height > clippedRegion.height
              ? srcRegion.height : clippedRegion.height;
          }

        int xOffset = param.getSubsamplingXOffset();

        clippedRegion.x += xOffset;
        clippedRegion.width -= xOffset;

        int yOffset = param.getSubsamplingYOffset();

        clippedRegion.y += yOffset;
        clippedRegion.height -= yOffset;
      }
    return clippedRegion;
  }

  /**
   * Get the metadata associated with the image being read.  If the
   * reader is set to ignore metadata or does not support reading
   * metadata, or if no metadata is available then null is returned.
   * This method returns metadata associated with the entirety of the
   * image data, whereas getStreamMetadata() returns metadata
   * associated with a frame within a multi-image data stream.
   *
   * This more specific version of getStreamMetadata() can be used to
   * restrict metadata retrieval to specific formats and node names,
   * which can limit the amount of data that needs to be processed.
   *
   * @param formatName the format of metadata requested
   * @param nodeNames a set of Strings specifiying node names to be
   * retrieved
   *
   * @return metadata associated with the image being read, or null
   *
   * @exception IllegalArgumentException if formatName is null
   * @exception IllegalArgumentException if nodeNames is null
   * @exception IOException if a read error occurs
   */
  public IIOMetadata getStreamMetadata (String formatName,
                                        Set<String> nodeNames)
    throws IOException
  {
    if (formatName == null || nodeNames == null)
      throw new IllegalArgumentException ("null argument");

    return getStreamMetadata();
  }

  /**
   * Read the given frame all at once, using default image read
   * parameters, and return a buffered image.
   *
   * The returned image will be formatted according to the
   * currently-preferred image type specifier.
   *
   * Installed read progress listeners, update progress listeners and
   * warning listeners will be notified of read progress, changes in
   * sample sets and warnings respectively.
   *
   * @param the index of the image frame to read
   *
   * @return a buffered image
   *
   * @exception IllegalStateException if input has not been set
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds
   * @exception IOException if a read error occurs
   */
  public BufferedImage read (int imageIndex)
    throws IOException
  {
    return read (imageIndex, null);
  }

  /**
   * Read the given frame all at once, using the given image read
   * parameters, and return an IIOImage.  The IIOImage will contain a
   * buffered image as returned by getDestination.
   *
   * Installed read progress listeners, update progress listeners and
   * warning listeners will be notified of read progress, changes in
   * sample sets and warnings respectively.
   *
   * The source and destination band settings are checked with a call
   * to checkReadParamBandSettings.
   *
   * @param the index of the image frame to read
   * @param the image read parameters
   *
   * @return an IIOImage
   *
   * @exception IllegalStateException if input has not been set
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds
   * @exception IllegalArgumentException if param.getSourceBands() and
   * param.getDestinationBands() are incompatible
   * @exception IllegalArgumentException if either the source or
   * destination image regions are empty
   * @exception IOException if a read error occurs
   */
  public IIOImage readAll (int imageIndex,
			   ImageReadParam param)
    throws IOException
  {
    checkReadParamBandSettings (param,
                                param.getSourceBands().length,
                                param.getDestinationBands().length);

    List l = new ArrayList ();

    for (int i = 0; i < getNumThumbnails (imageIndex); i++)
      l.add (readThumbnail(imageIndex, i));

    return new IIOImage (getDestination(param, getImageTypes(imageIndex),
                                        getWidth(imageIndex),
                                        getHeight(imageIndex)),
                         l,
                         getImageMetadata (imageIndex));
  }

  /**
   * Read all image frames all at once, using the given image read
   * parameters iterator, and return an iterator over a collection of
   * IIOImages.  Each IIOImage in the collection will contain a
   * buffered image as returned by getDestination.
   *
   * Installed read progress listeners, update progress listeners and
   * warning listeners will be notified of read progress, changes in
   * sample sets and warnings respectively.
   *
   * Each set of source and destination band settings are checked with
   * a call to checkReadParamBandSettings.
   *
   * @param an iterator over the image read parameters
   *
   * @return an IIOImage
   *
   * @exception IllegalStateException if input has not been set
   * @exception IllegalArgumentException if a non-ImageReadParam is
   * found in params
   * @exception IllegalArgumentException if param.getSourceBands() and
   * param.getDestinationBands() are incompatible
   * @exception IllegalArgumentException if either the source or
   * destination image regions are empty
   * @exception IOException if a read error occurs
   */
  public Iterator<IIOImage> readAll (Iterator<? extends ImageReadParam> params)
    throws IOException
  {
    List l = new ArrayList ();
    int index = 0;

    while (params.hasNext())
      {
        if (params != null && ! (params instanceof ImageReadParam))
          throw new IllegalArgumentException ("non-ImageReadParam found");

        l.add (readAll(index++, (ImageReadParam) params.next ()));
      }

    return l.iterator();
  }

  /**
   * Read a rendered image.  This is a more general counterpart to
   * read (int, ImageReadParam).  All image data may not be read
   * before this method returns and so listeners will not necessarily
   * be notified.
   *
   * @param the index of the image frame to read
   * @param the image read parameters
   *
   * @return a rendered image
   *
   * @exception IllegalStateException if input is null
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds
   * @exception IllegalArgumentException if param.getSourceBands() and
   * param.getDestinationBands() are incompatible
   * @exception IllegalArgumentException if either the source or
   * destination image regions are empty
   * @exception IOException if a read error occurs
   */
  public RenderedImage readAsRenderedImage (int imageIndex,
					    ImageReadParam param)
    throws IOException
  {
    return read (imageIndex, param);
  }

  /**
   * Read the given tile into a buffered image.  If the tile
   * coordinates are out-of-bounds an exception is thrown.  If the
   * image is not tiled then the coordinates 0, 0 are expected and the
   * entire image will be read.
   *
   * @param imageIndex the frame index
   * @param tileX the horizontal tile coordinate
   * @param tileY the vertical tile coordinate
   *
   * @return the contents of the tile as a buffered image
   *
   * @exception IllegalStateException if input is null
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds
   * @exception IllegalArgumentException if the tile coordinates are
   * out-of-bounds
   * @exception IOException if a read error occurs
   */
  public BufferedImage readTile (int imageIndex, int tileX, int tileY)
    throws IOException
  {
    if (tileX != 0 || tileY != 0)
      throw new IllegalArgumentException ("tileX not 0 or tileY not 0");

    return read (imageIndex);
  }

  /**
   * Read the given tile into a raster containing the raw image data.
   * If the tile coordinates are out-of-bounds an exception is thrown.
   * If the image is not tiled then the coordinates 0, 0 are expected
   * and the entire image will be read.
   *
   * @param imageIndex the frame index
   * @param tileX the horizontal tile coordinate
   * @param tileY the vertical tile coordinate
   *
   * @return the contents of the tile as a raster
   *
   * @exception UnsupportedOperationException if rasters are not
   * supported
   * @exception IllegalStateException if input is null
   * @exception IndexOutOfBoundsException if the frame index is
   * out-of-bounds
   * @exception IllegalArgumentException if the tile coordinates are
   * out-of-bounds
   * @exception IOException if a read error occurs
   */
  public Raster readTileRaster (int imageIndex, int tileX, int tileY)
    throws IOException
  {
    if (!canReadRaster())
      throw new UnsupportedOperationException ("cannot read rasters");

    if (tileX != 0 || tileY != 0)
      throw new IllegalArgumentException ("tileX not 0 or tileY not 0");

    return readRaster (imageIndex, null);
  }

  /**
   * Reset this reader's internal state.
   */
  public void reset ()
  {
    setInput (null, false);
    setLocale (null);
    removeAllIIOReadUpdateListeners ();
    removeAllIIOReadWarningListeners ();
    removeAllIIOReadProgressListeners ();
    clearAbortRequest ();
  }
}

