/*
 * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package java.awt.image;

import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Hashtable;
import java.util.Set;
import java.util.Vector;

import sun.awt.image.ByteComponentRaster;
import sun.awt.image.BytePackedRaster;
import sun.awt.image.IntegerComponentRaster;
import sun.awt.image.OffScreenImageSource;
import sun.awt.image.ShortComponentRaster;

/**
 *
 * The <code>BufferedImage</code> subclass describes an {@link
 * java.awt.Image Image} with an accessible buffer of image data.
 * A <code>BufferedImage</code> is comprised of a {@link ColorModel} and a
 * {@link Raster} of image data.
 * The number and types of bands in the {@link SampleModel} of the
 * <code>Raster</code> must match the number and types required by the
 * <code>ColorModel</code> to represent its color and alpha components.
 * All <code>BufferedImage</code> objects have an upper left corner
 * coordinate of (0,&nbsp;0).  Any <code>Raster</code> used to construct a
 * <code>BufferedImage</code> must therefore have minX=0 and minY=0.
 *
 * <p>
 * This class relies on the data fetching and setting methods
 * of <code>Raster</code>,
 * and on the color characterization methods of <code>ColorModel</code>.
 *
 * @see ColorModel
 * @see Raster
 * @see WritableRaster
 */
public class BufferedImage extends java.awt.Image
                           implements WritableRenderedImage, Transparency
{
    private int imageType = TYPE_CUSTOM;
    private ColorModel colorModel;
    private final WritableRaster raster;
    private OffScreenImageSource osis;
    private Hashtable<String, Object> properties;

    /**
     * Image Type Constants
     */

    /**
     * Image type is not recognized so it must be a customized
     * image.  This type is only used as a return value for the getType()
     * method.
     */
    public static final int TYPE_CUSTOM = 0;

    /**
     * Represents an image with 8-bit RGB color components packed into
     * integer pixels.  The image has a {@link DirectColorModel} without
     * alpha.
     * When data with non-opaque alpha is stored
     * in an image of this type,
     * the color data must be adjusted to a non-premultiplied form
     * and the alpha discarded,
     * as described in the
     * {@link java.awt.AlphaComposite} documentation.
     */
    public static final int TYPE_INT_RGB = 1;

    /**
     * Represents an image with 8-bit RGBA color components packed into
     * integer pixels.  The image has a <code>DirectColorModel</code>
     * with alpha. The color data in this image is considered not to be
     * premultiplied with alpha.  When this type is used as the
     * <code>imageType</code> argument to a <code>BufferedImage</code>
     * constructor, the created image is consistent with images
     * created in the JDK1.1 and earlier releases.
     */
    public static final int TYPE_INT_ARGB = 2;

    /**
     * Represents an image with 8-bit RGBA color components packed into
     * integer pixels.  The image has a <code>DirectColorModel</code>
     * with alpha.  The color data in this image is considered to be
     * premultiplied with alpha.
     */
    public static final int TYPE_INT_ARGB_PRE = 3;

    /**
     * Represents an image with 8-bit RGB color components, corresponding
     * to a Windows- or Solaris- style BGR color model, with the colors
     * Blue, Green, and Red packed into integer pixels.  There is no alpha.
     * The image has a {@link DirectColorModel}.
     * When data with non-opaque alpha is stored
     * in an image of this type,
     * the color data must be adjusted to a non-premultiplied form
     * and the alpha discarded,
     * as described in the
     * {@link java.awt.AlphaComposite} documentation.
     */
    public static final int TYPE_INT_BGR = 4;

    /**
     * Represents an image with 8-bit RGB color components, corresponding
     * to a Windows-style BGR color model) with the colors Blue, Green,
     * and Red stored in 3 bytes.  There is no alpha.  The image has a
     * <code>ComponentColorModel</code>.
     * When data with non-opaque alpha is stored
     * in an image of this type,
     * the color data must be adjusted to a non-premultiplied form
     * and the alpha discarded,
     * as described in the
     * {@link java.awt.AlphaComposite} documentation.
     */
    public static final int TYPE_3BYTE_BGR = 5;

    /**
     * Represents an image with 8-bit RGBA color components with the colors
     * Blue, Green, and Red stored in 3 bytes and 1 byte of alpha.  The
     * image has a <code>ComponentColorModel</code> with alpha.  The
     * color data in this image is considered not to be premultiplied with
     * alpha.  The byte data is interleaved in a single
     * byte array in the order A, B, G, R
     * from lower to higher byte addresses within each pixel.
     */
    public static final int TYPE_4BYTE_ABGR = 6;

    /**
     * Represents an image with 8-bit RGBA color components with the colors
     * Blue, Green, and Red stored in 3 bytes and 1 byte of alpha.  The
     * image has a <code>ComponentColorModel</code> with alpha. The color
     * data in this image is considered to be premultiplied with alpha.
     * The byte data is interleaved in a single byte array in the order
     * A, B, G, R from lower to higher byte addresses within each pixel.
     */
    public static final int TYPE_4BYTE_ABGR_PRE = 7;

    /**
     * Represents an image with 5-6-5 RGB color components (5-bits red,
     * 6-bits green, 5-bits blue) with no alpha.  This image has
     * a <code>DirectColorModel</code>.
     * When data with non-opaque alpha is stored
     * in an image of this type,
     * the color data must be adjusted to a non-premultiplied form
     * and the alpha discarded,
     * as described in the
     * {@link java.awt.AlphaComposite} documentation.
     */
    public static final int TYPE_USHORT_565_RGB = 8;

    /**
     * Represents an image with 5-5-5 RGB color components (5-bits red,
     * 5-bits green, 5-bits blue) with no alpha.  This image has
     * a <code>DirectColorModel</code>.
     * When data with non-opaque alpha is stored
     * in an image of this type,
     * the color data must be adjusted to a non-premultiplied form
     * and the alpha discarded,
     * as described in the
     * {@link java.awt.AlphaComposite} documentation.
     */
    public static final int TYPE_USHORT_555_RGB = 9;

    /**
     * Represents a unsigned byte grayscale image, non-indexed.  This
     * image has a <code>ComponentColorModel</code> with a CS_GRAY
     * {@link ColorSpace}.
     * When data with non-opaque alpha is stored
     * in an image of this type,
     * the color data must be adjusted to a non-premultiplied form
     * and the alpha discarded,
     * as described in the
     * {@link java.awt.AlphaComposite} documentation.
     */
    public static final int TYPE_BYTE_GRAY = 10;

    /**
     * Represents an unsigned short grayscale image, non-indexed).  This
     * image has a <code>ComponentColorModel</code> with a CS_GRAY
     * <code>ColorSpace</code>.
     * When data with non-opaque alpha is stored
     * in an image of this type,
     * the color data must be adjusted to a non-premultiplied form
     * and the alpha discarded,
     * as described in the
     * {@link java.awt.AlphaComposite} documentation.
     */
    public static final int TYPE_USHORT_GRAY = 11;

    /**
     * Represents an opaque byte-packed 1, 2, or 4 bit image.  The
     * image has an {@link IndexColorModel} without alpha.  When this
     * type is used as the <code>imageType</code> argument to the
     * <code>BufferedImage</code> constructor that takes an
     * <code>imageType</code> argument but no <code>ColorModel</code>
     * argument, a 1-bit image is created with an
     * <code>IndexColorModel</code> with two colors in the default
     * sRGB <code>ColorSpace</code>: {0,&nbsp;0,&nbsp;0} and
     * {255,&nbsp;255,&nbsp;255}.
     *
     * <p> Images with 2 or 4 bits per pixel may be constructed via
     * the <code>BufferedImage</code> constructor that takes a
     * <code>ColorModel</code> argument by supplying a
     * <code>ColorModel</code> with an appropriate map size.
     *
     * <p> Images with 8 bits per pixel should use the image types
     * <code>TYPE_BYTE_INDEXED</code> or <code>TYPE_BYTE_GRAY</code>
     * depending on their <code>ColorModel</code>.

     * <p> When color data is stored in an image of this type,
     * the closest color in the colormap is determined
     * by the <code>IndexColorModel</code> and the resulting index is stored.
     * Approximation and loss of alpha or color components
     * can result, depending on the colors in the
     * <code>IndexColorModel</code> colormap.
     */
    public static final int TYPE_BYTE_BINARY = 12;

    /**
     * Represents an indexed byte image.  When this type is used as the
     * <code>imageType</code> argument to the <code>BufferedImage</code>
     * constructor that takes an <code>imageType</code> argument
     * but no <code>ColorModel</code> argument, an
     * <code>IndexColorModel</code> is created with
     * a 256-color 6/6/6 color cube palette with the rest of the colors
     * from 216-255 populated by grayscale values in the
     * default sRGB ColorSpace.
     *
     * <p> When color data is stored in an image of this type,
     * the closest color in the colormap is determined
     * by the <code>IndexColorModel</code> and the resulting index is stored.
     * Approximation and loss of alpha or color components
     * can result, depending on the colors in the
     * <code>IndexColorModel</code> colormap.
     */
    public static final int TYPE_BYTE_INDEXED = 13;

    private static final int DCM_RED_MASK   = 0x00ff0000;
    private static final int DCM_GREEN_MASK = 0x0000ff00;
    private static final int DCM_BLUE_MASK  = 0x000000ff;
    private static final int DCM_ALPHA_MASK = 0xff000000;
    private static final int DCM_565_RED_MASK = 0xf800;
    private static final int DCM_565_GRN_MASK = 0x07E0;
    private static final int DCM_565_BLU_MASK = 0x001F;
    private static final int DCM_555_RED_MASK = 0x7C00;
    private static final int DCM_555_GRN_MASK = 0x03E0;
    private static final int DCM_555_BLU_MASK = 0x001F;
    private static final int DCM_BGR_RED_MASK = 0x0000ff;
    private static final int DCM_BGR_GRN_MASK = 0x00ff00;
    private static final int DCM_BGR_BLU_MASK = 0xff0000;


    static private native void initIDs();
    static {
        ColorModel.loadLibraries();
        initIDs();
    }

    /**
     * Constructs a <code>BufferedImage</code> of one of the predefined
     * image types.  The <code>ColorSpace</code> for the image is the
     * default sRGB space.
     * @param width     width of the created image
     * @param height    height of the created image
     * @param imageType type of the created image
     * @see ColorSpace
     * @see #TYPE_INT_RGB
     * @see #TYPE_INT_ARGB
     * @see #TYPE_INT_ARGB_PRE
     * @see #TYPE_INT_BGR
     * @see #TYPE_3BYTE_BGR
     * @see #TYPE_4BYTE_ABGR
     * @see #TYPE_4BYTE_ABGR_PRE
     * @see #TYPE_BYTE_GRAY
     * @see #TYPE_USHORT_GRAY
     * @see #TYPE_BYTE_BINARY
     * @see #TYPE_BYTE_INDEXED
     * @see #TYPE_USHORT_565_RGB
     * @see #TYPE_USHORT_555_RGB
     */
    public BufferedImage(int width,
                         int height,
                         int imageType) {
        switch (imageType) {
        case TYPE_INT_RGB:
            {
                colorModel = new DirectColorModel(24,
                                                  0x00ff0000,   // Red
                                                  0x0000ff00,   // Green
                                                  0x000000ff,   // Blue
                                                  0x0           // Alpha
                                                  );
                raster = colorModel.createCompatibleWritableRaster(width,
                                                                   height);
            }
        break;

        case TYPE_INT_ARGB:
            {
                colorModel = ColorModel.getRGBdefault();

                raster = colorModel.createCompatibleWritableRaster(width,
                                                                   height);
            }
        break;

        case TYPE_INT_ARGB_PRE:
            {
                colorModel = new
                    DirectColorModel(
                                     ColorSpace.getInstance(ColorSpace.CS_sRGB),
                                     32,
                                     0x00ff0000,// Red
                                     0x0000ff00,// Green
                                     0x000000ff,// Blue
                                     0xff000000,// Alpha
                                     true,       // Alpha Premultiplied
                                     DataBuffer.TYPE_INT
                                     );
                raster = colorModel.createCompatibleWritableRaster(width,
                                                                   height);
            }
        break;

        case TYPE_INT_BGR:
            {
                colorModel = new DirectColorModel(24,
                                                  0x000000ff,   // Red
                                                  0x0000ff00,   // Green
                                                  0x00ff0000    // Blue
                                                  );
                raster = colorModel.createCompatibleWritableRaster(width,
                                                                   height);
            }
        break;

        case TYPE_3BYTE_BGR:
            {
                ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
                int[] nBits = {8, 8, 8};
                int[] bOffs = {2, 1, 0};
                colorModel = new ComponentColorModel(cs, nBits, false, false,
                                                     Transparency.OPAQUE,
                                                     DataBuffer.TYPE_BYTE);
                raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
                                                        width, height,
                                                        width*3, 3,
                                                        bOffs, null);
            }
        break;

        case TYPE_4BYTE_ABGR:
            {
                ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
                int[] nBits = {8, 8, 8, 8};
                int[] bOffs = {3, 2, 1, 0};
                colorModel = new ComponentColorModel(cs, nBits, true, false,
                                                     Transparency.TRANSLUCENT,
                                                     DataBuffer.TYPE_BYTE);
                raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
                                                        width, height,
                                                        width*4, 4,
                                                        bOffs, null);
            }
        break;

        case TYPE_4BYTE_ABGR_PRE:
            {
                ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
                int[] nBits = {8, 8, 8, 8};
                int[] bOffs = {3, 2, 1, 0};
                colorModel = new ComponentColorModel(cs, nBits, true, true,
                                                     Transparency.TRANSLUCENT,
                                                     DataBuffer.TYPE_BYTE);
                raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
                                                        width, height,
                                                        width*4, 4,
                                                        bOffs, null);
            }
        break;

        case TYPE_BYTE_GRAY:
            {
                ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
                int[] nBits = {8};
                colorModel = new ComponentColorModel(cs, nBits, false, true,
                                                     Transparency.OPAQUE,
                                                     DataBuffer.TYPE_BYTE);
                raster = colorModel.createCompatibleWritableRaster(width,
                                                                   height);
            }
        break;

        case TYPE_USHORT_GRAY:
            {
                ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
                int[] nBits = {16};
                colorModel = new ComponentColorModel(cs, nBits, false, true,
                                                     Transparency.OPAQUE,
                                                     DataBuffer.TYPE_USHORT);
                raster = colorModel.createCompatibleWritableRaster(width,
                                                                   height);
            }
        break;

        case TYPE_BYTE_BINARY:
            {
                byte[] arr = {(byte)0, (byte)0xff};

                colorModel = new IndexColorModel(1, 2, arr, arr, arr);
                raster = Raster.createPackedRaster(DataBuffer.TYPE_BYTE,
                                                   width, height, 1, 1, null);
            }
        break;

        case TYPE_BYTE_INDEXED:
            {
                // Create a 6x6x6 color cube
                int[] cmap = new int[256];
                int i=0;
                for (int r=0; r < 256; r += 51) {
                    for (int g=0; g < 256; g += 51) {
                        for (int b=0; b < 256; b += 51) {
                            cmap[i++] = (r<<16)|(g<<8)|b;
                        }
                    }
                }
                // And populate the rest of the cmap with gray values
                int grayIncr = 256/(256-i);

                // The gray ramp will be between 18 and 252
                int gray = grayIncr*3;
                for (; i < 256; i++) {
                    cmap[i] = (gray<<16)|(gray<<8)|gray;
                    gray += grayIncr;
                }

                colorModel = new IndexColorModel(8, 256, cmap, 0, false, -1,
                                                 DataBuffer.TYPE_BYTE);
                raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
                                                      width, height, 1, null);
            }
        break;

        case TYPE_USHORT_565_RGB:
            {
                colorModel = new DirectColorModel(16,
                                                  DCM_565_RED_MASK,
                                                  DCM_565_GRN_MASK,
                                                  DCM_565_BLU_MASK
                                                  );
                raster = colorModel.createCompatibleWritableRaster(width,
                                                                   height);
            }
            break;

        case TYPE_USHORT_555_RGB:
            {
                colorModel = new DirectColorModel(15,
                                                  DCM_555_RED_MASK,
                                                  DCM_555_GRN_MASK,
                                                  DCM_555_BLU_MASK
                                                  );
                raster = colorModel.createCompatibleWritableRaster(width,
                                                                   height);
            }
            break;

        default:
            throw new IllegalArgumentException ("Unknown image type " +
                                                imageType);
        }

        this.imageType = imageType;
    }

    /**
     * Constructs a <code>BufferedImage</code> of one of the predefined
     * image types:
     * TYPE_BYTE_BINARY or TYPE_BYTE_INDEXED.
     *
     * <p> If the image type is TYPE_BYTE_BINARY, the number of
     * entries in the color model is used to determine whether the
     * image should have 1, 2, or 4 bits per pixel.  If the color model
     * has 1 or 2 entries, the image will have 1 bit per pixel.  If it
     * has 3 or 4 entries, the image with have 2 bits per pixel.  If
     * it has between 5 and 16 entries, the image will have 4 bits per
     * pixel.  Otherwise, an IllegalArgumentException will be thrown.
     *
     * @param width     width of the created image
     * @param height    height of the created image
     * @param imageType type of the created image
     * @param cm        <code>IndexColorModel</code> of the created image
     * @throws IllegalArgumentException   if the imageType is not
     * TYPE_BYTE_BINARY or TYPE_BYTE_INDEXED or if the imageType is
     * TYPE_BYTE_BINARY and the color map has more than 16 entries.
     * @see #TYPE_BYTE_BINARY
     * @see #TYPE_BYTE_INDEXED
     */
    public BufferedImage (int width,
                          int height,
                          int imageType,
                          IndexColorModel cm) {
        if (cm.hasAlpha() && cm.isAlphaPremultiplied()) {
            throw new IllegalArgumentException("This image types do not have "+
                                               "premultiplied alpha.");
        }

        switch(imageType) {
        case TYPE_BYTE_BINARY:
            int bits; // Will be set below
            int mapSize = cm.getMapSize();
            if (mapSize <= 2) {
                bits = 1;
            } else if (mapSize <= 4) {
                bits = 2;
            } else if (mapSize <= 16) {
                bits = 4;
            } else {
                throw new IllegalArgumentException
                    ("Color map for TYPE_BYTE_BINARY " +
                     "must have no more than 16 entries");
            }
            raster = Raster.createPackedRaster(DataBuffer.TYPE_BYTE,
                                                width, height, 1, bits, null);
            break;

        case TYPE_BYTE_INDEXED:
            raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
                                                    width, height, 1, null);
            break;
        default:
            throw new IllegalArgumentException("Invalid image type (" +
                                               imageType+").  Image type must"+
                                               " be either TYPE_BYTE_BINARY or "+
                                               " TYPE_BYTE_INDEXED");
        }

        if (!cm.isCompatibleRaster(raster)) {
            throw new IllegalArgumentException("Incompatible image type and IndexColorModel");
        }

        colorModel = cm;
        this.imageType = imageType;
    }

    /**
     * Constructs a new <code>BufferedImage</code> with a specified
     * <code>ColorModel</code> and <code>Raster</code>.  If the number and
     * types of bands in the <code>SampleModel</code> of the
     * <code>Raster</code> do not match the number and types required by
     * the <code>ColorModel</code> to represent its color and alpha
     * components, a {@link RasterFormatException} is thrown.  This
     * method can multiply or divide the color <code>Raster</code> data by
     * alpha to match the <code>alphaPremultiplied</code> state
     * in the <code>ColorModel</code>.  Properties for this
     * <code>BufferedImage</code> can be established by passing
     * in a {@link Hashtable} of <code>String</code>/<code>Object</code>
     * pairs.
     * @param cm <code>ColorModel</code> for the new image
     * @param raster     <code>Raster</code> for the image data
     * @param isRasterPremultiplied   if <code>true</code>, the data in
     *                  the raster has been premultiplied with alpha.
     * @param properties <code>Hashtable</code> of
     *                  <code>String</code>/<code>Object</code> pairs.
     * @exception RasterFormatException if the number and
     * types of bands in the <code>SampleModel</code> of the
     * <code>Raster</code> do not match the number and types required by
     * the <code>ColorModel</code> to represent its color and alpha
     * components.
     * @exception IllegalArgumentException if
     *          <code>raster</code> is incompatible with <code>cm</code>
     * @see ColorModel
     * @see Raster
     * @see WritableRaster
     */


