Java/2D Graphics GUI/Image IO

Материал из Java эксперт
Версия от 18:01, 31 мая 2010; (обсуждение)
(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск

Содержание

Compress and save an image to the disk

   
/*
 * Copyright (c) JForum Team
 * All rights reserved.
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met:
 * 1) Redistributions of source code must retain the above 
 * copyright notice, this list of conditions and the 
 * following  disclaimer.
 * 2)  Redistributions in binary form must reproduce the 
 * above copyright notice, this list of conditions and 
 * the following disclaimer in the documentation and/or 
 * other materials provided with the distribution.
 * 3) Neither the name of "Rafael Steil" nor 
 * the names of its contributors may be used to endorse 
 * or promote products derived from this software without 
 * specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT 
 * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
 * IN CONTRACT, STRICT LIABILITY, OR TORT 
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
 * 
 * This file creation date: 21/04/2004 - 19:54:16
 * The JForum Project
 * http://www.jforum.net
 */
import java.awt.Dimension;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.PixelGrabber;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Locale;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.ImageOutputStream;

/**
 * Utilities methods for image manipulation. It does not support writting of GIF images, but it can
 * read from. GIF images will be saved as PNG.
 * 
 * @author Rafael Steil
 * @version $Id: ImageUtils.java,v 1.23 2007/09/09 01:05:22 rafaelsteil Exp $
 */
public class ImageUtils
{
  public static final int IMAGE_UNKNOWN = -1;
  public static final int IMAGE_JPEG = 0;
  public static final int IMAGE_PNG = 1;
  public static final int IMAGE_GIF = 2;
  /**
   * Resizes an image
   * 
   * @param imgName The image name to resize. Must be the complet path to the file
   * @param type int
   * @param maxWidth The image"s max width
   * @param maxHeight The image"s max height
   * @return A resized <code>BufferedImage</code>
   */
  public static BufferedImage resizeImage(String imgName, int type, int maxWidth, int maxHeight)
  {
    try {
      return resizeImage(ImageIO.read(new File(imgName)), type, maxWidth, maxHeight);
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }
  /**
   * Resizes an image.
   * 
   * @param image
   *            The image to resize
   * @param maxWidth
   *            The image"s max width
   * @param maxHeight
   *            The image"s max height
   * @return A resized <code>BufferedImage</code>
   * @param type
   *            int
   */
  public static BufferedImage resizeImage(BufferedImage image, int type, int maxWidth, int maxHeight)
  {
    Dimension largestDimension = new Dimension(maxWidth, maxHeight);
    // Original size
    int imageWidth = image.getWidth(null);
    int imageHeight = image.getHeight(null);
    float aspectRatio = (float) imageWidth / imageHeight;
    if (imageWidth > maxWidth || imageHeight > maxHeight) {
      if ((float) largestDimension.width / largestDimension.height > aspectRatio) {
        largestDimension.width = (int) Math.ceil(largestDimension.height * aspectRatio);
      }
      else {
        largestDimension.height = (int) Math.ceil(largestDimension.width / aspectRatio);
      }
      imageWidth = largestDimension.width;
      imageHeight = largestDimension.height;
    }
    return createHeadlessSmoothBufferedImage(image, type, imageWidth, imageHeight);
  }
  /**
   * Saves an image to the disk.
   * 
   * @param image  The image to save
   * @param toFileName The filename to use
   * @param type The image type. Use <code>ImageUtils.IMAGE_JPEG</code> to save as JPEG images,
   *  or <code>ImageUtils.IMAGE_PNG</code> to save as PNG.
   * @return <code>false</code> if no appropriate writer is found
   */
  public static boolean saveImage(BufferedImage image, String toFileName, int type)
  {
    try {
      return ImageIO.write(image, type == IMAGE_JPEG ? "jpg" : "png", new File(toFileName));
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }
  /**
   * Compress and save an image to the disk. Currently this method only supports JPEG images.
   * 
   * @param image The image to save
   * @param toFileName The filename to use
   * @param type The image type. Use <code>ImageUtils.IMAGE_JPEG</code> to save as JPEG images,
   * or <code>ImageUtils.IMAGE_PNG</code> to save as PNG.
   */
  public static void saveCompressedImage(BufferedImage image, String toFileName, int type)
  {
    try {
      if (type == IMAGE_PNG) {
        throw new UnsupportedOperationException("PNG compression not implemented");
      }
      Iterator iter = ImageIO.getImageWritersByFormatName("jpg");
      ImageWriter writer;
      writer = (ImageWriter) iter.next();
      ImageOutputStream ios = ImageIO.createImageOutputStream(new File(toFileName));
      writer.setOutput(ios);
      ImageWriteParam iwparam = new JPEGImageWriteParam(Locale.getDefault());
      iwparam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
      iwparam.setCompressionQuality(0.7F);
      writer.write(null, new IIOImage(image, null, null), iwparam);
      ios.flush();
      writer.dispose();
      ios.close();
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }
  /**
   * Creates a <code>BufferedImage</code> from an <code>Image</code>. This method can
   * function on a completely headless system. This especially includes Linux and Unix systems
   * that do not have the X11 libraries installed, which are required for the AWT subsystem to
   * operate. This method uses nearest neighbor approximation, so it"s quite fast. Unfortunately,
   * the result is nowhere near as nice looking as the createHeadlessSmoothBufferedImage method.
   * 
   * @param image  The image to convert
   * @param w The desired image width
   * @param h The desired image height
   * @return The converted image
   * @param type int
   */
  public static BufferedImage createHeadlessBufferedImage(BufferedImage image, int type, int width, int height)
  {
    if (type == ImageUtils.IMAGE_PNG && hasAlpha(image)) {
      type = BufferedImage.TYPE_INT_ARGB;
    }
    else {
      type = BufferedImage.TYPE_INT_RGB;
    }
    BufferedImage bi = new BufferedImage(width, height, type);
    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
        bi.setRGB(x, y, image.getRGB(x * image.getWidth() / width, y * image.getHeight() / height));
      }
    }
    return bi;
  }
  /**
   * Creates a <code>BufferedImage</code> from an <code>Image</code>. This method can
   * function on a completely headless system. This especially includes Linux and Unix systems
   * that do not have the X11 libraries installed, which are required for the AWT subsystem to
   * operate. The resulting image will be smoothly scaled using bilinear filtering.
   * 
   * @param source The image to convert
   * @param w The desired image width
   * @param h The desired image height
   * @return The converted image
   * @param type  int
   */
  public static BufferedImage createHeadlessSmoothBufferedImage(BufferedImage source, int type, int width, int height)
  {
    if (type == ImageUtils.IMAGE_PNG && hasAlpha(source)) {
      type = BufferedImage.TYPE_INT_ARGB;
    }
    else {
      type = BufferedImage.TYPE_INT_RGB;
    }
    BufferedImage dest = new BufferedImage(width, height, type);
    int sourcex;
    int sourcey;
    double scalex = (double) width / source.getWidth();
    double scaley = (double) height / source.getHeight();
    int x1;
    int y1;
    double xdiff;
    double ydiff;
    int rgb;
    int rgb1;
    int rgb2;
    for (int y = 0; y < height; y++) {
      sourcey = y * source.getHeight() / dest.getHeight();
      ydiff = scale(y, scaley) - sourcey;
      for (int x = 0; x < width; x++) {
        sourcex = x * source.getWidth() / dest.getWidth();
        xdiff = scale(x, scalex) - sourcex;
        x1 = Math.min(source.getWidth() - 1, sourcex + 1);
        y1 = Math.min(source.getHeight() - 1, sourcey + 1);
        rgb1 = getRGBInterpolation(source.getRGB(sourcex, sourcey), source.getRGB(x1, sourcey), xdiff);
        rgb2 = getRGBInterpolation(source.getRGB(sourcex, y1), source.getRGB(x1, y1), xdiff);
        rgb = getRGBInterpolation(rgb1, rgb2, ydiff);
        dest.setRGB(x, y, rgb);
      }
    }
    return dest;
  }
  private static double scale(int point, double scale)
  {
    return point / scale;
  }
  private static int getRGBInterpolation(int value1, int value2, double distance)
  {
    int alpha1 = (value1 & 0xFF000000) >>> 24;
    int red1 = (value1 & 0x00FF0000) >> 16;
    int green1 = (value1 & 0x0000FF00) >> 8;
    int blue1 = (value1 & 0x000000FF);
    int alpha2 = (value2 & 0xFF000000) >>> 24;
    int red2 = (value2 & 0x00FF0000) >> 16;
    int green2 = (value2 & 0x0000FF00) >> 8;
    int blue2 = (value2 & 0x000000FF);
    int rgb = ((int) (alpha1 * (1.0 - distance) + alpha2 * distance) << 24)
      | ((int) (red1 * (1.0 - distance) + red2 * distance) << 16)
      | ((int) (green1 * (1.0 - distance) + green2 * distance) << 8)
      | (int) (blue1 * (1.0 - distance) + blue2 * distance);
    return rgb;
  }
  /**
   * Determines if the image has transparent pixels.
   * 
   * @param image The image to check for transparent pixel.s
   * @return <code>true</code> of <code>false</code>, according to the result
   */
  public static boolean hasAlpha(Image image)
  {
    try {
      PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
      pg.grabPixels();
      return pg.getColorModel().hasAlpha();
    }
    catch (InterruptedException e) {
      return false;
    }
  }
}



Creates a new raster copy

   
/*
   Copyright 2001-2004  The Apache Software Foundation 
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
       http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
import java.awt.ruposite;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ruponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferShort;
import java.awt.image.DataBufferUShort;
import java.awt.image.DirectColorModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;
import java.awt.image.renderable.RenderContext;
import java.awt.image.renderable.RenderableImage;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
/*
import org.apache.batik.ext.awt.RenderingHintsKeyExt;
import org.apache.batik.ext.awt.image.renderable.PaintRable;
import org.apache.batik.ext.awt.image.rendered.AffineRed;
import org.apache.batik.ext.awt.image.rendered.Any2LsRGBRed;
import org.apache.batik.ext.awt.image.rendered.Any2sRGBRed;
import org.apache.batik.ext.awt.image.rendered.BufferedImageCachableRed;
import org.apache.batik.ext.awt.image.rendered.CachableRed;
import org.apache.batik.ext.awt.image.rendered.FormatRed;
import org.apache.batik.ext.awt.image.rendered.RenderedImageCachableRed;
import org.apache.batik.ext.awt.image.rendered.TranslateRed;
*/
/**
 * Set of utility methods for Graphics.
 * These generally bypass broken methods in Java2D or provide tweaked
 * implementations.
 *
 * @author 
 * @version $Id: GraphicsUtil.java,v 1.36 2005/03/27 08:58:32 cam Exp $
 */
public class GraphicsUtil {
    public static AffineTransform IDENTITY = new AffineTransform();
    /**
     * Standard prebuilt Linear_sRGB color model with no alpha. 
     */
    public final static ColorModel Linear_sRGB =
        new DirectColorModel(ColorSpace.getInstance
                             (ColorSpace.CS_LINEAR_RGB), 24,
                             0x00FF0000, 0x0000FF00,
                             0x000000FF, 0x0, false,
                             DataBuffer.TYPE_INT);
    /**
     * Standard prebuilt Linear_sRGB color model with premultiplied alpha.
     */
    public final static ColorModel Linear_sRGB_Pre =
        new DirectColorModel(ColorSpace.getInstance
                             (ColorSpace.CS_LINEAR_RGB), 32,
                             0x00FF0000, 0x0000FF00,
                             0x000000FF, 0xFF000000, true,
                             DataBuffer.TYPE_INT);
    /**
     * Standard prebuilt Linear_sRGB color model with unpremultiplied alpha.
     */
    public final static ColorModel Linear_sRGB_Unpre =
        new DirectColorModel(ColorSpace.getInstance
                             (ColorSpace.CS_LINEAR_RGB), 32,
                             0x00FF0000, 0x0000FF00,
                             0x000000FF, 0xFF000000, false,
                             DataBuffer.TYPE_INT);
    /**
     * Standard prebuilt sRGB color model with no alpha.
     */
    public final static ColorModel sRGB =
        new DirectColorModel(ColorSpace.getInstance
                             (ColorSpace.CS_sRGB), 24,
                             0x00FF0000, 0x0000FF00,
                             0x000000FF, 0x0, false,
                             DataBuffer.TYPE_INT);
    /**
     * Standard prebuilt sRGB color model with premultiplied alpha.
     */
    public final static ColorModel sRGB_Pre =
        new DirectColorModel(ColorSpace.getInstance
                             (ColorSpace.CS_sRGB), 32,
                             0x00FF0000, 0x0000FF00,
                             0x000000FF, 0xFF000000, true,
                             DataBuffer.TYPE_INT);
    /**
     * Standard prebuilt sRGB color model with unpremultiplied alpha.
     */
    public final static ColorModel sRGB_Unpre =
        new DirectColorModel(ColorSpace.getInstance
                             (ColorSpace.CS_sRGB), 32,
                             0x00FF0000, 0x0000FF00,
                             0x000000FF, 0xFF000000, false,
                             DataBuffer.TYPE_INT);
    /**
     * Method that returns either Linear_sRGB_Pre or Linear_sRGB_UnPre
     * based on premult flag.
     * @param premult True if the ColorModel should have premultiplied alpha.
     * @return        a ColorMdoel with Linear sRGB colorSpace and
     *                the alpha channel set in accordance with
     *                <tt>premult</tt>
     */
    public static ColorModel makeLinear_sRGBCM(boolean premult) {
        if (premult)
            return Linear_sRGB_Pre;
        return Linear_sRGB_Unpre;
    }
    /**
     * Constructs a BufferedImage with a linear sRGB colorModel, and alpha.
     * @param width   The desired width of the BufferedImage
     * @param height  The desired height of the BufferedImage
     * @param premult The desired state of alpha premultiplied
     * @return        The requested BufferedImage.
     */
    public static BufferedImage makeLinearBufferedImage(int width,
                                                        int height,
                                                        boolean premult) {
        ColorModel cm = makeLinear_sRGBCM(premult);
        WritableRaster wr = cm.createCompatibleWritableRaster(width, height);
        return new BufferedImage(cm, wr, premult, null);
    }

    /**
     * Creates a new raster that has a <b>copy</b> of the data in
     * <tt>ras</tt>.  This is highly optimized for speed.  There is
     * no provision for changing any aspect of the SampleModel.
     *
     * This method should be used when you need to change the contents
     * of a Raster that you do not "own" (ie the result of a
     * <tt>getData</tt> call).
     * @param ras The Raster to copy.
     * @return    A writable copy of <tt>ras</tt>
     */
    public static WritableRaster copyRaster(Raster ras) {
        return copyRaster(ras, ras.getMinX(), ras.getMinY());
    }

    /**
     * Creates a new raster that has a <b>copy</b> of the data in
     * <tt>ras</tt>.  This is highly optimized for speed.  There is
     * no provision for changing any aspect of the SampleModel.
     * However you can specify a new location for the returned raster.
     *
     * This method should be used when you need to change the contents
     * of a Raster that you do not "own" (ie the result of a
     * <tt>getData</tt> call).
     *
     * @param ras The Raster to copy.
     *
     * @param minX The x location for the upper left corner of the
     *             returned WritableRaster.
     *
     * @param minY The y location for the upper left corner of the
     *             returned WritableRaster.
     *
     * @return    A writable copy of <tt>ras</tt>
     */
    public static WritableRaster copyRaster(Raster ras, int minX, int minY) {
        WritableRaster ret = Raster.createWritableRaster
            (ras.getSampleModel(),
             new Point(0,0));
        ret = ret.createWritableChild
            (ras.getMinX()-ras.getSampleModelTranslateX(),
             ras.getMinY()-ras.getSampleModelTranslateY(),
             ras.getWidth(), ras.getHeight(),
             minX, minY, null);
        // Use System.arraycopy to copy the data between the two...
        DataBuffer srcDB = ras.getDataBuffer();
        DataBuffer retDB = ret.getDataBuffer();
        if (srcDB.getDataType() != retDB.getDataType()) {
            throw new IllegalArgumentException
                ("New DataBuffer doesn"t match original");
        }
        int len   = srcDB.getSize();
        int banks = srcDB.getNumBanks();
        int [] offsets = srcDB.getOffsets();
        for (int b=0; b< banks; b++) {
            switch (srcDB.getDataType()) {
            case DataBuffer.TYPE_BYTE: {
                DataBufferByte srcDBT = (DataBufferByte)srcDB;
                DataBufferByte retDBT = (DataBufferByte)retDB;
                System.arraycopy(srcDBT.getData(b), offsets[b],
                                 retDBT.getData(b), offsets[b], len);
            }
            case DataBuffer.TYPE_INT: {
                DataBufferInt srcDBT = (DataBufferInt)srcDB;
                DataBufferInt retDBT = (DataBufferInt)retDB;
                System.arraycopy(srcDBT.getData(b), offsets[b],
                                 retDBT.getData(b), offsets[b], len);
            }
            case DataBuffer.TYPE_SHORT: {
                DataBufferShort srcDBT = (DataBufferShort)srcDB;
                DataBufferShort retDBT = (DataBufferShort)retDB;
                System.arraycopy(srcDBT.getData(b), offsets[b],
                                 retDBT.getData(b), offsets[b], len);
            }
            case DataBuffer.TYPE_USHORT: {
                DataBufferUShort srcDBT = (DataBufferUShort)srcDB;
                DataBufferUShort retDBT = (DataBufferUShort)retDB;
                System.arraycopy(srcDBT.getData(b), offsets[b],
                                 retDBT.getData(b), offsets[b], len);
            }
            }
        }
        return ret;
    }
    /**
     * Coerces <tt>ras</tt> to be writable.  The returned Raster continues to
     * reference the DataBuffer from ras, so modifications to the returned
     * WritableRaster will be seen in ras.<p>
     *
     * This method should only be used if you need a WritableRaster due to
     * an interface (such as to construct a BufferedImage), but have no
     * intention of modifying the contents of the returned Raster.  If
     * you have any doubt about other users of the data in <tt>ras</tt>,
     * use copyRaster (above).
     * @param ras The raster to make writable.
     * @return    A Writable version of ras (shares DataBuffer with
     *            <tt>ras</tt>).
     */
    public static WritableRaster makeRasterWritable(Raster ras) {
        return makeRasterWritable(ras, ras.getMinX(), ras.getMinY());
    }
    /**
     * Coerces <tt>ras</tt> to be writable.  The returned Raster continues to
     * reference the DataBuffer from ras, so modifications to the returned
     * WritableRaster will be seen in ras.<p>
     *
     * You can specify a new location for the returned WritableRaster, this
     * is especially useful for constructing BufferedImages which require
     * the Raster to be at (0,0).
     *
     * This method should only be used if you need a WritableRaster due to
     * an interface (such as to construct a BufferedImage), but have no
     * intention of modifying the contents of the returned Raster.  If
     * you have any doubt about other users of the data in <tt>ras</tt>,
     * use copyRaster (above).
     *
     * @param ras The raster to make writable.
     *
     * @param minX The x location for the upper left corner of the
     *             returned WritableRaster.
     *
     * @param minY The y location for the upper left corner of the
     *             returned WritableRaster.
     *
     * @return A Writable version of <tT>ras</tt> with it"s upper left
     *         hand coordinate set to minX, minY (shares it"s DataBuffer
     *         with <tt>ras</tt>).
     */
    public static WritableRaster makeRasterWritable(Raster ras,
                                                    int minX, int minY) {
        WritableRaster ret = Raster.createWritableRaster
            (ras.getSampleModel(),
             ras.getDataBuffer(),
             new Point(0,0));
        ret = ret.createWritableChild
            (ras.getMinX()-ras.getSampleModelTranslateX(),
             ras.getMinY()-ras.getSampleModelTranslateY(),
             ras.getWidth(), ras.getHeight(),
             minX, minY, null);
        return ret;
    }
    /**
     * Create a new ColorModel with it"s alpha premultiplied state matching
     * newAlphaPreMult.
     * @param cm The ColorModel to change the alpha premult state of.
     * @param newAlphaPreMult The new state of alpha premult.
     * @return   A new colorModel that has isAlphaPremultiplied()
     *           equal to newAlphaPreMult.
     */
    public static ColorModel
        coerceColorModel(ColorModel cm, boolean newAlphaPreMult) {
        if (cm.isAlphaPremultiplied() == newAlphaPreMult)
            return cm;
        // Easiest way to build proper colormodel for new Alpha state...
        // Eventually this should switch on known ColorModel types and
        // only fall back on this hack when the CM type is unknown.
        WritableRaster wr = cm.createCompatibleWritableRaster(1,1);
        return cm.coerceData(wr, newAlphaPreMult);
    }
    /**
     * Coerces data within a bufferedImage to match newAlphaPreMult,
     * Note that this can not change the colormodel of bi so you
     *
     * @param wr The raster to change the state of.
     * @param cm The colormodel currently associated with data in wr.
     * @param newAlphaPreMult The desired state of alpha Premult for raster.
     * @return A new colormodel that matches newAlphaPreMult.
     */
    public static ColorModel
        coerceData(WritableRaster wr, ColorModel cm, boolean newAlphaPreMult) {
        // System.out.println("CoerceData: " + cm.isAlphaPremultiplied() +
        //                    " Out: " + newAlphaPreMult);
        if (cm.hasAlpha()== false)
            // Nothing to do no alpha channel
            return cm;
        if (cm.isAlphaPremultiplied() == newAlphaPreMult)
            // nothing to do alpha state matches...
            return cm;
        // System.out.println("CoerceData: " + wr.getSampleModel());
        if (newAlphaPreMult) {
            multiplyAlpha(wr);
        } else {
            divideAlpha(wr);
        }
        return coerceColorModel(cm, newAlphaPreMult);
    }
    public static void multiplyAlpha(WritableRaster wr) {
        if (is_BYTE_COMP_Data(wr.getSampleModel()))
            mult_BYTE_COMP_Data(wr);
        else if (is_INT_PACK_Data(wr.getSampleModel(), true))
            mult_INT_PACK_Data(wr);
        else {
            int [] pixel = null;
            int    bands = wr.getNumBands();
            float  norm = 1f/255f;
            int x0, x1, y0, y1, a, b;
            float alpha;
            x0 = wr.getMinX();
            x1 = x0+wr.getWidth();
            y0 = wr.getMinY();
            y1 = y0+wr.getHeight();
            for (int y=y0; y<y1; y++)
                for (int x=x0; x<x1; x++) {
                    pixel = wr.getPixel(x,y,pixel);
                    a = pixel[bands-1];
                    if ((a >= 0) && (a < 255)) {
                        alpha = a*norm;
                        for (b=0; b<bands-1; b++)
                            pixel[b] = (int)(pixel[b]*alpha+0.5f);
                        wr.setPixel(x,y,pixel);
                    }
                }
        }
    }
    
    public static void divideAlpha(WritableRaster wr) {
        if (is_BYTE_COMP_Data(wr.getSampleModel()))
            divide_BYTE_COMP_Data(wr);
        else if (is_INT_PACK_Data(wr.getSampleModel(), true))
            divide_INT_PACK_Data(wr);
        else {
            int x0, x1, y0, y1, a, b;
            float ialpha;
            int    bands = wr.getNumBands();
            int [] pixel = null;
        
            x0 = wr.getMinX();
            x1 = x0+wr.getWidth();
            y0 = wr.getMinY();
            y1 = y0+wr.getHeight();
            for (int y=y0; y<y1; y++)
                for (int x=x0; x<x1; x++) {
                    pixel = wr.getPixel(x,y,pixel);
                    a = pixel[bands-1];
                    if ((a > 0) && (a < 255)) {
                        ialpha = 255/(float)a;
                        for (b=0; b<bands-1; b++)
                            pixel[b] = (int)(pixel[b]*ialpha+0.5f);
                        wr.setPixel(x,y,pixel);
                    }
                }
        }
    }
    public static boolean is_INT_PACK_Data(SampleModel sm,
                                           boolean requireAlpha) {
        // Check ColorModel is of type DirectColorModel
        if(!(sm instanceof SinglePixelPackedSampleModel)) return false;
        // Check transfer type
        if(sm.getDataType() != DataBuffer.TYPE_INT)       return false;
        SinglePixelPackedSampleModel sppsm;
        sppsm = (SinglePixelPackedSampleModel)sm;
        int [] masks = sppsm.getBitMasks();
        if (masks.length == 3) {
            if (requireAlpha) return false;
        } else if (masks.length != 4)
            return false;
        if(masks[0] != 0x00ff0000) return false;
        if(masks[1] != 0x0000ff00) return false;
        if(masks[2] != 0x000000ff) return false;
        if ((masks.length == 4) &&
            (masks[3] != 0xff000000)) return false;
        return true;
    }
        public static boolean is_BYTE_COMP_Data(SampleModel sm) {
            // Check ColorModel is of type DirectColorModel
            if(!(sm instanceof ComponentSampleModel))    return false;
            // Check transfer type
            if(sm.getDataType() != DataBuffer.TYPE_BYTE) return false;
            return true;
        }
    protected static void divide_INT_PACK_Data(WritableRaster wr) {
        // System.out.println("Divide Int");
        SinglePixelPackedSampleModel sppsm;
        sppsm = (SinglePixelPackedSampleModel)wr.getSampleModel();
        final int width = wr.getWidth();
        final int scanStride = sppsm.getScanlineStride();
        DataBufferInt db = (DataBufferInt)wr.getDataBuffer();
        final int base
            = (db.getOffset() +
               sppsm.getOffset(wr.getMinX()-wr.getSampleModelTranslateX(),
                               wr.getMinY()-wr.getSampleModelTranslateY()));
        int pixel, a, aFP;
        // Access the pixel data array
        final int pixels[] = db.getBankData()[0];
        for (int y=0; y<wr.getHeight(); y++) {
            int sp = base + y*scanStride;
            final int end = sp + width;
            while (sp < end) {
                pixel = pixels[sp];
                a = pixel>>>24;
                if (a<=0) {
                    pixels[sp] = 0x00FFFFFF;
                }
                else if (a<255) {
                    aFP = (0x00FF0000/a);
                    pixels[sp] =
                        ((a << 24) |
                         (((((pixel&0xFF0000)>>16)*aFP)&0xFF0000)    ) |
                         (((((pixel&0x00FF00)>>8) *aFP)&0xFF0000)>>8 ) |
                         (((((pixel&0x0000FF))    *aFP)&0xFF0000)>>16));
                }
                sp++;
            }
        }
    }
    protected static void mult_INT_PACK_Data(WritableRaster wr) {
        // System.out.println("Multiply Int: " + wr);
        SinglePixelPackedSampleModel sppsm;
        sppsm = (SinglePixelPackedSampleModel)wr.getSampleModel();
        final int width = wr.getWidth();
        final int scanStride = sppsm.getScanlineStride();
        DataBufferInt db = (DataBufferInt)wr.getDataBuffer();
        final int base
            = (db.getOffset() +
               sppsm.getOffset(wr.getMinX()-wr.getSampleModelTranslateX(),
                               wr.getMinY()-wr.getSampleModelTranslateY()));
        // Access the pixel data array
        final int pixels[] = db.getBankData()[0];
        for (int y=0; y<wr.getHeight(); y++) {
            int sp = base + y*scanStride;
            final int end = sp + width;
            while (sp < end) {
                int pixel = pixels[sp];
                int a = pixel>>>24;
                if ((a>=0) && (a<255)) {
                    pixels[sp] = ((a << 24) |
                                  ((((pixel&0xFF0000)*a)>>8)&0xFF0000) |
                                  ((((pixel&0x00FF00)*a)>>8)&0x00FF00) |
                                  ((((pixel&0x0000FF)*a)>>8)&0x0000FF));
                }
                sp++;
            }
        }
    }

    protected static void divide_BYTE_COMP_Data(WritableRaster wr) {
        // System.out.println("Multiply Int: " + wr);
        ComponentSampleModel csm;
        csm = (ComponentSampleModel)wr.getSampleModel();
        final int width = wr.getWidth();
        final int scanStride = csm.getScanlineStride();
        final int pixStride  = csm.getPixelStride();
        final int [] bandOff = csm.getBandOffsets();
        DataBufferByte db = (DataBufferByte)wr.getDataBuffer();
        final int base
            = (db.getOffset() +
               csm.getOffset(wr.getMinX()-wr.getSampleModelTranslateX(),
                             wr.getMinY()-wr.getSampleModelTranslateY()));

        int a=0;
        int aOff = bandOff[bandOff.length-1];
        int bands = bandOff.length-1;
        int b, i;
        // Access the pixel data array
        final byte pixels[] = db.getBankData()[0];
        for (int y=0; y<wr.getHeight(); y++) {
            int sp = base + y*scanStride;
            final int end = sp + width*pixStride;
            while (sp < end) {
              a = pixels[sp+aOff]&0xFF;
              if (a==0) {
                for (b=0; b<bands; b++)
                  pixels[sp+bandOff[b]] = (byte)0xFF;
              } else if (a<255) {
                int aFP = (0x00FF0000/a);
                for (b=0; b<bands; b++) {
                  i = sp+bandOff[b];
                  pixels[i] = (byte)(((pixels[i]&0xFF)*aFP)>>>16);
                }
              }
              sp+=pixStride;
            }
        }
    }
    protected static void mult_BYTE_COMP_Data(WritableRaster wr) {
        // System.out.println("Multiply Int: " + wr);
        ComponentSampleModel csm;
        csm = (ComponentSampleModel)wr.getSampleModel();
        final int width = wr.getWidth();
        final int scanStride = csm.getScanlineStride();
        final int pixStride  = csm.getPixelStride();
        final int [] bandOff = csm.getBandOffsets();
        DataBufferByte db = (DataBufferByte)wr.getDataBuffer();
        final int base
            = (db.getOffset() +
               csm.getOffset(wr.getMinX()-wr.getSampleModelTranslateX(),
                             wr.getMinY()-wr.getSampleModelTranslateY()));

        int a=0;
        int aOff = bandOff[bandOff.length-1];
        int bands = bandOff.length-1;
        int b, i;
        // Access the pixel data array
        final byte pixels[] = db.getBankData()[0];
        for (int y=0; y<wr.getHeight(); y++) {
            int sp = base + y*scanStride;
            final int end = sp + width*pixStride;
            while (sp < end) {
              a = pixels[sp+aOff]&0xFF;
              if (a!=0xFF)
                for (b=0; b<bands; b++) {
                  i = sp+bandOff[b];
                  pixels[i] = (byte)(((pixels[i]&0xFF)*a)>>8);
                }
              sp+=pixStride;
            }
        }
    }
/*
  This is skanky debugging code that might be useful in the future:
            if (count == 33) {
                String label = "sub [" + x + ", " + y + "]: ";
                org.ImageDisplay.showImage
                    (label, subBI);
                org.ImageDisplay.printImage
                    (label, subBI,
                     new Rectangle(75-iR.x, 90-iR.y, 32, 32));
                
            }

            // if ((count++ % 50) == 10)
            //     org.ImageDisplay.showImage("foo: ", subBI);

            Graphics2D realG2D = g2d;
            while (realG2D instanceof sun.java2d.ProxyGraphics2D) {
                realG2D = ((sun.java2d.ProxyGraphics2D)realG2D).getDelegate();
            }
            if (realG2D instanceof sun.awt.image.BufferedImageGraphics2D) {
                count++;
                if (count == 34) {
                    RenderedImage ri;
                    ri = ((sun.awt.image.BufferedImageGraphics2D)realG2D).bufImg;
                    // g2d.setComposite(SVGComposite.OVER);
                    // org.ImageDisplay.showImage("Bar: " + count, cr);
                    org.ImageDisplay.printImage("Bar: " + count, cr,
                                                new Rectangle(75, 90, 32, 32));
                    org.ImageDisplay.showImage ("Foo: " + count, ri);
                    org.ImageDisplay.printImage("Foo: " + count, ri,
                                                new Rectangle(75, 90, 32, 32));
                    System.out.println("BI: "   + ri);
                    System.out.println("BISM: " + ri.getSampleModel());
                    System.out.println("BICM: " + ri.getColorModel());
                    System.out.println("BICM class: " + ri.getColorModel().getClass());
                    System.out.println("BICS: " + ri.getColorModel().getColorSpace());
                    System.out.println
                        ("sRGB CS: " + 
                         ColorSpace.getInstance(ColorSpace.CS_sRGB));
                    System.out.println("G2D info");
                    System.out.println("\tComposite: " + g2d.getComposite());
                    System.out.println("\tTransform" + g2d.getTransform());
                    java.awt.RenderingHints rh = g2d.getRenderingHints();
                    java.util.Set keys = rh.keySet();
                    java.util.Iterator iter = keys.iterator();
                    while (iter.hasNext()) {
                        Object o = iter.next();
                        System.out.println("\t" + o.toString() + " -> " +
                                           rh.get(o).toString());
                    }
                    ri = cr;
                    System.out.println("RI: "   + ri);
                    System.out.println("RISM: " + ri.getSampleModel());
                    System.out.println("RICM: " + ri.getColorModel());
                    System.out.println("RICM class: " + ri.getColorModel().getClass());
                    System.out.println("RICS: " + ri.getColorModel().getColorSpace());
                }
            }
*/
}



Creates an image compatible with the current display

 
/*
 *
 * Created on March 16, 2007, 4:34 PM
 *
 * Copyright 2006-2007 Nigel Hughes
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
 * licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
/**
 * @author nigel
 */
public class Utils {
  /**
   * Creates an image compatible with the current display
   * 
   * @return A BufferedImage with the appropriate color model
   */
  public static BufferedImage createCompatibleImage(int width, int height) {
    GraphicsConfiguration configuration = GraphicsEnvironment.getLocalGraphicsEnvironment()
        .getDefaultScreenDevice().getDefaultConfiguration();
    return configuration.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
  }
}



Display available ImageReaders and ImageWriters by image format and MIME type

    
import javax.imageio.ImageIO;
public class RWtypes {
  public static void main(String[] args) {
    String[] readers, writers;
    System.out.println("For Reading:");
    readers = ImageIO.getReaderFormatNames();
    System.out.println("\tBy format:");
    for (int i = 0; i < readers.length; i++)
      System.out.println("\t\t" + readers[i]);
    readers = ImageIO.getReaderMIMETypes();
    System.out.println("\tBy MIME Types:");
    for (int i = 0; i < readers.length; i++)
      System.out.println("\t\t" + readers[i]);
    System.out.println("For Writing:");
    writers = ImageIO.getWriterFormatNames();
    System.out.println("\tBy format:");
    for (int i = 0; i < writers.length; i++)
      System.out.println("\t\t" + writers[i]);
    writers = ImageIO.getWriterMIMETypes();
    System.out.println("\tBy MIME Types:");
    for (int i = 0; i < writers.length; i++)
      System.out.println("\t\t" + writers[i]);
  }
}



Display image supported by ImageIO

    
import java.awt.Graphics;
import java.awt.Panel;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
public class ShowImage extends Panel {
  private static final long serialVersionUID = 1L;
  private BufferedImage image;
  public ShowImage(String filename) {
    try {
      image = ImageIO.read(new File(filename));
    } catch (IOException ie) {
      ie.printStackTrace();
    }
  }
  public void paint(Graphics g) {
    g.drawImage(image, 0, 0, null);
  }
  static public void main(String args[]) throws Exception {
    JFrame frame = new JFrame("ShowImage.java");
    Panel panel = new ShowImage(args[0]);
    frame.getContentPane().add(panel);
    frame.setSize(400, 400);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);
  }
}



Example showing how to reset the ordering of ImageReaderSpis in Image I/O

    
/*
 * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * -Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * -Redistribution in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that Software is not designed,licensed or intended for use in
 * the design, construction, operation or maintenance of any nuclear facility.
 */
import java.util.Iterator;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ServiceRegistry;
/**
 * Example showing how to reset the ordering of ImageReaderSpis in Image I/O.
 * This is particularly applicable to the case where metadata handling is a
 * requirement and is of special importance for the JPEG format as the
 * JAI-Image I/O JPEG plug-ins do not handle metadata and have a higher
 * priority setting in the IIORegistry than do the core J2SE JPEG plug-ins.
 */
public class SetOrderingExample {
    public static void main(String[] args) {
        IIORegistry registry = IIORegistry.getDefaultInstance();
        // Print the initial ordered list of readers.
        System.out.println("- Initial list of readers -");
        Iterator providers =
            registry.getServiceProviders(ImageReaderSpi.class, true);
        while(providers.hasNext()) {
            ImageReaderSpi provider = (ImageReaderSpi) providers.next();
            System.out.println(provider.getPluginClassName());
        }
        System.out.println("");
        // Get the reader SPIs for JPEG.
        providers = registry.getServiceProviders(ImageReaderSpi.class,
                                                 new JPEGFilter(),
                                                 true);
        // Save reader SPIs for JPEG according to whether they support
        // native image metadata. This removes the dependence on knowing
        // the class name of the associated reader plug-in.
        ImageReaderSpi readerMetadataSPI = null;
        ImageReaderSpi readerNoMetadataSPI = null;
        while(providers.hasNext()) {
            ImageReaderSpi readerSPI = (ImageReaderSpi)providers.next();
            if(readerSPI.getNativeImageMetadataFormatName() != null) {
                readerMetadataSPI = readerSPI;
            } else {
                readerNoMetadataSPI = readerSPI;
            }
        }
        // Update ordering if both metadata-capable and -incapable readers
        // are detected.
        if(readerMetadataSPI != null && readerNoMetadataSPI != null) {
            // Remove ordering that places metadata-capable reader last.
            boolean unsetResult = registry.unsetOrdering(ImageReaderSpi.class,
                                                         readerNoMetadataSPI,
                                                         readerMetadataSPI);
            // Add ordering that places metadata-capable reader first.
            boolean setResult = registry.setOrdering(ImageReaderSpi.class,
                                                     readerMetadataSPI,
                                                     readerNoMetadataSPI);
        }
        // Print the final ordered list of readers.
        System.out.println("- Final list of readers -");
        providers = registry.getServiceProviders(ImageReaderSpi.class, true);
        while(providers.hasNext()) {
            ImageReaderSpi provider = (ImageReaderSpi) providers.next();
            System.out.println(provider.getPluginClassName());
        }
    }
}
/**
 * Filter which returns <code>true</code> if and only if the provider is
 * an <code>ImageReaderSpi</code> which supports the JPEG format.
 */
class JPEGFilter implements ServiceRegistry.Filter {
    JPEGFilter() {}
    public boolean filter(Object provider) {
        if(!(provider instanceof ImageReaderSpi)) {
            return false;
        }
        ImageReaderSpi readerSPI = (ImageReaderSpi)provider;
        String[] formatNames = readerSPI.getFormatNames();
        for(int i = 0; i < formatNames.length; i++) {
            if(formatNames[i].equalsIgnoreCase("JPEG")) {
                return true;
            }
        }
        return false;
    }
}



Get list of unique MIME types that can be read

   

import java.util.Arrays;
import javax.imageio.ImageIO;
public class Main {
  public static void main(String[] argv) throws Exception {
   
    String[]  formatNames = ImageIO.getReaderMIMETypes();
    System.out.println(Arrays.toString(formatNames));
  }
}



Get list of unique MIME types that can be written

   
import java.util.Arrays;
import javax.imageio.ImageIO;
public class Main {
  public static void main(String[] argv) throws Exception {
    
    String[] formatNames = ImageIO.getWriterMIMETypes();
    System.out.println(Arrays.toString(formatNames));
  }
}



Get list of unique supported read formats

   
import java.util.Arrays;
import javax.imageio.ImageIO;
public class Main {
  public static void main(String[] argv) throws Exception {
    String[] formatNames = ImageIO.getReaderFormatNames();
    System.out.println(Arrays.toString(formatNames));
  }
}



Get list of unique supported write formats

   
import java.util.Arrays;
import javax.imageio.ImageIO;
public class Main {
  public static void main(String[] argv) throws Exception {
   String[]  formatNames = ImageIO.getWriterFormatNames();
    System.out.println(Arrays.toString(formatNames));
   
  }
}



Helper class for debugging stuff in Image I/O.

 
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/* $Id: ImageIODebugUtil.java 610706 2008-01-10 08:14:18Z jeremias $ */

import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Node;
/**
 * Helper class for debugging stuff in Image I/O.
 *
 * @version $Id: ImageIODebugUtil.java 610706 2008-01-10 08:14:18Z jeremias $
 */
public class ImageIODebugUtil {
    public static void dumpMetadata(IIOMetadata meta, boolean nativeFormat) {
        String format;
        if (nativeFormat) {
            format = meta.getNativeMetadataFormatName();
        } else {
            format = IIOMetadataFormatImpl.standardMetadataFormatName;
        }
        Node node = meta.getAsTree(format);
        dumpNode(node);
    }
    
    public static void dumpNode(Node node) {
        try {
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer t = tf.newTransformer();
            t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            Source src = new DOMSource(node);
            Result res = new StreamResult(System.out);
            t.transform(src, res);
            System.out.println();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}



List All reader and writer formats supported by ImageIO

    
/*
reader BMP
reader bmp
reader jpeg
reader wbmp
reader gif
reader png
reader JPG
reader jpg
reader WBMP
reader JPEG
writer BMP
writer bmp
writer jpeg
writer wbmp
writer png
writer JPG
writer PNG
writer jpg
writer WBMP
writer JPEG
*/
import javax.imageio.ImageIO;
public class ShowImageIOInfo {
  static public void main(String args[]) throws Exception {
    String names[] = ImageIO.getReaderFormatNames();
    for (int i = 0; i < names.length; ++i) {
      System.out.println("reader " + names[i]);
    }
    names = ImageIO.getWriterFormatNames();
    for (int i = 0; i < names.length; ++i) {
      System.out.println("writer " + names[i]);
    }
  }
}



Loads an image in a format compatible with the current display

 
/*
 *
 * Created on March 16, 2007, 4:34 PM
 *
 * Copyright 2006-2007 Nigel Hughes
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
 * licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.Hashtable;
import java.util.LinkedList;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
/**
 * @author nigel
 */
public class Utils {
  /** 
   * Loads an image in a format compatible with the current display
   *
   *
   * @return A BufferedImage with the appropriate color model
   */
  public static BufferedImage loadCompatibleImage(String imageURL){
      Image image = null;
      try {
          image = ImageIO.read(new URL(imageURL));
      } catch (MalformedURLException ex) {
          ex.printStackTrace();
          return null;
      } catch (IOException ex) {
          ex.printStackTrace();
          return null;
      }
      if (image==null){
          return null;
      }
      
      GraphicsConfiguration configuration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
      
      //Create a buffered image which is the right (translucent) format for the current graphics device, this
      //should ensure the fastest possible performance. Adding on some extra height to make room for the reflection
      BufferedImage originalImage = configuration.createCompatibleImage(image.getWidth(null),image.getHeight(null), Transparency.TRANSLUCENT);
      
      //Blit the loaded image onto the optimized surface by creating a graphics context for the new image
      Graphics2D g = originalImage.createGraphics();
      //Draw the original image
      g.drawImage(image, 0, 0, null);
      g.dispose();
      return originalImage;
  }
}



Load the image file from a folder or a jar file: use javax.imageio.ImageIO class to read the image file

   
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
public class Main {
  public static void main(String[] args) throws Exception {
    BufferedImage image = ImageIO.read(Main.class.getResource("S.jpg"));
  }
}



Produces a copy of the supplied image

 
/*
 *
 * Created on March 16, 2007, 4:34 PM
 *
 * Copyright 2006-2007 Nigel Hughes
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
 * licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
/**
 * @author nigel
 */
public class Utils {
  /**
   * Produces a copy of the supplied image
   *
   * @param image The original image
   * @return The new BufferedImage
   */
  public static BufferedImage copyImage(BufferedImage image){
      return scaledImage(image,image.getWidth(),image.getHeight());
  } 
  /**
   * Produces a resized image that is of the given dimensions
   *
   * @param image The original image
   * @param width The desired width
   * @param height The desired height
   * @return The new BufferedImage
   */
  public static BufferedImage scaledImage(BufferedImage image, int width, int height){
      BufferedImage newImage = createCompatibleImage(width,height);
      Graphics graphics = newImage.createGraphics();
      
      graphics.drawImage(image,0,0,width,height,null);
      
      graphics.dispose();
      return newImage;
  }
  /**
   * Creates an image compatible with the current display
   * 
   * @return A BufferedImage with the appropriate color model
   */
  public static BufferedImage createCompatibleImage(int width, int height) {
    GraphicsConfiguration configuration = GraphicsEnvironment.getLocalGraphicsEnvironment()
        .getDefaultScreenDevice().getDefaultConfiguration();
    return configuration.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
  }
}



Produces a resized image that is of the given dimensions

 
/*
 *
 * Created on March 16, 2007, 4:34 PM
 *
 * Copyright 2006-2007 Nigel Hughes
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
 * licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
/**
 * @author nigel
 */
public class Utils {
  /**
   * Produces a resized image that is of the given dimensions
   *
   * @param image The original image
   * @param width The desired width
   * @param height The desired height
   * @return The new BufferedImage
   */
  public static BufferedImage scaledImage(BufferedImage image, int width, int height){
      BufferedImage newImage = createCompatibleImage(width,height);
      Graphics graphics = newImage.createGraphics();
      
      graphics.drawImage(image,0,0,width,height,null);
      
      graphics.dispose();
      return newImage;
  }
  /**
   * Creates an image compatible with the current display
   * 
   * @return A BufferedImage with the appropriate color model
   */
  public static BufferedImage createCompatibleImage(int width, int height) {
    GraphicsConfiguration configuration = GraphicsEnvironment.getLocalGraphicsEnvironment()
        .getDefaultScreenDevice().getDefaultConfiguration();
    return configuration.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
  }
}



Read an image

    
/*
Java Media APIs: Cross-Platform Imaging, Media and Visualization
Alejandro Terrazas
Sams, Published November 2002, 
ISBN 0672320940
*/
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.util.Iterator;
import javax.imageio.IIOException;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormat;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import org.w3c.dom.Node;
/**
 * 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();
      while (s.length() == 0)
        s = iis.readLine();
      imagemd[imageIndex].imageWidth = Integer.parseInt(s.trim());
      s = iis.readLine();
      imagemd[imageIndex].imageHeight = Integer.parseInt(s.trim());
    } catch (IOException exception) {
    }
  }
  /**
   * this method sets the stream metadata for the images represented by the
   * ImageInputStream iis. This method is specific for the ch5 format and thus
   * only sets the numberImages field.
   */
  private void setStreamMetadata(ImageInputStream iis) {
    streammd = new ch5StreamMetadata();
    try {
      String magicNumber = iis.readLine();
      int numImages = Integer.parseInt(iis.readLine().trim());
      streammd.numberImages = numImages;
      imagemd = new ch5ImageMetadata[streammd.numberImages];
    } catch (IOException exception) {
    }
  }
  /**
   * This method can only be used after the stream metadata has been set
   * (which occurs in the setInput method). Else it will return a -1
   */
  public int getNumImages(boolean allowSearch) {
    return streammd.numberImages;
  }
  /**
   * This method can only be used after the stream metadata has been set
   * (which occurs in the setInput method). Else it will return a -1
   */
  public int getHeight(int imageIndex) {
    if (imagemd == null)
      return -1;
    checkIndex(imageIndex);
    return imagemd[imageIndex].imageHeight;
  }
  /**
   * This method can only be used after the stream metadata has been set
   * (which occurs in the setInput method). Else it will return a -1
   */
  public int getWidth(int imageIndex) {
    if (imagemd == null)
      return -1;
    checkIndex(imageIndex);
    return imagemd[imageIndex].imageWidth;
  }
  private void checkIndex(int imageIndex) {
    if (imageIndex >= streammd.numberImages) {
      String argString = "imageIndex >= number of images";
      throw new IndexOutOfBoundsException(argString);
    }
    if (imageIndex < minIndex) {
      String argString = "imageIndex < minIndex";
      throw new IndexOutOfBoundsException(argString);
    }
  }
}
/**
 * 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;
  }
}



Resizes an image

   
/*
 * Copyright (c) JForum Team
 * All rights reserved.
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met:
 * 1) Redistributions of source code must retain the above 
 * copyright notice, this list of conditions and the 
 * following  disclaimer.
 * 2)  Redistributions in binary form must reproduce the 
 * above copyright notice, this list of conditions and 
 * the following disclaimer in the documentation and/or 
 * other materials provided with the distribution.
 * 3) Neither the name of "Rafael Steil" nor 
 * the names of its contributors may be used to endorse 
 * or promote products derived from this software without 
 * specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT 
 * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
 * IN CONTRACT, STRICT LIABILITY, OR TORT 
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
 * 
 * This file creation date: 21/04/2004 - 19:54:16
 * The JForum Project
 * http://www.jforum.net
 */
import java.awt.Dimension;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.PixelGrabber;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Locale;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.ImageOutputStream;

/**
 * Utilities methods for image manipulation. It does not support writting of GIF images, but it can
 * read from. GIF images will be saved as PNG.
 * 
 * @author Rafael Steil
 * @version $Id: ImageUtils.java,v 1.23 2007/09/09 01:05:22 rafaelsteil Exp $
 */
public class ImageUtils
{
  public static final int IMAGE_UNKNOWN = -1;
  public static final int IMAGE_JPEG = 0;
  public static final int IMAGE_PNG = 1;
  public static final int IMAGE_GIF = 2;
  /**
   * Resizes an image
   * 
   * @param imgName The image name to resize. Must be the complet path to the file
   * @param type int
   * @param maxWidth The image"s max width
   * @param maxHeight The image"s max height
   * @return A resized <code>BufferedImage</code>
   */
  public static BufferedImage resizeImage(String imgName, int type, int maxWidth, int maxHeight)
  {
    try {
      return resizeImage(ImageIO.read(new File(imgName)), type, maxWidth, maxHeight);
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }
  /**
   * Resizes an image.
   * 
   * @param image
   *            The image to resize
   * @param maxWidth
   *            The image"s max width
   * @param maxHeight
   *            The image"s max height
   * @return A resized <code>BufferedImage</code>
   * @param type
   *            int
   */
  public static BufferedImage resizeImage(BufferedImage image, int type, int maxWidth, int maxHeight)
  {
    Dimension largestDimension = new Dimension(maxWidth, maxHeight);
    // Original size
    int imageWidth = image.getWidth(null);
    int imageHeight = image.getHeight(null);
    float aspectRatio = (float) imageWidth / imageHeight;
    if (imageWidth > maxWidth || imageHeight > maxHeight) {
      if ((float) largestDimension.width / largestDimension.height > aspectRatio) {
        largestDimension.width = (int) Math.ceil(largestDimension.height * aspectRatio);
      }
      else {
        largestDimension.height = (int) Math.ceil(largestDimension.width / aspectRatio);
      }
      imageWidth = largestDimension.width;
      imageHeight = largestDimension.height;
    }
    return createHeadlessSmoothBufferedImage(image, type, imageWidth, imageHeight);
  }
  /**
   * Saves an image to the disk.
   * 
   * @param image  The image to save
   * @param toFileName The filename to use
   * @param type The image type. Use <code>ImageUtils.IMAGE_JPEG</code> to save as JPEG images,
   *  or <code>ImageUtils.IMAGE_PNG</code> to save as PNG.
   * @return <code>false</code> if no appropriate writer is found
   */
  public static boolean saveImage(BufferedImage image, String toFileName, int type)
  {
    try {
      return ImageIO.write(image, type == IMAGE_JPEG ? "jpg" : "png", new File(toFileName));
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }
  /**
   * Compress and save an image to the disk. Currently this method only supports JPEG images.
   * 
   * @param image The image to save
   * @param toFileName The filename to use
   * @param type The image type. Use <code>ImageUtils.IMAGE_JPEG</code> to save as JPEG images,
   * or <code>ImageUtils.IMAGE_PNG</code> to save as PNG.
   */
  public static void saveCompressedImage(BufferedImage image, String toFileName, int type)
  {
    try {
      if (type == IMAGE_PNG) {
        throw new UnsupportedOperationException("PNG compression not implemented");
      }
      Iterator iter = ImageIO.getImageWritersByFormatName("jpg");
      ImageWriter writer;
      writer = (ImageWriter) iter.next();
      ImageOutputStream ios = ImageIO.createImageOutputStream(new File(toFileName));
      writer.setOutput(ios);
      ImageWriteParam iwparam = new JPEGImageWriteParam(Locale.getDefault());
      iwparam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
      iwparam.setCompressionQuality(0.7F);
      writer.write(null, new IIOImage(image, null, null), iwparam);
      ios.flush();
      writer.dispose();
      ios.close();
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }
  /**
   * Creates a <code>BufferedImage</code> from an <code>Image</code>. This method can
   * function on a completely headless system. This especially includes Linux and Unix systems
   * that do not have the X11 libraries installed, which are required for the AWT subsystem to
   * operate. This method uses nearest neighbor approximation, so it"s quite fast. Unfortunately,
   * the result is nowhere near as nice looking as the createHeadlessSmoothBufferedImage method.
   * 
   * @param image  The image to convert
   * @param w The desired image width
   * @param h The desired image height
   * @return The converted image
   * @param type int
   */
  public static BufferedImage createHeadlessBufferedImage(BufferedImage image, int type, int width, int height)
  {
    if (type == ImageUtils.IMAGE_PNG && hasAlpha(image)) {
      type = BufferedImage.TYPE_INT_ARGB;
    }
    else {
      type = BufferedImage.TYPE_INT_RGB;
    }
    BufferedImage bi = new BufferedImage(width, height, type);
    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
        bi.setRGB(x, y, image.getRGB(x * image.getWidth() / width, y * image.getHeight() / height));
      }
    }
    return bi;
  }
  /**
   * Creates a <code>BufferedImage</code> from an <code>Image</code>. This method can
   * function on a completely headless system. This especially includes Linux and Unix systems
   * that do not have the X11 libraries installed, which are required for the AWT subsystem to
   * operate. The resulting image will be smoothly scaled using bilinear filtering.
   * 
   * @param source The image to convert
   * @param w The desired image width
   * @param h The desired image height
   * @return The converted image
   * @param type  int
   */
  public static BufferedImage createHeadlessSmoothBufferedImage(BufferedImage source, int type, int width, int height)
  {
    if (type == ImageUtils.IMAGE_PNG && hasAlpha(source)) {
      type = BufferedImage.TYPE_INT_ARGB;
    }
    else {
      type = BufferedImage.TYPE_INT_RGB;
    }
    BufferedImage dest = new BufferedImage(width, height, type);
    int sourcex;
    int sourcey;
    double scalex = (double) width / source.getWidth();
    double scaley = (double) height / source.getHeight();
    int x1;
    int y1;
    double xdiff;
    double ydiff;
    int rgb;
    int rgb1;
    int rgb2;
    for (int y = 0; y < height; y++) {
      sourcey = y * source.getHeight() / dest.getHeight();
      ydiff = scale(y, scaley) - sourcey;
      for (int x = 0; x < width; x++) {
        sourcex = x * source.getWidth() / dest.getWidth();
        xdiff = scale(x, scalex) - sourcex;
        x1 = Math.min(source.getWidth() - 1, sourcex + 1);
        y1 = Math.min(source.getHeight() - 1, sourcey + 1);
        rgb1 = getRGBInterpolation(source.getRGB(sourcex, sourcey), source.getRGB(x1, sourcey), xdiff);
        rgb2 = getRGBInterpolation(source.getRGB(sourcex, y1), source.getRGB(x1, y1), xdiff);
        rgb = getRGBInterpolation(rgb1, rgb2, ydiff);
        dest.setRGB(x, y, rgb);
      }
    }
    return dest;
  }
  private static double scale(int point, double scale)
  {
    return point / scale;
  }
  private static int getRGBInterpolation(int value1, int value2, double distance)
  {
    int alpha1 = (value1 & 0xFF000000) >>> 24;
    int red1 = (value1 & 0x00FF0000) >> 16;
    int green1 = (value1 & 0x0000FF00) >> 8;
    int blue1 = (value1 & 0x000000FF);
    int alpha2 = (value2 & 0xFF000000) >>> 24;
    int red2 = (value2 & 0x00FF0000) >> 16;
    int green2 = (value2 & 0x0000FF00) >> 8;
    int blue2 = (value2 & 0x000000FF);
    int rgb = ((int) (alpha1 * (1.0 - distance) + alpha2 * distance) << 24)
      | ((int) (red1 * (1.0 - distance) + red2 * distance) << 16)
      | ((int) (green1 * (1.0 - distance) + green2 * distance) << 8)
      | (int) (blue1 * (1.0 - distance) + blue2 * distance);
    return rgb;
  }
  /**
   * Determines if the image has transparent pixels.
   * 
   * @param image The image to check for transparent pixel.s
   * @return <code>true</code> of <code>false</code>, according to the result
   */
  public static boolean hasAlpha(Image image)
  {
    try {
      PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
      pg.grabPixels();
      return pg.getColorModel().hasAlpha();
    }
    catch (InterruptedException e) {
      return false;
    }
  }
}



Returns true if the specified file extension can be read

   
import java.io.File;
import java.util.Iterator;
import javax.imageio.ImageIO;
public class Main {
  public static void main(String[] argv) throws Exception {
    boolean b;
    b = canReadExtension("yourExtensionName"); 
    b = canReadExtension("gif"); 
    b = canReadExtension("giF"); 
  }
  public static boolean canReadExtension(String fileExt) {
    Iterator iter = ImageIO.getImageReadersBySuffix(fileExt);
    return iter.hasNext();
  }
}



Returns true if the specified file extension can be written

   
import java.io.File;
import java.util.Iterator;
import javax.imageio.ImageIO;
public class Main {
  public static void main(String[] argv) throws Exception {
    boolean b;
    b = canWriteExtension("yourExtensionName"); 
    b = canWriteExtension("gif"); 
    b = canWriteExtension("giF"); 
  }
  public static boolean canWriteExtension(String fileExt) {
    Iterator iter = ImageIO.getImageWritersBySuffix(fileExt);
    return iter.hasNext();
  }
}



Returns true if the specified format name can be read

   

import java.io.File;
import java.util.Iterator;
import javax.imageio.ImageIO;
public class Main {
  public static void main(String[] argv) throws Exception {
    boolean b;
    b = canReadFormat("yourExtensionName"); 
    b = canReadFormat("gif"); 
    b = canReadFormat("giF"); 
  }
  public static boolean canReadFormat(String formatName) {
    Iterator iter = ImageIO.getImageReadersByFormatName(formatName);
    return iter.hasNext();
  }  
}



Returns true if the specified format name can be written

   

import java.io.File;
import java.util.Iterator;
import javax.imageio.ImageIO;
public class Main {
  public static void main(String[] argv) throws Exception {
    boolean b;
    b = canWriteFormat("yourExtensionName"); 
    b = canWriteFormat("gif"); 
    b = canWriteFormat("giF"); 
  }
 
  public static boolean canWriteFormat(String formatName) {
    Iterator iter = ImageIO.getImageWritersByFormatName(formatName);
    return iter.hasNext();
  }
}



Returns true if the specified mime type can be read

   
import java.io.File;
import java.util.Iterator;
import javax.imageio.ImageIO;
public class Main {
  public static void main(String[] argv) throws Exception {
    boolean b;
    b = canReadMimeType("image/jpg"); 
    b = canReadMimeType("image/jpeg"); 
  }
  public static boolean canReadMimeType(String mimeType) {
    Iterator iter = ImageIO.getImageReadersByMIMEType(mimeType);
    return iter.hasNext();
  }
}



Returns true if the specified mime type can be written

   

import java.io.File;
import java.util.Iterator;
import javax.imageio.ImageIO;
public class Main {
  public static void main(String[] argv) throws Exception {
    boolean b;
    b = canWriteMimeType("image/jpg"); 
    b = canWriteMimeType("image/jpeg");
  }
  public static boolean canWriteMimeType(String mimeType) {
    Iterator iter = ImageIO.getImageWritersByMIMEType(mimeType);
    return iter.hasNext();
  }
}



Saves an image to the disk

   
/*
 * Copyright (c) JForum Team
 * All rights reserved.
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met:
 * 1) Redistributions of source code must retain the above 
 * copyright notice, this list of conditions and the 
 * following  disclaimer.
 * 2)  Redistributions in binary form must reproduce the 
 * above copyright notice, this list of conditions and 
 * the following disclaimer in the documentation and/or 
 * other materials provided with the distribution.
 * 3) Neither the name of "Rafael Steil" nor 
 * the names of its contributors may be used to endorse 
 * or promote products derived from this software without 
 * specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT 
 * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
 * IN CONTRACT, STRICT LIABILITY, OR TORT 
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
 * 
 * This file creation date: 21/04/2004 - 19:54:16
 * The JForum Project
 * http://www.jforum.net
 */
import java.awt.Dimension;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.PixelGrabber;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Locale;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.ImageOutputStream;

/**
 * Utilities methods for image manipulation. It does not support writting of GIF images, but it can
 * read from. GIF images will be saved as PNG.
 * 
 * @author Rafael Steil
 * @version $Id: ImageUtils.java,v 1.23 2007/09/09 01:05:22 rafaelsteil Exp $
 */
public class ImageUtils
{
  public static final int IMAGE_UNKNOWN = -1;
  public static final int IMAGE_JPEG = 0;
  public static final int IMAGE_PNG = 1;
  public static final int IMAGE_GIF = 2;
  /**
   * Resizes an image
   * 
   * @param imgName The image name to resize. Must be the complet path to the file
   * @param type int
   * @param maxWidth The image"s max width
   * @param maxHeight The image"s max height
   * @return A resized <code>BufferedImage</code>
   */
  public static BufferedImage resizeImage(String imgName, int type, int maxWidth, int maxHeight)
  {
    try {
      return resizeImage(ImageIO.read(new File(imgName)), type, maxWidth, maxHeight);
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }
  /**
   * Resizes an image.
   * 
   * @param image
   *            The image to resize
   * @param maxWidth
   *            The image"s max width
   * @param maxHeight
   *            The image"s max height
   * @return A resized <code>BufferedImage</code>
   * @param type
   *            int
   */
  public static BufferedImage resizeImage(BufferedImage image, int type, int maxWidth, int maxHeight)
  {
    Dimension largestDimension = new Dimension(maxWidth, maxHeight);
    // Original size
    int imageWidth = image.getWidth(null);
    int imageHeight = image.getHeight(null);
    float aspectRatio = (float) imageWidth / imageHeight;
    if (imageWidth > maxWidth || imageHeight > maxHeight) {
      if ((float) largestDimension.width / largestDimension.height > aspectRatio) {
        largestDimension.width = (int) Math.ceil(largestDimension.height * aspectRatio);
      }
      else {
        largestDimension.height = (int) Math.ceil(largestDimension.width / aspectRatio);
      }
      imageWidth = largestDimension.width;
      imageHeight = largestDimension.height;
    }
    return createHeadlessSmoothBufferedImage(image, type, imageWidth, imageHeight);
  }
  /**
   * Saves an image to the disk.
   * 
   * @param image  The image to save
   * @param toFileName The filename to use
   * @param type The image type. Use <code>ImageUtils.IMAGE_JPEG</code> to save as JPEG images,
   *  or <code>ImageUtils.IMAGE_PNG</code> to save as PNG.
   * @return <code>false</code> if no appropriate writer is found
   */
  public static boolean saveImage(BufferedImage image, String toFileName, int type)
  {
    try {
      return ImageIO.write(image, type == IMAGE_JPEG ? "jpg" : "png", new File(toFileName));
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }
  /**
   * Compress and save an image to the disk. Currently this method only supports JPEG images.
   * 
   * @param image The image to save
   * @param toFileName The filename to use
   * @param type The image type. Use <code>ImageUtils.IMAGE_JPEG</code> to save as JPEG images,
   * or <code>ImageUtils.IMAGE_PNG</code> to save as PNG.
   */
  public static void saveCompressedImage(BufferedImage image, String toFileName, int type)
  {
    try {
      if (type == IMAGE_PNG) {
        throw new UnsupportedOperationException("PNG compression not implemented");
      }
      Iterator iter = ImageIO.getImageWritersByFormatName("jpg");
      ImageWriter writer;
      writer = (ImageWriter) iter.next();
      ImageOutputStream ios = ImageIO.createImageOutputStream(new File(toFileName));
      writer.setOutput(ios);
      ImageWriteParam iwparam = new JPEGImageWriteParam(Locale.getDefault());
      iwparam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
      iwparam.setCompressionQuality(0.7F);
      writer.write(null, new IIOImage(image, null, null), iwparam);
      ios.flush();
      writer.dispose();
      ios.close();
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }
  /**
   * Creates a <code>BufferedImage</code> from an <code>Image</code>. This method can
   * function on a completely headless system. This especially includes Linux and Unix systems
   * that do not have the X11 libraries installed, which are required for the AWT subsystem to
   * operate. This method uses nearest neighbor approximation, so it"s quite fast. Unfortunately,
   * the result is nowhere near as nice looking as the createHeadlessSmoothBufferedImage method.
   * 
   * @param image  The image to convert
   * @param w The desired image width
   * @param h The desired image height
   * @return The converted image
   * @param type int
   */
  public static BufferedImage createHeadlessBufferedImage(BufferedImage image, int type, int width, int height)
  {
    if (type == ImageUtils.IMAGE_PNG && hasAlpha(image)) {
      type = BufferedImage.TYPE_INT_ARGB;
    }
    else {
      type = BufferedImage.TYPE_INT_RGB;
    }
    BufferedImage bi = new BufferedImage(width, height, type);
    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
        bi.setRGB(x, y, image.getRGB(x * image.getWidth() / width, y * image.getHeight() / height));
      }
    }
    return bi;
  }
  /**
   * Creates a <code>BufferedImage</code> from an <code>Image</code>. This method can
   * function on a completely headless system. This especially includes Linux and Unix systems
   * that do not have the X11 libraries installed, which are required for the AWT subsystem to
   * operate. The resulting image will be smoothly scaled using bilinear filtering.
   * 
   * @param source The image to convert
   * @param w The desired image width
   * @param h The desired image height
   * @return The converted image
   * @param type  int
   */
  public static BufferedImage createHeadlessSmoothBufferedImage(BufferedImage source, int type, int width, int height)
  {
    if (type == ImageUtils.IMAGE_PNG && hasAlpha(source)) {
      type = BufferedImage.TYPE_INT_ARGB;
    }
    else {
      type = BufferedImage.TYPE_INT_RGB;
    }
    BufferedImage dest = new BufferedImage(width, height, type);
    int sourcex;
    int sourcey;
    double scalex = (double) width / source.getWidth();
    double scaley = (double) height / source.getHeight();
    int x1;
    int y1;
    double xdiff;
    double ydiff;
    int rgb;
    int rgb1;
    int rgb2;
    for (int y = 0; y < height; y++) {
      sourcey = y * source.getHeight() / dest.getHeight();
      ydiff = scale(y, scaley) - sourcey;
      for (int x = 0; x < width; x++) {
        sourcex = x * source.getWidth() / dest.getWidth();
        xdiff = scale(x, scalex) - sourcex;
        x1 = Math.min(source.getWidth() - 1, sourcex + 1);
        y1 = Math.min(source.getHeight() - 1, sourcey + 1);
        rgb1 = getRGBInterpolation(source.getRGB(sourcex, sourcey), source.getRGB(x1, sourcey), xdiff);
        rgb2 = getRGBInterpolation(source.getRGB(sourcex, y1), source.getRGB(x1, y1), xdiff);
        rgb = getRGBInterpolation(rgb1, rgb2, ydiff);
        dest.setRGB(x, y, rgb);
      }
    }
    return dest;
  }
  private static double scale(int point, double scale)
  {
    return point / scale;
  }
  private static int getRGBInterpolation(int value1, int value2, double distance)
  {
    int alpha1 = (value1 & 0xFF000000) >>> 24;
    int red1 = (value1 & 0x00FF0000) >> 16;
    int green1 = (value1 & 0x0000FF00) >> 8;
    int blue1 = (value1 & 0x000000FF);
    int alpha2 = (value2 & 0xFF000000) >>> 24;
    int red2 = (value2 & 0x00FF0000) >> 16;
    int green2 = (value2 & 0x0000FF00) >> 8;
    int blue2 = (value2 & 0x000000FF);
    int rgb = ((int) (alpha1 * (1.0 - distance) + alpha2 * distance) << 24)
      | ((int) (red1 * (1.0 - distance) + red2 * distance) << 16)
      | ((int) (green1 * (1.0 - distance) + green2 * distance) << 8)
      | (int) (blue1 * (1.0 - distance) + blue2 * distance);
    return rgb;
  }
  /**
   * Determines if the image has transparent pixels.
   * 
   * @param image The image to check for transparent pixel.s
   * @return <code>true</code> of <code>false</code>, according to the result
   */
  public static boolean hasAlpha(Image image)
  {
    try {
      PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
      pg.grabPixels();
      return pg.getColorModel().hasAlpha();
    }
    catch (InterruptedException e) {
      return false;
    }
  }
}



Show ImageIO Info

   
import javax.imageio.ImageIO;
public class Main {
  static public void main(String args[]) throws Exception {
    String names[] = ImageIO.getReaderFormatNames();
    for (int i = 0; i < names.length; ++i) {
      System.out.println("reader " + names[i]);
    }
    names = ImageIO.getWriterFormatNames();
    for (int i = 0; i < names.length; ++i) {
      System.out.println("writer " + names[i]);
    }
  }
}



Simple, functional ImageReaderSpi used to understand how information

    
/*
Java Media APIs: Cross-Platform Imaging, Media and Visualization
Alejandro Terrazas
Sams, Published November 2002, 
ISBN 0672320940
*/

import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.util.Iterator;
import java.util.Locale;
import javax.imageio.IIOException;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormat;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import org.w3c.dom.Node;
/**
 * Simple, functional ImageReaderSpi used to understand how information
 * regarding format name, suffices and mime types get passed to ImageIO static
 * methods
 */
public class ch5ImageReaderSpi extends ImageReaderSpi {
  static final String[] suffixes = { "ch5", "CH5" };
  static final String[] names = { "ch5" };
  static final String[] MIMETypes = { "image/ch5" };
  static final String version = "1.00";
  static final String readerCN = "ch5.imageio.plugins.ch5ImageReader";
  static final String vendorName = "CompanyName";
  //writerSpiNames
  static final String[] wSN = { "ch5.imageio.plugins.ch5ImageWriterSpi" };
  //StreamMetadataFormatNames and StreamMetadataFormatClassNames
  static final boolean supportedStandardStreamMetadataFormat = false;
  static final String nativeStreamMFN = "ch5.imageio.ch5stream_1.00";
  static final String nativeStreamMFCN = "ch5.imageio.ch5stream";
  static final String[] extraStreamMFN = null;
  static final String[] extraStreamMFCN = null;
  //ImageMetadataFormatNames and ImageMetadataFormatClassNames
  static final boolean supportedStandardImageMetadataFormat = false;
  static final String nativeImageMFN = "ch5.imageio.ch5image1.00";
  static final String nativeImageMFCN = "ch5.imageio.ch5image";
  static final String[] extraImageMFN = null;
  static final String[] extraImageMFCN = null;
  public ch5ImageReaderSpi() {
    super(vendorName, version, names, suffixes, MIMETypes, readerCN, //readerClassName
        STANDARD_INPUT_TYPE, wSN, //writerSpiNames
        false, nativeStreamMFN, nativeStreamMFCN, extraStreamMFN,
        extraStreamMFCN, false, nativeImageMFN, nativeImageMFCN,
        extraImageMFN, extraImageMFCN);
  }
  public String getDescription(Locale locale) {
    return "Demo ch5 image reader, version " + version;
  }
  public ImageReader createReaderInstance(Object extension) {
    return new ch5ImageReader(this);
  }
  /**
   * This method gets called when an application wants to see if the input
   * image"s format can be decoded by this ImageReader. In this case, we"ll
   * simply check the first byte of data to see if its a 5 which is the format
   * type"s magic number
   */
  public boolean canDecodeInput(Object input) {
    boolean reply = false;
    ImageInputStream iis = (ImageInputStream) input;
    iis.mark(); // mark where we are in ImageInputStream
    try {
      String magicNumber = iis.readLine().trim();
      iis.reset(); // reset stream back to marked location
      if (magicNumber.equals("5"))
        reply = true;
    } catch (IOException exception) {
    }
    return reply;
  }
}
class ch5ImageReader extends ImageReader {
  private ImageInputStream iis;
  private ch5ImageMetadata[] imagemd;
  private ch5StreamMetadata streammd;
  public ch5ImageReader(ImageReaderSpi originatingProvider) {
    super(originatingProvider);
  }
  /**
   * return the ch5StreamMetadata object instantiated in the setStreamMetadata
   * method
   */
  public IIOMetadata getStreamMetadata() {
    return streammd;
  }
  /**
   * return the ch5ImageMetadata object instantiated in the setImageMetadata
   * method
   */
  public IIOMetadata getImageMetadata(int imageIndex) {
    return imagemd[imageIndex];
  }
  /**
   * this method sets the input for this ImageReader and also calls the
   * setStreamMetadata method so that the numberImages field is available
   */
  public void setInput(Object object, boolean seekForwardOnly) {
    super.setInput(object, seekForwardOnly);
    if (object == null)
      throw new IllegalArgumentException("input is null");
    if (!(object instanceof ImageInputStream)) {
      String argString = "input not an ImageInputStream";
      throw new IllegalArgumentException(argString);
    }
    iis = (ImageInputStream) object;
    setStreamMetadata(iis);
  }
  /**
   * this method provides suggestions for possible image types that will be
   * used to decode the image specified by index imageIndex. By default, the
   * first image type returned by this method will be the image type of the
   * BufferedImage returned by the ImageReader"s getDestination method. In
   * this case, we are suggesting using an 8 bit grayscale image with no alpha
   * component.
   */
  public Iterator getImageTypes(int imageIndex) {
    java.util.List l = new java.util.ArrayList();
    ;
    int bits = 8;
    /*
     * can convert ch5 format into 8 bit grayscale image with no alpha
     */
    l.add(ImageTypeSpecifier.createGrayscale(bits, DataBuffer.TYPE_BYTE,
        false));
    return l.iterator();
  }
  /**
   * read in the input image specified by index imageIndex using the
   * parameters specified by the ImageReadParam object param
   */
  public BufferedImage read(int imageIndex, ImageReadParam param) {
    checkIndex(imageIndex);
    if (isSeekForwardOnly())
      minIndex = imageIndex;
    else
      minIndex = 0;
    BufferedImage bimage = null;
    WritableRaster raster = null;
    /*
     * this method sets the image metadata so that we can use the getWidth
     * and getHeight methods
     */
    setImageMetadata(iis, imageIndex);
    int srcWidth = getWidth(imageIndex);
    int srcHeight = getHeight(imageIndex);
    // initialize values to -1
    int dstWidth = -1;
    int dstHeight = -1;
    int srcRegionWidth = -1;
    int srcRegionHeight = -1;
    int srcRegionXOffset = -1;
    int srcRegionYOffset = -1;
    int xSubsamplingFactor = -1;
    int ySubsamplingFactor = -1;
    if (param == null)
      param = getDefaultReadParam();
    Iterator imageTypes = getImageTypes(imageIndex);
    try {
      /*
       * get the destination BufferedImage which will be filled using the
       * input image"s pixel data
       */
      bimage = getDestination(param, imageTypes, srcWidth, srcHeight);
      /*
       * get Rectangle object which will be used to clip the source
       * image"s dimensions.
       */
      Rectangle srcRegion = param.getSourceRegion();
      if (srcRegion != null) {
        srcRegionWidth = (int) srcRegion.getWidth();
        srcRegionHeight = (int) srcRegion.getHeight();
        srcRegionXOffset = (int) srcRegion.getX();
        srcRegionYOffset = (int) srcRegion.getY();
        /*
         * correct for overextended source regions
         */
        if (srcRegionXOffset + srcRegionWidth > srcWidth)
          dstWidth = srcWidth - srcRegionXOffset;
        else
          dstWidth = srcRegionWidth;
        if (srcRegionYOffset + srcRegionHeight > srcHeight)
          dstHeight = srcHeight - srcRegionYOffset;
        else
          dstHeight = srcRegionHeight;
      } else {
        dstWidth = srcWidth;
        dstHeight = srcHeight;
        srcRegionXOffset = srcRegionYOffset = 0;
      }
      /*
       * get subsampling factors
       */
      xSubsamplingFactor = param.getSourceXSubsampling();
      ySubsamplingFactor = param.getSourceYSubsampling();
      /**
       * dstWidth and dstHeight should be equal to bimage.getWidth() and
       * bimage.getHeight() after these next two instructions
       */
      dstWidth = (dstWidth - 1) / xSubsamplingFactor + 1;
      dstHeight = (dstHeight - 1) / ySubsamplingFactor + 1;
    } catch (IIOException e) {
      System.err.println("Can"t create destination BufferedImage");
    }
    raster = bimage.getWritableTile(0, 0);
    /*
     * using the parameters specified by the ImageReadParam object, read the
     * image image data into the destination BufferedImage
     */
    byte[] srcBuffer = new byte[srcWidth];
    byte[] dstBuffer = new byte[dstWidth];
    int jj;
    int index;
    try {
      for (int j = 0; j < srcHeight; j++) {
        iis.readFully(srcBuffer, 0, srcWidth);
        jj = j - srcRegionYOffset;
        if (jj % ySubsamplingFactor == 0) {
          jj /= ySubsamplingFactor;
          if ((jj >= 0) && (jj < dstHeight)) {
            for (int i = 0; i < dstWidth; i++) {
              index = srcRegionXOffset + i * xSubsamplingFactor;
              dstBuffer[i] = srcBuffer[index];
            }
            raster.setDataElements(0, jj, dstWidth, 1, dstBuffer);
          }
        }
      }
    } catch (IOException e) {
      bimage = null;
    }
    return bimage;
  }
  /**
   * this method sets the image metadata for the image indexed by index
   * imageIndex. This method is specific for the ch5 format and thus only sets
   * the image width and image height
   */
  private void setImageMetadata(ImageInputStream iis, int imageIndex) {
    imagemd[imageIndex] = new ch5ImageMetadata();
    try {
      String s;
      s = iis.readLine();
      while (s.length() == 0)
        s = iis.readLine();
      imagemd[imageIndex].imageWidth = Integer.parseInt(s.trim());
      s = iis.readLine();
      imagemd[imageIndex].imageHeight = Integer.parseInt(s.trim());
    } catch (IOException exception) {
    }
  }
  /**
   * this method sets the stream metadata for the images represented by the
   * ImageInputStream iis. This method is specific for the ch5 format and thus
   * only sets the numberImages field.
   */
  private void setStreamMetadata(ImageInputStream iis) {
    streammd = new ch5StreamMetadata();
    try {
      String magicNumber = iis.readLine();
      int numImages = Integer.parseInt(iis.readLine().trim());
      streammd.numberImages = numImages;
      imagemd = new ch5ImageMetadata[streammd.numberImages];
    } catch (IOException exception) {
    }
  }
  /**
   * This method can only be used after the stream metadata has been set
   * (which occurs in the setInput method). Else it will return a -1
   */
  public int getNumImages(boolean allowSearch) {
    return streammd.numberImages;
  }
  /**
   * This method can only be used after the stream metadata has been set
   * (which occurs in the setInput method). Else it will return a -1
   */
  public int getHeight(int imageIndex) {
    if (imagemd == null)
      return -1;
    checkIndex(imageIndex);
    return imagemd[imageIndex].imageHeight;
  }
  /**
   * This method can only be used after the stream metadata has been set
   * (which occurs in the setInput method). Else it will return a -1
   */
  public int getWidth(int imageIndex) {
    if (imagemd == null)
      return -1;
    checkIndex(imageIndex);
    return imagemd[imageIndex].imageWidth;
  }
  private void checkIndex(int imageIndex) {
    if (imageIndex >= streammd.numberImages) {
      String argString = "imageIndex >= number of images";
      throw new IndexOutOfBoundsException(argString);
    }
    if (imageIndex < minIndex) {
      String argString = "imageIndex < minIndex";
      throw new IndexOutOfBoundsException(argString);
    }
  }
}

/**
 * ch5ImageMetadata.java -- holds image metadata for the ch5 format. The
 * internal tree for holding this metadata is read only
 */
class ch5ImageMetadata extends IIOMetadata {
  static final String nativeMetadataFormatName = "ch5.imageio.ch5image_1.00";
  static final String nativeMetadataFormatClassName = "ch5.imageio.ch5image";
  static final String[] extraMetadataFormatNames = null;
  static final String[] extraMetadataFormatClassNames = null;
  static final boolean standardMetadataFormatSupported = false;
  public int imageWidth;
  public int imageHeight;
  public ch5ImageMetadata() {
    super(standardMetadataFormatSupported, nativeMetadataFormatName,
        nativeMetadataFormatClassName, extraMetadataFormatNames,
        extraMetadataFormatClassNames);
    imageWidth = -1;
    imageHeight = -1;
  }
  public boolean isReadOnly() {
    return true;
  }
  /**
   * IIOMetadataFormat objects are meant to describe the structure of metadata
   * returned from the getAsTree method. In this case, no such description is
   * available
   */
  public IIOMetadataFormat getMetadataFormat(String formatName) {
    if (formatName.equals(nativeMetadataFormatName)) {
      return null;
    } else {
      throw new IllegalArgumentException("Unrecognized format!");
    }
  }
  /**
   * returns the image metadata in a tree corresponding to the provided
   * formatName
   */
  public Node getAsTree(String formatName) {
    if (formatName.equals(nativeMetadataFormatName)) {
      return getNativeTree();
    } else {
      throw new IllegalArgumentException("Unrecognized format!");
    }
  }
  /**
   * returns the image metadata in a tree using the following format <!ELEMENT
   * ch5.imageio.ch5image_1.00 (imageDimensions)> <!ATTLIST imageDimensions
   * imageWidth CDATA #REQUIRED imageHeight CDATA #REQUIRED
   */
  private Node getNativeTree() {
    IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
    IIOMetadataNode node = new IIOMetadataNode("imageDimensions");
    node.setAttribute("imageWidth", Integer.toString(imageWidth));
    node.setAttribute("imageHeight", Integer.toString(imageHeight));
    root.appendChild(node);
    return root;
  }
  public void setFromTree(String formatName, Node root) {
    throw new IllegalStateException("Metadata is read-only!");
  }
  public void mergeTree(String formatName, Node root) {
    throw new IllegalStateException("Metadata is read-only!");
  }
  public void reset() {
    throw new IllegalStateException("Metadata is read-only!");
  }
  /**
   * initialize the image metadata elements width and height
   */
  public void initialize(int width, int height) {
    imageWidth = width;
    imageHeight = height;
  }
}

/**
 * ch5StreamMetadata.java -- holds stream metadata for the ch5 format. The
 * internal tree for holding this metadata is read only
 */
class ch5StreamMetadata extends IIOMetadata {
  static final String nativeMetadataFormatName = "ch5.imageio.ch5stream_1.00";
  static final String nativeMetadataFormatClassName = "ch5.imageio.ch5stream";
  static final String[] extraMetadataFormatNames = null;
  static final String[] extraMetadataFormatClassNames = null;
  static final boolean standardMetadataFormatSupported = false;
  public int numberImages;
  public ch5StreamMetadata() {
    super(standardMetadataFormatSupported, nativeMetadataFormatName,
        nativeMetadataFormatClassName, extraMetadataFormatNames,
        extraMetadataFormatClassNames);
    numberImages = -1;
  }
  public boolean isReadOnly() {
    return true;
  }
  /**
   * IIOMetadataFormat objects are meant to describe the structure of metadata
   * returned from the getAsTree method. In this case, no such description is
   * available
   */
  public IIOMetadataFormat getMetadataFormat(String formatName) {
    if (formatName.equals(nativeMetadataFormatName)) {
      return null;
    } else {
      throw new IllegalArgumentException("Unrecognized format!");
    }
  }
  /**
   * returns the stream metadata in a tree corresponding to the provided
   * formatName
   */
  public Node getAsTree(String formatName) {
    if (formatName.equals(nativeMetadataFormatName)) {
      return getNativeTree();
    } else {
      throw new IllegalArgumentException("Unrecognized format!");
    }
  }
  /**
   * returns the stream metadata in a tree using the following format
   * <!ELEMENT ch5.imageio.ch5stream_1.00 (imageDimensions)> <!ATTLIST
   * imageDimensions numberImages CDATA #REQUIRED
   */
  private Node getNativeTree() {
    IIOMetadataNode node; // scratch node
    IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
    // Image descriptor
    node = new IIOMetadataNode("imageDimensions");
    node.setAttribute("numberImages", Integer.toString(numberImages));
    root.appendChild(node);
    return root;
  }
  public void setFromTree(String formatName, Node root) {
    throw new IllegalStateException("Metadata is read-only!");
  }
  public void mergeTree(String formatName, Node root) {
    throw new IllegalStateException("Metadata is read-only!");
  }
  public void reset() {
    throw new IllegalStateException("Metadata is read-only!");
  }
  /**
   * initialize the stream metadata element numberImages
   */
  public void initialize(int numberImages) {
    this.numberImages = numberImages;
  }
}



Simple, functional ImageWriterSpi used to understand how information

    
/*
Java Media APIs: Cross-Platform Imaging, Media and Visualization
Alejandro Terrazas
Sams, Published November 2002, 
ISBN 0672320940
*/

import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.IOException;
import java.util.Locale;
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;
/**
 * Simple, functional ImageWriterSpi used to understand how information
 * regarding format name, suffices and mime types get passed to ImageIO static
 * methods
 */
public class ch5ImageWriterSpi extends ImageWriterSpi {
  static final String[] suffixes = { "ch5", "CH5" };
  static final String[] names = { "ch5" };
  static final String[] MIMETypes = { "image/ch5" };
  static final String version = "1.00";
  static final String writerClassName = "ch5.imageio.plugins.ch5ImageWriter";
  static final String vendorName = "Company Name";
  static final String[] readerSpiNames = { "ch5.imagio.plugins.ch5ImageReaderSpi" };
  /*
   * static final String nativeStreamMetadataFormatName =
   * "ch5.imageio.ch5stream_1.0"; static final String[]
   * streamMetadataFormatNames = {nativeStreamMetadataFormatName}; static
   * final String nativeImageMetadataFormatName = "ch5.imageio.ch5image_1.0";
   * static final String[] imageMetadataFormatNames =
   * {nativeImageMetadataFormatName};
   */
  static final String nativeStreamMetadataFormatName = "ch5.imageio.ch5stream_1.00";
  static final String nativeStreamMetadataFormatClassName = "ch5.imageio.ch5stream";
  static final String[] extraStreamMetadataFormatNames = { null };
  static final String[] extraStreamMetadataFormatClassNames = { null };
  static final String nativeImageMetadataFormatName = "ch5.imageio.ch5image_1.00";
  static final String nativeImageMetadataFormatClassName = "ch5.imageio.ch5image";
  static final String[] extraImageMetadataFormatNames = { null };
  static final String[] extraImageMetadataFormatClassNames = { null };
  public ch5ImageWriterSpi() {
    super(vendorName, version, names, suffixes, MIMETypes, writerClassName,
        STANDARD_OUTPUT_TYPE, readerSpiNames, false,
        nativeStreamMetadataFormatName,
        nativeStreamMetadataFormatClassName,
        extraStreamMetadataFormatNames,
        extraStreamMetadataFormatClassNames, false,
        nativeImageMetadataFormatName,
        nativeImageMetadataFormatClassName,
        extraImageMetadataFormatNames,
        extraImageMetadataFormatClassNames);
  }
  public String getDescription(Locale locale) {
    return "Demo ch5 image writer, version " + version;
  }
  public ImageWriter createWriterInstance(Object extension) {
    return new ch5ImageWriter(this);
  }
  /**
   * This method gets called when an application wants to see if the
   * corresponding ImageWriter can encode an image with a ColorModel and
   * SampleModel specified by the ImageTypeSpecifier
   */
  public boolean canEncodeImage(ImageTypeSpecifier its) {
    if (its.getBufferedImageType() == BufferedImage.TYPE_BYTE_GRAY)
      return true;
    else
      return false;
  }
}
class ch5v1ImageWriter extends ImageWriter {
  public ch5v1ImageWriter(ImageWriterSpi originatingProvider) {
    super(originatingProvider);
    streamMetadataWritten = false;
  }
  /**
   * this method returns null for now. We will revisit it at the end of this
   * chapter after metadata has been discussed.
   */
  public IIOMetadata convertImageMetadata(IIOMetadata metadata,
      ImageTypeSpecifier its, ImageWriteParam param) {
    return null;
  }
  /**
   * this method returns null for now. We will revisit it at the end of this
   * chapter after metadata has been discussed.
   */
  public IIOMetadata convertStreamMetadata(IIOMetadata metadata,
      ImageWriteParam param) {
    return null;
  }
  /**
   * this method returns null for now. We will revisit it at the end of this
   * chapter after metadata has been discussed.
   */
  public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier its,
      ImageWriteParam param) {
    return null;
  }
  /**
   * this method returns null for now. We will revisit it at the end of this
   * chapter after metadata has been discussed.
   */
  public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
    return null;
  }
  /**
   * 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. Not using metadata in this version, so using
     * default value of 1 image per output stream.
     */
    if (streamMetadataWritten == false) {
      try {
        ios.writeBytes("5\n");
        ios.writeBytes("1");
        ios.flush();
      } catch (IOException ioe) {
        System.err.println("IOException " + ioe.getMessage());
      }
      streamMetadataWritten = true;
    }
    int sourceWidth = raster.getWidth();
    int sourceHeight = raster.getHeight();
    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;
}
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;
}

/**
 * ch5ImageMetadata.java -- holds image metadata for the ch5 format.
 * The internal tree for holding this metadata is read only
 */
class ch5ImageMetadata extends IIOMetadata {
    static final String
        nativeMetadataFormatName = "ch5.imageio.ch5image_1.00";
    static final String
        nativeMetadataFormatClassName = "ch5.imageio.ch5image";
    static final String[] extraMetadataFormatNames = null;
    static final String[] extraMetadataFormatClassNames = null;
    static final boolean standardMetadataFormatSupported = false;
    public int imageWidth;
    public int imageHeight;
    public ch5ImageMetadata() {
  super(standardMetadataFormatSupported,
        nativeMetadataFormatName, 
        nativeMetadataFormatClassName,
        extraMetadataFormatNames,
        extraMetadataFormatClassNames
        );
  imageWidth = -1;
  imageHeight = -1;
    }
    
    public boolean isReadOnly() {
        return true;
    }
    /**
     * IIOMetadataFormat objects are meant to describe the structure of
     * metadata returned from the getAsTree method.  In this case,
     * no such description is available
     */
    public IIOMetadataFormat getMetadataFormat(String formatName) {
        if (formatName.equals(nativeMetadataFormatName)) {
            return null;
        } else {
            throw new IllegalArgumentException("Unrecognized format!");
        }
    }
    /**
     * returns the image metadata in a tree corresponding to the
     * provided formatName
     */
    public Node getAsTree(String formatName) {
        if (formatName.equals(nativeMetadataFormatName)) {
            return getNativeTree();
        } else {
            throw new IllegalArgumentException("Unrecognized format!");
        }
    }
    /**
     * returns the image metadata in a tree using the following format
     * <!ELEMENT ch5.imageio.ch5image_1.00 (imageDimensions)>
     * <!ATTLIST imageDimensions
     *      imageWidth   CDATA  #REQUIRED
     *      imageHeight  CDATA  #REQUIRED
     */
    private Node getNativeTree() {
        IIOMetadataNode root =
            new IIOMetadataNode(nativeMetadataFormatName);
        IIOMetadataNode node = new IIOMetadataNode("imageDimensions");
        node.setAttribute("imageWidth", Integer.toString(imageWidth));
        node.setAttribute("imageHeight", Integer.toString(imageHeight));
        root.appendChild(node);
        return root;
    }
    public void setFromTree(String formatName, Node root) {
        throw new IllegalStateException("Metadata is read-only!");
    }
    public void mergeTree(String formatName, Node root) {
        throw new IllegalStateException("Metadata is read-only!");
    }
    public void reset() {
        throw new IllegalStateException("Metadata is read-only!");
    }
    /**
     * initialize the image metadata elements width and height
     */
    public void initialize(int width, int height) {
  imageWidth = width;
  imageHeight = height;
    }
}

/**
 * ch5StreamMetadata.java -- holds stream metadata for the ch5 format. The
 * internal tree for holding this metadata is read only
 */
class ch5StreamMetadata extends IIOMetadata {
  static final String nativeMetadataFormatName = "ch5.imageio.ch5stream_1.00";
  static final String nativeMetadataFormatClassName = "ch5.imageio.ch5stream";
  static final String[] extraMetadataFormatNames = null;
  static final String[] extraMetadataFormatClassNames = null;
  static final boolean standardMetadataFormatSupported = false;
  public int numberImages;
  public ch5StreamMetadata() {
    super(standardMetadataFormatSupported, nativeMetadataFormatName,
        nativeMetadataFormatClassName, extraMetadataFormatNames,
        extraMetadataFormatClassNames);
    numberImages = -1;
  }
  public boolean isReadOnly() {
    return true;
  }
  /**
   * IIOMetadataFormat objects are meant to describe the structure of metadata
   * returned from the getAsTree method. In this case, no such description is
   * available
   */
  public IIOMetadataFormat getMetadataFormat(String formatName) {
    if (formatName.equals(nativeMetadataFormatName)) {
      return null;
    } else {
      throw new IllegalArgumentException("Unrecognized format!");
    }
  }
  /**
   * returns the stream metadata in a tree corresponding to the provided
   * formatName
   */
  public Node getAsTree(String formatName) {
    if (formatName.equals(nativeMetadataFormatName)) {
      return getNativeTree();
    } else {
      throw new IllegalArgumentException("Unrecognized format!");
    }
  }
  /**
   * returns the stream metadata in a tree using the following format
   * <!ELEMENT ch5.imageio.ch5stream_1.00 (imageDimensions)> <!ATTLIST
   * imageDimensions numberImages CDATA #REQUIRED
   */
  private Node getNativeTree() {
    IIOMetadataNode node; // scratch node
    IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
    // Image descriptor
    node = new IIOMetadataNode("imageDimensions");
    node.setAttribute("numberImages", Integer.toString(numberImages));
    root.appendChild(node);
    return root;
  }
  public void setFromTree(String formatName, Node root) {
    throw new IllegalStateException("Metadata is read-only!");
  }
  public void mergeTree(String formatName, Node root) {
    throw new IllegalStateException("Metadata is read-only!");
  }
  public void reset() {
    throw new IllegalStateException("Metadata is read-only!");
  }
  /**
   * initialize the stream metadata element numberImages
   */
  public void initialize(int numberImages) {
    this.numberImages = numberImages;
  }
}



Thumbnail Generator

   
/*
 *
 * Part of the InfoGlue Content Management Platform (www.infoglue.org)
 *
 * 
 *
 *  Copyright (C)
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License version 2, as published by the
 * Free Software Foundation. See the file LICENSE.html for more information.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY, including 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
 * this program; if not, write to the Free Software Foundation, Inc. / 59 Temple
 * Place, Suite 330 / Boston, MA 02111-1307 / USA.
 *
 * 
 */

import java.awt.*;
import java.awt.image.*;
import java.io.*;
public class ThumbnailGenerator 
{
  public ThumbnailGenerator()
  {
  }
  
  public void transform(String originalFile, String thumbnailFile, int thumbWidth, int thumbHeight, int quality) throws Exception 
  {
    Image image = javax.imageio.ImageIO.read(new File(originalFile));
      
      double thumbRatio = (double)thumbWidth / (double)thumbHeight;
      int imageWidth    = image.getWidth(null);
      int imageHeight   = image.getHeight(null);
      double imageRatio = (double)imageWidth / (double)imageHeight;
      if (thumbRatio < imageRatio) 
      {
        thumbHeight = (int)(thumbWidth / imageRatio);
      } 
      else 
      {
          thumbWidth = (int)(thumbHeight * imageRatio);
      }
      
    if(imageWidth < thumbWidth && imageHeight < thumbHeight)
    {
      thumbWidth = imageWidth;
      thumbHeight = imageHeight;
    }
    else if(imageWidth < thumbWidth)
      thumbWidth = imageWidth;
    else if(imageHeight < thumbHeight)
      thumbHeight = imageHeight;
      BufferedImage thumbImage = new BufferedImage(thumbWidth, thumbHeight, BufferedImage.TYPE_INT_RGB);
      Graphics2D graphics2D = thumbImage.createGraphics();
      graphics2D.setBackground(Color.WHITE);
      graphics2D.setPaint(Color.WHITE); 
      graphics2D.fillRect(0, 0, thumbWidth, thumbHeight);
      graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
      graphics2D.drawImage(image, 0, 0, thumbWidth, thumbHeight, null);
      
    javax.imageio.ImageIO.write(thumbImage, "JPG", new File(thumbnailFile));
  }
}



Using mediatracker to pre-load images

    
import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Toolkit;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main extends JFrame {
  Main() {
    add(BorderLayout.CENTER, new ImagePanel());
    setSize(800, 150);
  }
  public static void main(String[] args) {
    Main jrframe = new Main();
    jrframe.setVisible(true);
  }
}
class ImagePanel extends JPanel {
  String images[] = { "i.png", "j.png" };
  Image[] imgs = new Image[images.length];
  ImagePanel() {
    MediaTracker mt = new MediaTracker(this);
    for (int i = 0; i < images.length; i++) {
      imgs[i] = Toolkit.getDefaultToolkit().getImage(images[i]);
      mt.addImage(imgs[i], i);
    }
    try {
      mt.waitForAll();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
  protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    int x = 0;
    int y = 0;
    for (int i = 0; i < imgs.length; i++) {
      g.drawImage(imgs[i], x, y, null);
      x += imgs[i].getWidth(null);
    }
  }
}



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



Write Image with different types

   
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
public class Main {
  static public void main(String args[]) throws Exception {
    int width = 200, height = 200;
    BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2d = bi.createGraphics();
    g2d.fillRect(0, 0, width - 1, height - 1);
    BasicStroke stroke = new BasicStroke(10, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
    g2d.setPaint(Color.lightGray);
    g2d.setStroke(stroke);
    g2d.draw(new Ellipse2D.Double(0, 0, 100, 100));
    ImageIO.write(bi, "GIF", new File("a.gif"));
  }
}