/*
 *
 *  FOR NOW THE CODE WHICH DEFINES THE RASTER TYPE IS DUPLICATED BY DVF
 *  SEE THE METHOD DEFINERASTERTYPE @ RASTEROUTPUTMANAGER
 *
 */
    public BufferedImage (ColorModel cm,
                          WritableRaster raster,
                          boolean isRasterPremultiplied,
                          Hashtable<?,?> properties) {

        if (!cm.isCompatibleRaster(raster)) {
            throw new
                IllegalArgumentException("Raster "+raster+
                                         " is incompatible with ColorModel "+
                                         cm);
        }

        if ((raster.minX != 0) || (raster.minY != 0)) {
            throw new
                IllegalArgumentException("Raster "+raster+
                                         " has minX or minY not equal to zero: "
                                         + raster.minX + " " + raster.minY);
        }

        colorModel = cm;
        this.raster  = raster;
        if (properties != null && !properties.isEmpty()) {
            this.properties = new Hashtable<>();
            for (final Object key : properties.keySet()) {
                if (key instanceof String) {
                    this.properties.put((String) key, properties.get(key));
                }
            }
        }
        int numBands = raster.getNumBands();
        boolean isAlphaPre = cm.isAlphaPremultiplied();
        final boolean isStandard = isStandard(cm, raster);
        ColorSpace cs;

        // Force the raster data alpha state to match the premultiplied
        // state in the color model
        coerceData(isRasterPremultiplied);

        SampleModel sm = raster.getSampleModel();
        cs = cm.getColorSpace();
        int csType = cs.getType();
        if (csType != ColorSpace.TYPE_RGB) {
            if (csType == ColorSpace.TYPE_GRAY &&
                isStandard &&
                cm instanceof ComponentColorModel) {
                // Check if this might be a child raster (fix for bug 4240596)
                if (sm instanceof ComponentSampleModel &&
                    ((ComponentSampleModel)sm).getPixelStride() != numBands) {
                    imageType = TYPE_CUSTOM;
                } else if (raster instanceof ByteComponentRaster &&
                       raster.getNumBands() == 1 &&
                       cm.getComponentSize(0) == 8 &&
                       ((ByteComponentRaster)raster).getPixelStride() == 1) {
                    imageType = TYPE_BYTE_GRAY;
                } else if (raster instanceof ShortComponentRaster &&
                       raster.getNumBands() == 1 &&
                       cm.getComponentSize(0) == 16 &&
                       ((ShortComponentRaster)raster).getPixelStride() == 1) {
                    imageType = TYPE_USHORT_GRAY;
                }
            } else {
                imageType = TYPE_CUSTOM;
            }
            return;
        }

        if ((raster instanceof IntegerComponentRaster) &&
            (numBands == 3 || numBands == 4)) {
            IntegerComponentRaster iraster =
                (IntegerComponentRaster) raster;
            // Check if the raster params and the color model
            // are correct
            int pixSize = cm.getPixelSize();
            if (iraster.getPixelStride() == 1 &&
                isStandard &&
                cm instanceof DirectColorModel  &&
                (pixSize == 32 || pixSize == 24))
            {
                // Now check on the DirectColorModel params
                DirectColorModel dcm = (DirectColorModel) cm;
                int rmask = dcm.getRedMask();
                int gmask = dcm.getGreenMask();
                int bmask = dcm.getBlueMask();
                if (rmask == DCM_RED_MASK && gmask == DCM_GREEN_MASK &&
                    bmask == DCM_BLUE_MASK)
                {
                    if (dcm.getAlphaMask() == DCM_ALPHA_MASK) {
                        imageType = (isAlphaPre
                                     ? TYPE_INT_ARGB_PRE
                                     : TYPE_INT_ARGB);
                    }
                    else {
                        // No Alpha
                        if (!dcm.hasAlpha()) {
                            imageType = TYPE_INT_RGB;
                        }
                    }
                }   // if (dcm.getRedMask() == DCM_RED_MASK &&
                else if (rmask == DCM_BGR_RED_MASK && gmask == DCM_BGR_GRN_MASK
                         && bmask == DCM_BGR_BLU_MASK) {
                    if (!dcm.hasAlpha()) {
                        imageType = TYPE_INT_BGR;
                    }
                }  // if (rmask == DCM_BGR_RED_MASK &&
            }   // if (iraster.getPixelStride() == 1
        }   // ((raster instanceof IntegerComponentRaster) &&
        else if ((cm instanceof IndexColorModel) && (numBands == 1) &&
                 isStandard &&
                 (!cm.hasAlpha() || !isAlphaPre))
        {
            IndexColorModel icm = (IndexColorModel) cm;
            int pixSize = icm.getPixelSize();

            if (raster instanceof BytePackedRaster) {
                imageType = TYPE_BYTE_BINARY;
            }   // if (raster instanceof BytePackedRaster)
            else if (raster instanceof ByteComponentRaster) {
                ByteComponentRaster braster = (ByteComponentRaster) raster;
                if (braster.getPixelStride() == 1 && pixSize <= 8) {
                    imageType = TYPE_BYTE_INDEXED;
                }
            }
        }   // else if (cm instanceof IndexColorModel) && (numBands == 1))
        else if ((raster instanceof ShortComponentRaster)
                 && (cm instanceof DirectColorModel)
                 && isStandard
                 && (numBands == 3)
                 && !cm.hasAlpha())
        {
            DirectColorModel dcm = (DirectColorModel) cm;
            if (dcm.getRedMask() == DCM_565_RED_MASK) {
                if (dcm.getGreenMask() == DCM_565_GRN_MASK &&
                    dcm.getBlueMask()  == DCM_565_BLU_MASK) {
                    imageType = TYPE_USHORT_565_RGB;
                }
            }
            else if (dcm.getRedMask() == DCM_555_RED_MASK) {
                if (dcm.getGreenMask() == DCM_555_GRN_MASK &&
                    dcm.getBlueMask() == DCM_555_BLU_MASK) {
                    imageType = TYPE_USHORT_555_RGB;
                }
            }
        }   // else if ((cm instanceof IndexColorModel) && (numBands == 1))
        else if ((raster instanceof ByteComponentRaster)
                 && (cm instanceof ComponentColorModel)
                 && isStandard
                 && (raster.getSampleModel() instanceof PixelInterleavedSampleModel)
                 && (numBands == 3 || numBands == 4))
        {
            ComponentColorModel ccm = (ComponentColorModel) cm;
            PixelInterleavedSampleModel csm =
                (PixelInterleavedSampleModel)raster.getSampleModel();
            ByteComponentRaster braster = (ByteComponentRaster) raster;
            int[] offs = csm.getBandOffsets();
            if (ccm.getNumComponents() != numBands) {
                throw new RasterFormatException("Number of components in "+
                                                "ColorModel ("+
                                                ccm.getNumComponents()+
                                                ") does not match # in "+
                                                " Raster ("+numBands+")");
            }
            int[] nBits = ccm.getComponentSize();
            boolean is8bit = true;
            for (int i=0; i < numBands; i++) {
                if (nBits[i] != 8) {
                    is8bit = false;
                    break;
                }
            }
            if (is8bit &&
                braster.getPixelStride() == numBands &&
                offs[0] == numBands-1 &&
                offs[1] == numBands-2 &&
                offs[2] == numBands-3)
            {
                if (numBands == 3 && !ccm.hasAlpha()) {
                    imageType = TYPE_3BYTE_BGR;
                }
                else if (offs[3] == 0 && ccm.hasAlpha()) {
                    imageType = (isAlphaPre
                                 ? TYPE_4BYTE_ABGR_PRE
                                 : TYPE_4BYTE_ABGR);
                }
            }
        }   // else if ((raster instanceof ByteComponentRaster) &&
    }

    private static boolean isStandard(ColorModel cm, WritableRaster wr) {
        final Class<? extends ColorModel> cmClass = cm.getClass();
        final Class<? extends WritableRaster> wrClass = wr.getClass();
        final Class<? extends SampleModel> smClass = wr.getSampleModel().getClass();

        final PrivilegedAction<Boolean> checkClassLoadersAction =
                new PrivilegedAction<Boolean>()
        {

            @Override
            public Boolean run() {
                final ClassLoader std = System.class.getClassLoader();

                return (cmClass.getClassLoader() == std) &&
                        (smClass.getClassLoader() == std) &&
                        (wrClass.getClassLoader() == std);
            }
        };
        return AccessController.doPrivileged(checkClassLoadersAction);
    }

    /**
     * Returns the image type.  If it is not one of the known types,
     * TYPE_CUSTOM is returned.
     * @return the image type of this <code>BufferedImage</code>.
     * @see #TYPE_INT_RGB
     * @see #TYPE_INT_ARGB
     * @see #TYPE_INT_ARGB_PRE
     * @see #TYPE_INT_BGR
     * @see #TYPE_3BYTE_BGR
     * @see #TYPE_4BYTE_ABGR
     * @see #TYPE_4BYTE_ABGR_PRE
     * @see #TYPE_BYTE_GRAY
     * @see #TYPE_BYTE_BINARY
     * @see #TYPE_BYTE_INDEXED
     * @see #TYPE_USHORT_GRAY
     * @see #TYPE_USHORT_565_RGB
     * @see #TYPE_USHORT_555_RGB
     * @see #TYPE_CUSTOM
     */
    public int getType() {
        return imageType;
    }

    /**
     * Returns the <code>ColorModel</code>.
     * @return the <code>ColorModel</code> of this
     *  <code>BufferedImage</code>.
     */
    public ColorModel getColorModel() {
        return colorModel;
    }

    /**
     * Returns the {@link WritableRaster}.
     * @return the <code>WriteableRaster</code> of this
     *  <code>BufferedImage</code>.
     */
    public WritableRaster getRaster() {
        return raster;
    }


    /**
     * Returns a <code>WritableRaster</code> representing the alpha
     * channel for <code>BufferedImage</code> objects
     * with <code>ColorModel</code> objects that support a separate
     * spatial alpha channel, such as <code>ComponentColorModel</code> and
     * <code>DirectColorModel</code>.  Returns <code>null</code> if there
     * is no alpha channel associated with the <code>ColorModel</code> in
     * this image.  This method assumes that for all
     * <code>ColorModel</code> objects other than
     * <code>IndexColorModel</code>, if the <code>ColorModel</code>
     * supports alpha, there is a separate alpha channel
     * which is stored as the last band of image data.
     * If the image uses an <code>IndexColorModel</code> that
     * has alpha in the lookup table, this method returns
     * <code>null</code> since there is no spatially discrete alpha
     * channel.  This method creates a new
     * <code>WritableRaster</code>, but shares the data array.
     * @return a <code>WritableRaster</code> or <code>null</code> if this
     *          <code>BufferedImage</code> has no alpha channel associated
     *          with its <code>ColorModel</code>.
     */
    public WritableRaster getAlphaRaster() {
        return colorModel.getAlphaRaster(raster);
    }

    /**
     * Returns an integer pixel in the default RGB color model
     * (TYPE_INT_ARGB) and default sRGB colorspace.  Color
     * conversion takes place if this default model does not match
     * the image <code>ColorModel</code>.  There are only 8-bits of
     * precision for each color component in the returned data when using
     * this method.
     *
     * <p>
     *
     * An <code>ArrayOutOfBoundsException</code> may be thrown
     * if the coordinates are not in bounds.
     * However, explicit bounds checking is not guaranteed.
     *
     * @param x the X coordinate of the pixel from which to get
     *          the pixel in the default RGB color model and sRGB
     *          color space
     * @param y the Y coordinate of the pixel from which to get
     *          the pixel in the default RGB color model and sRGB
     *          color space
     * @return an integer pixel in the default RGB color model and
     *          default sRGB colorspace.
     * @see #setRGB(int, int, int)
     * @see #setRGB(int, int, int, int, int[], int, int)
     */
    public int getRGB(int x, int y) {
        return colorModel.getRGB(raster.getDataElements(x, y, null));
    }

    /**
     * Returns an array of integer pixels in the default RGB color model
     * (TYPE_INT_ARGB) and default sRGB color space,
     * from a portion of the image data.  Color conversion takes
     * place if the default model does not match the image
     * <code>ColorModel</code>.  There are only 8-bits of precision for
     * each color component in the returned data when
     * using this method.  With a specified coordinate (x,&nbsp;y) in the
     * image, the ARGB pixel can be accessed in this way:
     *
     * <pre>
     *    pixel   = rgbArray[offset + (y-startY)*scansize + (x-startX)]; </pre>
     *
     * <p>
     *
     * An <code>ArrayOutOfBoundsException</code> may be thrown
     * if the region is not in bounds.
     * However, explicit bounds checking is not guaranteed.
     *
     * @param startX      the starting X coordinate
     * @param startY      the starting Y coordinate
     * @param w           width of region
     * @param h           height of region
     * @param rgbArray    if not <code>null</code>, the rgb pixels are
     *          written here
     * @param offset      offset into the <code>rgbArray</code>
     * @param scansize    scanline stride for the <code>rgbArray</code>
     * @return            array of RGB pixels.
     * @see #setRGB(int, int, int)
     * @see #setRGB(int, int, int, int, int[], int, int)
     */
    public int[] getRGB(int startX, int startY, int w, int h,
                        int[] rgbArray, int offset, int scansize) {
        int yoff  = offset;
        int off;
        Object data;
        int nbands = raster.getNumBands();
        int dataType = raster.getDataBuffer().getDataType();
        switch (dataType) {
        case DataBuffer.TYPE_BYTE:
            data = new byte[nbands];
            break;
        case DataBuffer.TYPE_USHORT:
            data = new short[nbands];
            break;
        case DataBuffer.TYPE_INT:
            data = new int[nbands];
            break;
        case DataBuffer.TYPE_FLOAT:
            data = new float[nbands];
            break;
        case DataBuffer.TYPE_DOUBLE:
            data = new double[nbands];
            break;
        default:
            throw new IllegalArgumentException("Unknown data buffer type: "+
                                               dataType);
        }

        if (rgbArray == null) {
            rgbArray = new int[offset+h*scansize];
        }

        for (int y = startY; y < startY+h; y++, yoff+=scansize) {
            off = yoff;
            for (int x = startX; x < startX+w; x++) {
                rgbArray[off++] = colorModel.getRGB(raster.getDataElements(x,
                                                                        y,
                                                                        data));
            }
        }

        return rgbArray;
    }


    /**
     * Sets a pixel in this <code>BufferedImage</code> to the specified
     * RGB value. The pixel is assumed to be in the default RGB color
     * model, TYPE_INT_ARGB, and default sRGB color space.  For images
     * with an <code>IndexColorModel</code>, the index with the nearest
     * color is chosen.
     *
     * <p>
     *
     * An <code>ArrayOutOfBoundsException</code> may be thrown
     * if the coordinates are not in bounds.
     * However, explicit bounds checking is not guaranteed.
     *
     * @param x the X coordinate of the pixel to set
     * @param y the Y coordinate of the pixel to set
     * @param rgb the RGB value
     * @see #getRGB(int, int)
     * @see #getRGB(int, int, int, int, int[], int, int)
     */
    public synchronized void setRGB(int x, int y, int rgb) {
        raster.setDataElements(x, y, colorModel.getDataElements(rgb, null));
    }

    /**
     * Sets an array of integer pixels in the default RGB color model
     * (TYPE_INT_ARGB) and default sRGB color space,
     * into a portion of the image data.  Color conversion takes place
     * if the default model does not match the image
     * <code>ColorModel</code>.  There are only 8-bits of precision for
     * each color component in the returned data when
     * using this method.  With a specified coordinate (x,&nbsp;y) in the
     * this image, the ARGB pixel can be accessed in this way:
     * <pre>
     *    pixel   = rgbArray[offset + (y-startY)*scansize + (x-startX)];
     * </pre>
     * WARNING: No dithering takes place.
     *
     * <p>
     *
     * An <code>ArrayOutOfBoundsException</code> may be thrown
     * if the region is not in bounds.
     * However, explicit bounds checking is not guaranteed.
     *
     * @param startX      the starting X coordinate
     * @param startY      the starting Y coordinate
     * @param w           width of the region
     * @param h           height of the region
     * @param rgbArray    the rgb pixels
     * @param offset      offset into the <code>rgbArray</code>
     * @param scansize    scanline stride for the <code>rgbArray</code>
     * @see #getRGB(int, int)
     * @see #getRGB(int, int, int, int, int[], int, int)
     */
    public void setRGB(int startX, int startY, int w, int h,
                        int[] rgbArray, int offset, int scansize) {
        int yoff  = offset;
        int off;
        Object pixel = null;

        for (int y = startY; y < startY+h; y++, yoff+=scansize) {
            off = yoff;
            for (int x = startX; x < startX+w; x++) {
                pixel = colorModel.getDataElements(rgbArray[off++], pixel);
                raster.setDataElements(x, y, pixel);
            }
        }
    }


    /**
     * Returns the width of the <code>BufferedImage</code>.
     * @return the width of this <code>BufferedImage</code>
     */
    public int getWidth() {
        return raster.getWidth();
    }

    /**
     * Returns the height of the <code>BufferedImage</code>.
     * @return the height of this <code>BufferedImage</code>
     */
    public int getHeight() {
        return raster.getHeight();
    }

    /**
     * Returns the width of the <code>BufferedImage</code>.
     * @param observer ignored
     * @return the width of this <code>BufferedImage</code>
     */
    public int getWidth(ImageObserver observer) {
        return raster.getWidth();
    }

    /**
     * Returns the height of the <code>BufferedImage</code>.
     * @param observer ignored
     * @return the height of this <code>BufferedImage</code>
     */
    public int getHeight(ImageObserver observer) {
        return raster.getHeight();
    }

    /**
     * Returns the object that produces the pixels for the image.
     * @return the {@link ImageProducer} that is used to produce the
     * pixels for this image.
     * @see ImageProducer
     */
    public ImageProducer getSource() {
        if (osis == null) {
            if (properties == null) {
                properties = new Hashtable();
            }
            osis = new OffScreenImageSource(this, properties);
        }
        return osis;
    }


    /**
     * Returns a property of the image by name.  Individual property names
     * are defined by the various image formats.  If a property is not
     * defined for a particular image, this method returns the
     * <code>UndefinedProperty</code> field.  If the properties
     * for this image are not yet known, then this method returns
     * <code>null</code> and the <code>ImageObserver</code> object is
     * notified later.  The property name "comment" should be used to
     * store an optional comment that can be presented to the user as a
     * description of the image, its source, or its author.
     * @param name the property name
     * @param observer the <code>ImageObserver</code> that receives
     *  notification regarding image information
     * @return an {@link Object} that is the property referred to by the
     *          specified <code>name</code> or <code>null</code> if the
     *          properties of this image are not yet known.
     * @throws NullPointerException if the property name is null.
     * @see ImageObserver
     * @see java.awt.Image#UndefinedProperty
     */
    public Object getProperty(String name, ImageObserver observer) {
        return getProperty(name);
    }

    /**
     * Returns a property of the image by name.
     * @param name the property name
     * @return an <code>Object</code> that is the property referred to by
     *          the specified <code>name</code>.
     * @throws NullPointerException if the property name is null.
     */
    public Object getProperty(String name) {
        if (name == null) {
            throw new NullPointerException("null property name is not allowed");
        }
        if (properties == null) {
            return java.awt.Image.UndefinedProperty;
        }
        Object o = properties.get(name);
        if (o == null) {
            o = java.awt.Image.UndefinedProperty;
        }
        return o;
    }

    /**
     * This method returns a {@link Graphics2D}, but is here
     * for backwards compatibility.  {@link #createGraphics() createGraphics} is more
     * convenient, since it is declared to return a
     * <code>Graphics2D</code>.
     * @return a <code>Graphics2D</code>, which can be used to draw into
     *          this image.
     */
    public java.awt.Graphics getGraphics() {
        return createGraphics();
    }

    /**
     * Creates a <code>Graphics2D</code>, which can be used to draw into
     * this <code>BufferedImage</code>.
     * @return a <code>Graphics2D</code>, used for drawing into this
     *          image.
     */
    public Graphics2D createGraphics() {
        GraphicsEnvironment env =
            GraphicsEnvironment.getLocalGraphicsEnvironment();
        return env.createGraphics(this);
    }

    /**
     * Returns a subimage defined by a specified rectangular region.
     * The returned <code>BufferedImage</code> shares the same
     * data array as the original image.
     * @param x the X coordinate of the upper-left corner of the
     *          specified rectangular region
     * @param y the Y coordinate of the upper-left corner of the
     *          specified rectangular region
     * @param w the width of the specified rectangular region
     * @param h the height of the specified rectangular region
     * @return a <code>BufferedImage</code> that is the subimage of this
     *          <code>BufferedImage</code>.
     * @exception RasterFormatException if the specified
     * area is not contained within this <code>BufferedImage</code>.
     */
    public BufferedImage getSubimage (int x, int y, int w, int h) {
        return new BufferedImage (colorModel,
                                  raster.createWritableChild(x, y, w, h,
                                                             0, 0, null),
                                  colorModel.isAlphaPremultiplied(),
                                  properties);
    }

    /**
     * Returns whether or not the alpha has been premultiplied.  It
     * returns <code>false</code> if there is no alpha.
     * @return <code>true</code> if the alpha has been premultiplied;
     *          <code>false</code> otherwise.
     */
    public boolean isAlphaPremultiplied() {
        return colorModel.isAlphaPremultiplied();
    }

    /**
     * Forces the data to match the state specified in the
     * <code>isAlphaPremultiplied</code> variable.  It may multiply or
     * divide the color raster data by alpha, or do nothing if the data is
     * in the correct state.
     * @param isAlphaPremultiplied <code>true</code> if the alpha has been
     *          premultiplied; <code>false</code> otherwise.
     */
    public void coerceData (boolean isAlphaPremultiplied) {
        if (colorModel.hasAlpha() &&
            colorModel.isAlphaPremultiplied() != isAlphaPremultiplied) {
            // Make the color model do the conversion
            colorModel = colorModel.coerceData (raster, isAlphaPremultiplied);
        }
    }

    /**
     * Returns a <code>String</code> representation of this
     * <code>BufferedImage</code> object and its values.
     * @return a <code>String</code> representing this
     *          <code>BufferedImage</code>.
     */
    public String toString() {
        return "BufferedImage@"+Integer.toHexString(hashCode())
            +": type = "+imageType
            +" "+colorModel+" "+raster;
    }

    /**
     * Returns a {@link Vector} of {@link RenderedImage} objects that are
     * the immediate sources, not the sources of these immediate sources,
     * of image data for this <code>BufferedImage</code>.  This
     * method returns <code>null</code> if the <code>BufferedImage</code>
     * has no information about its immediate sources.  It returns an
     * empty <code>Vector</code> if the <code>BufferedImage</code> has no
     * immediate sources.
     * @return a <code>Vector</code> containing immediate sources of
     *          this <code>BufferedImage</code> object's image date, or
     *          <code>null</code> if this <code>BufferedImage</code> has
     *          no information about its immediate sources, or an empty
     *          <code>Vector</code> if this <code>BufferedImage</code>
     *          has no immediate sources.
     */
    public Vector<RenderedImage> getSources() {
        return null;
    }

    /**
     * Returns an array of names recognized by
     * {@link #getProperty(String) getProperty(String)}
     * or <code>null</code>, if no property names are recognized.
     * @return a <code>String</code> array containing all of the property
     *          names that <code>getProperty(String)</code> recognizes;
     *          or <code>null</code> if no property names are recognized.
     */
    public String[] getPropertyNames() {
        if (properties == null || properties.isEmpty()) {
            return null;
        }
        final Set<String> keys = properties.keySet();
        return keys.toArray(new String[keys.size()]);
    }

    /**
     * Returns the minimum x coordinate of this
     * <code>BufferedImage</code>.  This is always zero.
     * @return the minimum x coordinate of this
     *          <code>BufferedImage</code>.
     */
    public int getMinX() {
        return raster.getMinX();
    }

    /**
     * Returns the minimum y coordinate of this
     * <code>BufferedImage</code>.  This is always zero.
     * @return the minimum y coordinate of this
     *          <code>BufferedImage</code>.
     */
    public int getMinY() {
        return raster.getMinY();
    }

    /**
     * Returns the <code>SampleModel</code> associated with this
     * <code>BufferedImage</code>.
     * @return the <code>SampleModel</code> of this
     *          <code>BufferedImage</code>.
     */
    public SampleModel getSampleModel() {
        return raster.getSampleModel();
    }

    /**
     * Returns the number of tiles in the x direction.
     * This is always one.
     * @return the number of tiles in the x direction.
     */
    public int getNumXTiles() {
        return 1;
    }

    /**
     * Returns the number of tiles in the y direction.
     * This is always one.
     * @return the number of tiles in the y direction.
     */
    public int getNumYTiles() {
        return 1;
    }

    /**
     * Returns the minimum tile index in the x direction.
     * This is always zero.
     * @return the minimum tile index in the x direction.
     */
    public int getMinTileX() {
        return 0;
    }

    /**
     * Returns the minimum tile index in the y direction.
     * This is always zero.
     * @return the minimum tile index in the y direction.
     */
    public int getMinTileY() {
        return 0;
    }

    /**
     * Returns the tile width in pixels.
     * @return the tile width in pixels.
     */
    public int getTileWidth() {
       return raster.getWidth();
    }

    /**
     * Returns the tile height in pixels.
     * @return the tile height in pixels.
     */
    public int getTileHeight() {
       return raster.getHeight();
    }

    /**
     * Returns the x offset of the tile grid relative to the origin,
     * For example, the x coordinate of the location of tile
     * (0,&nbsp;0).  This is always zero.
     * @return the x offset of the tile grid.
     */
    public int getTileGridXOffset() {
        return raster.getSampleModelTranslateX();
    }

    /**
     * Returns the y offset of the tile grid relative to the origin,
     * For example, the y coordinate of the location of tile
     * (0,&nbsp;0).  This is always zero.
     * @return the y offset of the tile grid.
     */
    public int getTileGridYOffset() {
        return raster.getSampleModelTranslateY();
    }

    /**
     * Returns tile (<code>tileX</code>,&nbsp;<code>tileY</code>).  Note
     * that <code>tileX</code> and <code>tileY</code> are indices
     * into the tile array, not pixel locations.  The <code>Raster</code>
     * that is returned is live, which means that it is updated if the
     * image is changed.
     * @param tileX the x index of the requested tile in the tile array
     * @param tileY the y index of the requested tile in the tile array
     * @return a <code>Raster</code> that is the tile defined by the
     *          arguments <code>tileX</code> and <code>tileY</code>.
     * @exception ArrayIndexOutOfBoundsException if both
     *          <code>tileX</code> and <code>tileY</code> are not
     *          equal to 0
     */
    public Raster getTile(int tileX, int tileY) {
        if (tileX == 0 && tileY == 0) {
            return raster;
        }
        throw new ArrayIndexOutOfBoundsException("BufferedImages only have"+
             " one tile with index 0,0");
    }

    /**
     * Returns the image as one large tile.  The <code>Raster</code>
     * returned is a copy of the image data is not updated if the
     * image is changed.
     * @return a <code>Raster</code> that is a copy of the image data.
     * @see #setData(Raster)
     */
    public Raster getData() {

        // REMIND : this allocates a whole new tile if raster is a
        // subtile.  (It only copies in the requested area)
        // We should do something smarter.
        int width = raster.getWidth();
        int height = raster.getHeight();
        int startX = raster.getMinX();
        int startY = raster.getMinY();
        WritableRaster wr =
           Raster.createWritableRaster(raster.getSampleModel(),
                         new Point(raster.getSampleModelTranslateX(),
                                   raster.getSampleModelTranslateY()));

        Object tdata = null;

        for (int i = startY; i < startY+height; i++)  {
            tdata = raster.getDataElements(startX,i,width,1,tdata);
            wr.setDataElements(startX,i,width,1, tdata);
        }
        return wr;
    }

    /**
     * Computes and returns an arbitrary region of the
     * <code>BufferedImage</code>.  The <code>Raster</code> returned is a
     * copy of the image data and is not updated if the image is
     * changed.
     * @param rect the region of the <code>BufferedImage</code> to be
     * returned.
     * @return a <code>Raster</code> that is a copy of the image data of
     *          the specified region of the <code>BufferedImage</code>
     * @see #setData(Raster)
     */
    public Raster getData(Rectangle rect) {
        SampleModel sm = raster.getSampleModel();
        SampleModel nsm = sm.createCompatibleSampleModel(rect.width,
                                                         rect.height);
        WritableRaster wr = Raster.createWritableRaster(nsm,
                                                  rect.getLocation());
        int width = rect.width;
        int height = rect.height;
        int startX = rect.x;
        int startY = rect.y;

        Object tdata = null;

        for (int i = startY; i < startY+height; i++)  {
            tdata = raster.getDataElements(startX,i,width,1,tdata);
            wr.setDataElements(startX,i,width,1, tdata);
        }
        return wr;
    }

    /**
     * Computes an arbitrary rectangular region of the
     * <code>BufferedImage</code> and copies it into a specified
     * <code>WritableRaster</code>.  The region to be computed is
     * determined from the bounds of the specified
     * <code>WritableRaster</code>.  The specified
     * <code>WritableRaster</code> must have a
     * <code>SampleModel</code> that is compatible with this image.  If
     * <code>outRaster</code> is <code>null</code>,
     * an appropriate <code>WritableRaster</code> is created.
     * @param outRaster a <code>WritableRaster</code> to hold the returned
     *          part of the image, or <code>null</code>
     * @return a reference to the supplied or created
     *          <code>WritableRaster</code>.
     */
    public WritableRaster copyData(WritableRaster outRaster) {
        if (outRaster == null) {
            return (WritableRaster) getData();
        }
        int width = outRaster.getWidth();
        int height = outRaster.getHeight();
        int startX = outRaster.getMinX();
        int startY = outRaster.getMinY();

        Object tdata = null;

        for (int i = startY; i < startY+height; i++)  {
            tdata = raster.getDataElements(startX,i,width,1,tdata);
            outRaster.setDataElements(startX,i,width,1, tdata);
        }

        return outRaster;
    }

  /**
     * Sets a rectangular region of the image to the contents of the
     * specified <code>Raster</code> <code>r</code>, which is
     * assumed to be in the same coordinate space as the
     * <code>BufferedImage</code>. The operation is clipped to the bounds
     * of the <code>BufferedImage</code>.
     * @param r the specified <code>Raster</code>
     * @see #getData
     * @see #getData(Rectangle)
    */
    public void setData(Raster r) {
        int width = r.getWidth();
        int height = r.getHeight();
        int startX = r.getMinX();
        int startY = r.getMinY();

        int[] tdata = null;

        // Clip to the current Raster
        Rectangle rclip = new Rectangle(startX, startY, width, height);
        Rectangle bclip = new Rectangle(0, 0, raster.width, raster.height);
        Rectangle intersect = rclip.intersection(bclip);
        if (intersect.isEmpty()) {
            return;
        }
        width = intersect.width;
        height = intersect.height;
        startX = intersect.x;
        startY = intersect.y;

        // remind use get/setDataElements for speed if Rasters are
        // compatible
        for (int i = startY; i < startY+height; i++)  {
            tdata = r.getPixels(startX,i,width,1,tdata);
            raster.setPixels(startX,i,width,1, tdata);
        }
    }


  /**
   * Adds a tile observer.  If the observer is already present,
   * it receives multiple notifications.
   * @param to the specified {@link TileObserver}
   */
    public void addTileObserver (TileObserver to) {
    }

  /**
   * Removes a tile observer.  If the observer was not registered,
   * nothing happens.  If the observer was registered for multiple
   * notifications, it is now registered for one fewer notification.
   * @param to the specified <code>TileObserver</code>.
   */
    public void removeTileObserver (TileObserver to) {
    }

    /**
     * Returns whether or not a tile is currently checked out for writing.
     * @param tileX the x index of the tile.
     * @param tileY the y index of the tile.
     * @return <code>true</code> if the tile specified by the specified
     *          indices is checked out for writing; <code>false</code>
     *          otherwise.
     * @exception ArrayIndexOutOfBoundsException if both
     *          <code>tileX</code> and <code>tileY</code> are not equal
     *          to 0
     */
    public boolean isTileWritable (int tileX, int tileY) {
        if (tileX == 0 && tileY == 0) {
            return true;
        }
        throw new IllegalArgumentException("Only 1 tile in image");
    }

    /**
     * Returns an array of {@link Point} objects indicating which tiles
     * are checked out for writing.  Returns <code>null</code> if none are
     * checked out.
     * @return a <code>Point</code> array that indicates the tiles that
     *          are checked out for writing, or <code>null</code> if no
     *          tiles are checked out for writing.
     */
    public Point[] getWritableTileIndices() {
        Point[] p = new Point[1];
        p[0] = new Point(0, 0);

        return p;
    }

    /**
     * Returns whether or not any tile is checked out for writing.
     * Semantically equivalent to
     * <pre>
     * (getWritableTileIndices() != null).
     * </pre>
     * @return <code>true</code> if any tile is checked out for writing;
     *          <code>false</code> otherwise.
     */
    public boolean hasTileWriters () {
        return true;
    }

  /**
   * Checks out a tile for writing.  All registered
   * <code>TileObservers</code> are notified when a tile goes from having
   * no writers to having one writer.
   * @param tileX the x index of the tile
   * @param tileY the y index of the tile
   * @return a <code>WritableRaster</code> that is the tile, indicated by
   *            the specified indices, to be checked out for writing.
   */
    public WritableRaster getWritableTile (int tileX, int tileY) {
        return raster;
    }

  /**
   * Relinquishes permission to write to a tile.  If the caller
   * continues to write to the tile, the results are undefined.
   * Calls to this method should only appear in matching pairs
   * with calls to {@link #getWritableTile(int, int) getWritableTile(int, int)}.  Any other leads
   * to undefined results.  All registered <code>TileObservers</code>
   * are notified when a tile goes from having one writer to having no
   * writers.
   * @param tileX the x index of the tile
   * @param tileY the y index of the tile
   */
    public void releaseWritableTile (int tileX, int tileY) {
    }

    /**
     * Returns the transparency.  Returns either OPAQUE, BITMASK,
     * or TRANSLUCENT.
     * @return the transparency of this <code>BufferedImage</code>.
     * @see Transparency#OPAQUE
     * @see Transparency#BITMASK
     * @see Transparency#TRANSLUCENT
     * @since 1.5
     */
    public int getTransparency() {
        return colorModel.getTransparency();
    }
}
