/*
 * Copyright (c) 1995, 2023, 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 sun.awt.image;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Transparency;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.image.DirectColorModel;
import java.awt.image.ImageConsumer;
import java.awt.image.ImageObserver;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.util.Hashtable;

public class ImageRepresentation extends ImageWatched implements ImageConsumer
{
    InputStreamImageSource src;
    ToolkitImage image;
    int tag;

    long pData; // used by windows native code only -- internal state REMIND ATTN @@

    int width = -1;
    int height = -1;
    int hints;

    int availinfo;

    Rectangle newbits;

    BufferedImage bimage;
    WritableRaster biRaster;
    protected ColorModel cmodel;
    ColorModel srcModel = null;
    int[] srcLUT = null;
    int srcLUTtransIndex = -1;
    int numSrcLUT = 0;
    boolean forceCMhint;
    int sstride;
    boolean isDefaultBI = false;
    boolean isSameCM = false;

    private static native void initIDs();

    static {
        /* ensure that the necessary native libraries are loaded */
        NativeLibLoader.loadLibraries();
        initIDs();
    }

    /**
     * Create an ImageRepresentation for the given Image.  The
     * width and height are unknown at this point.  The color
     * model is a hint as to the color model to use when creating
     * the buffered image.  If null, the src color model will
     * be used.
     */
    public ImageRepresentation(ToolkitImage im, ColorModel cmodel, boolean
                               forceCMhint) {
        image = im;

        if (image.getSource() instanceof InputStreamImageSource) {
            src = (InputStreamImageSource) image.getSource();
        }

        setColorModel(cmodel);

        this.forceCMhint = forceCMhint;
    }

    /* REMIND: Only used for Frame.setIcon - should use ImageWatcher instead */
    public synchronized void reconstruct(int flags) {
        if (src != null) {
            src.checkSecurity(null, false);
        }
        int missinginfo = flags & ~availinfo;
        if ((availinfo & ImageObserver.ERROR) == 0 && missinginfo != 0) {
            numWaiters++;
            try {
                startProduction();
                missinginfo = flags & ~availinfo;
                while ((availinfo & (ImageObserver.ERROR | ImageObserver.FRAMEBITS)) == 0
                       && missinginfo != 0)
                {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        return;
                    }
                    missinginfo = flags & ~availinfo;
                }
            } finally {
                decrementWaiters();
            }
        }
    }

    public void setDimensions(int w, int h) {
        if (src != null) {
            src.checkSecurity(null, false);
        }

        image.setDimensions(w, h);

        newInfo(image, (ImageObserver.WIDTH | ImageObserver.HEIGHT),
                0, 0, w, h);

        if (w <= 0 || h <= 0) {
            imageComplete(ImageConsumer.IMAGEERROR);
            return;
        }

        if (width != w || height != h) {
            // dimension mismatch => trigger recreation of the buffer
            bimage = null;
        }

        width = w;
        height = h;

        availinfo |= ImageObserver.WIDTH | ImageObserver.HEIGHT;
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

    ColorModel getColorModel() {
        return cmodel;
    }

    BufferedImage getBufferedImage() {
        return bimage;
    }

    /**
     * Returns the BufferedImage that will be used as the representation of
     * the pixel data.  Subclasses can override this method to return
     * platform specific subclasses of BufferedImage that may or may not be
     * accelerated.
     *
     * It is subclass' responsibility to propagate acceleration priority
     * to the newly created image.
     */
    protected BufferedImage createImage(ColorModel cm,
                                        WritableRaster raster,
                                        boolean isRasterPremultiplied,
                                        Hashtable<?,?> properties)
    {
        BufferedImage bi =
            new BufferedImage(cm, raster, isRasterPremultiplied, null);
        bi.setAccelerationPriority(image.getAccelerationPriority());
        return bi;
    }

    public void setProperties(Hashtable<?,?> props) {
        if (src != null) {
            src.checkSecurity(null, false);
        }
        image.setProperties(props);
        newInfo(image, ImageObserver.PROPERTIES, 0, 0, 0, 0);
    }

    public void setColorModel(ColorModel model) {
        if (src != null) {
            src.checkSecurity(null, false);
        }
        srcModel = model;

        // Check to see if model is INT_RGB
        if (model instanceof IndexColorModel) {
            if (model.getTransparency() == Transparency.TRANSLUCENT) {
                // REMIND:
                // Probably need to composite anyway so force ARGB
                cmodel = ColorModel.getRGBdefault();
                srcLUT = null;
            }
            else {
                IndexColorModel icm = (IndexColorModel) model;
                numSrcLUT = icm.getMapSize();
                srcLUT = new int[Math.max(numSrcLUT, 256)];
                icm.getRGBs(srcLUT);
                srcLUTtransIndex = icm.getTransparentPixel();
                cmodel = model;
            }
        }
        else {
            if (cmodel == null) {
                cmodel = model;
                srcLUT   = null;
            }
            else if (model instanceof DirectColorModel) {
                // If it is INT_RGB or INT_ARGB, use the model
                DirectColorModel dcm = (DirectColorModel) model;
                if ((dcm.getRedMask() == 0xff0000) &&
                    (dcm.getGreenMask() == 0xff00) &&
                    (dcm.getBlueMask()  == 0x00ff)) {
                    cmodel   = model;
                    srcLUT   = null;
                }
            }
        }

        isSameCM = (cmodel == model);
    }

    void createBufferedImage() {
        // REMIND:  Be careful!  Is this called every time there is a
        // startProduction?  We only want to call it if it is new or
        // there is an error
        isDefaultBI = false;
        try {
            biRaster = cmodel.createCompatibleWritableRaster(width, height);
            bimage = createImage(cmodel, biRaster,
                                 cmodel.isAlphaPremultiplied(), null);
        } catch (Exception e) {
            // Create a default image
            cmodel = ColorModel.getRGBdefault();
            biRaster = cmodel.createCompatibleWritableRaster(width, height);
            bimage = createImage(cmodel, biRaster, false, null);
        }
        int type = bimage.getType();

        if ((cmodel == ColorModel.getRGBdefault()) ||
               (type == BufferedImage.TYPE_INT_RGB) ||
               (type == BufferedImage.TYPE_INT_ARGB_PRE)) {
            isDefaultBI = true;
        }
        else if (cmodel instanceof DirectColorModel) {
            DirectColorModel dcm = (DirectColorModel) cmodel;
            if (dcm.getRedMask() == 0xff0000 &&
                dcm.getGreenMask() == 0xff00 &&
                dcm.getBlueMask()  == 0xff) {
                isDefaultBI = true;
            }
        }
    }

    private void convertToRGB() {
        int w = bimage.getWidth();
        int h = bimage.getHeight();
        int size = w*h;

        DataBufferInt dbi = new DataBufferInt(size);
        // Note that stealData() requires a markDirty() afterwards
        // since we modify the data in it.
        int[] newpixels = SunWritableRaster.stealData(dbi, 0);
        if (cmodel instanceof IndexColorModel &&
            biRaster instanceof ByteComponentRaster &&
            biRaster.getNumDataElements() == 1)
        {
            ByteComponentRaster bct = (ByteComponentRaster) biRaster;
            byte[] data = bct.getDataStorage();
            int coff = bct.getDataOffset(0);
            for (int i=0; i < size; i++) {
                newpixels[i] = srcLUT[data[coff+i]&0xff];
            }
        }
        else {
            Object srcpixels = null;
            int off=0;
            for (int y=0; y < h; y++) {
                for (int x=0; x < w; x++) {
                    srcpixels=biRaster.getDataElements(x, y, srcpixels);
                    newpixels[off++] = cmodel.getRGB(srcpixels);
                }
            }
        }
        // We modified the data array directly above so mark it as dirty now...
        SunWritableRaster.markDirty(dbi);

        isSameCM = false;
        cmodel = ColorModel.getRGBdefault();

        int[] bandMasks = {0x00ff0000,
                           0x0000ff00,
                           0x000000ff,
                           0xff000000};

        biRaster = Raster.createPackedRaster(dbi,w,h,w,
                                             bandMasks,null);

        bimage = createImage(cmodel, biRaster,
                             cmodel.isAlphaPremultiplied(), null);
        srcLUT = null;
        isDefaultBI = true;
    }

    public void setHints(int h) {
        if (src != null) {
            src.checkSecurity(null, false);
        }
        hints = h;
    }

    private native boolean setICMpixels(int x, int y, int w, int h, int[] lut,
                                    byte[] pix, int off, int scansize,
                                    IntegerComponentRaster ict);
    private native boolean setDiffICM(int x, int y, int w, int h, int[] lut,
                                 int transPix, int numLut, IndexColorModel icm,
                                 byte[] pix, int off, int scansize,
                                 ByteComponentRaster bct, int chanOff);
    static boolean s_useNative = true;

    public void setPixels(int x, int y, int w, int h,
                          ColorModel model,
                          byte[] pix, int off, int scansize) {
        int lineOff=off;
        int poff;
        int[] newLUT=null;

        if (src != null) {
            src.checkSecurity(null, false);
        }

        // REMIND: What if the model doesn't fit in default color model?
        synchronized (this) {
            if (bimage == null) {
                if (cmodel == null) {
                    cmodel = model;
                }
                createBufferedImage();
            }

            if (w <= 0 || h <= 0) {
                return;
            }

            int biWidth = biRaster.getWidth();
            int biHeight = biRaster.getHeight();

            int x1 = x+w;  // Overflow protection below
            int y1 = y+h;  // Overflow protection below
            if (x < 0) {
                off -= x;
                x = 0;
            } else if (x1 < 0) {
                x1 = biWidth;  // Must be overflow
            }
            if (y < 0) {
                off -= y*scansize;
                y = 0;
            } else if (y1 < 0) {
                y1 = biHeight;  // Must be overflow
            }
            if (x1 > biWidth) {
                x1 = biWidth;
            }
            if (y1 > biHeight) {
                y1 = biHeight;
            }
            if (x >= x1 || y >= y1) {
                return;
            }
            // x,y,x1,y1 are all >= 0, so w,h must be >= 0
            w = x1-x;
            h = y1-y;
            // off is first pixel read so it must be in bounds
            if (off < 0 || off >= pix.length) {
                // They overflowed their own array
                throw new ArrayIndexOutOfBoundsException("Data offset out of bounds.");
            }
            // pix.length and off are >= 0 so remainder >= 0
            int remainder = pix.length - off;
            if (remainder < w) {
                // They overflowed their own array
                throw new ArrayIndexOutOfBoundsException("Data array is too short.");
            }
            int num;
            if (scansize < 0) {
                num = (off / -scansize) + 1;
            } else if (scansize > 0) {
                num = ((remainder-w) / scansize) + 1;
            } else {
                num = h;
            }
            if (h > num) {
                // They overflowed their own array.
                throw new ArrayIndexOutOfBoundsException("Data array is too short.");
            }

            if (isSameCM && (cmodel != model) && (srcLUT != null) &&
                (model instanceof IndexColorModel) &&
                (biRaster instanceof ByteComponentRaster))
            {
                IndexColorModel icm = (IndexColorModel) model;
                ByteComponentRaster bct = (ByteComponentRaster) biRaster;
                int numlut = numSrcLUT;
                if (!setDiffICM(x, y, w, h, srcLUT, srcLUTtransIndex,
                               numSrcLUT, icm,
                               pix, off, scansize, bct,
                               bct.getDataOffset(0))) {
                    convertToRGB();
                }
                else {
                    // Note that setDiffICM modified the raster directly
                    // so we must mark it as changed
                    bct.markDirty();
                    if (numlut != numSrcLUT) {
                        boolean hasAlpha = icm.hasAlpha();
                        if (srcLUTtransIndex != -1) {
                            hasAlpha = true;
                        }
                        int nbits = icm.getPixelSize();
                        icm = new IndexColorModel(nbits,
                                                  numSrcLUT, srcLUT,
                                                  0, hasAlpha,
                                                  srcLUTtransIndex,
                                                  (nbits > 8
                                                   ? DataBuffer.TYPE_USHORT
                                                   : DataBuffer.TYPE_BYTE));
                        cmodel = icm;
                        bimage = createImage(icm, bct, false, null);
                    }
                    return;
                }
            }

            if (isDefaultBI) {
                int pixel;
                IntegerComponentRaster iraster =
                                          (IntegerComponentRaster) biRaster;
                if (srcLUT != null && model instanceof IndexColorModel) {
                    if (model != srcModel) {
                        // Fill in the new lut
                        ((IndexColorModel)model).getRGBs(srcLUT);
                        srcModel = model;
                    }

                    if (s_useNative) {
                        // Note that setICMpixels modifies the raster directly
                        // so we must mark it as changed afterwards
                        if (setICMpixels(x, y, w, h, srcLUT, pix, off, scansize,
                                     iraster))
                        {
                            iraster.markDirty();
                        } else {
                            abort();
                            return;
                        }
                    }
                    else {
                        int[] storage = new int[w*h];
                        int soff = 0;
                        // It is an IndexColorModel
                        for (int yoff=0; yoff < h; yoff++,
                                 lineOff += scansize) {
                            poff = lineOff;
                            for (int i=0; i < w; i++) {
                                storage[soff++] = srcLUT[pix[poff++]&0xff];
                            }
                        }
                        iraster.setDataElements(x, y, w, h, storage);
                    }
                }
                else {
                    int[] storage = new int[w];
                    for (int yoff=y; yoff < y+h; yoff++, lineOff += scansize) {
                        poff = lineOff;
                        for (int i=0; i < w; i++) {
                            storage[i] = model.getRGB(pix[poff++]&0xff);
                        }
                        iraster.setDataElements(x, yoff, w, 1, storage);
                    }
                    availinfo |= ImageObserver.SOMEBITS;
                }
            }
            else if ((cmodel == model) &&
                     (biRaster instanceof ByteComponentRaster) &&
                     (biRaster.getNumDataElements() == 1)){
                ByteComponentRaster bt = (ByteComponentRaster) biRaster;
                if (off == 0 && scansize == w) {
                    bt.putByteData(x, y, w, h, pix);
                }
                else {
                    byte[] bpix = new byte[w];
                    poff = off;
                    for (int yoff=y; yoff < y+h; yoff++) {
                        System.arraycopy(pix, poff, bpix, 0, w);
                        bt.putByteData(x, yoff, w, 1, bpix);
                        poff += scansize;
                    }
                }
            }
            else {
                for (int yoff=y; yoff < y+h; yoff++, lineOff += scansize) {
                    poff = lineOff;
                    for (int xoff=x; xoff < x+w; xoff++) {
                        bimage.setRGB(xoff, yoff,
                                      model.getRGB(pix[poff++]&0xff));
                    }
                }
                availinfo |= ImageObserver.SOMEBITS;
            }
        }

        if ((availinfo & ImageObserver.FRAMEBITS) == 0) {
            newInfo(image, ImageObserver.SOMEBITS, x, y, w, h);
        }
    }


    public void setPixels(int x, int y, int w, int h, ColorModel model,
                          int[] pix, int off, int scansize)
    {
        int lineOff=off;
        int poff;

        if (src != null) {
            src.checkSecurity(null, false);
        }

        // REMIND: What if the model doesn't fit in default color model?
        synchronized (this) {
            if (bimage == null) {
                if (cmodel == null) {
                    cmodel = model;
                }
                createBufferedImage();
            }

            int[] storage = new int[w];
            int yoff;
            int pixel;

            if (cmodel instanceof IndexColorModel) {
                // REMIND: Right now we don't support writing back into ICM
                // images.
                convertToRGB();
            }

            if ((model == cmodel) &&
                (biRaster instanceof IntegerComponentRaster)) {
                IntegerComponentRaster iraster =
                                         (IntegerComponentRaster) biRaster;

                if (off == 0 && scansize == w) {
                    iraster.setDataElements(x, y, w, h, pix);
                }
                else {
                    // Need to pack the data
                    for (yoff=y; yoff < y+h; yoff++, lineOff+=scansize) {
                        System.arraycopy(pix, lineOff, storage, 0, w);
                        iraster.setDataElements(x, yoff, w, 1, storage);
                    }
                }
            }
            else {
                if (model.getTransparency() != Transparency.OPAQUE &&
                    cmodel.getTransparency() == Transparency.OPAQUE) {
                    convertToRGB();
                }

                if (isDefaultBI) {
                    IntegerComponentRaster iraster =
                                        (IntegerComponentRaster) biRaster;
                    int[] data = iraster.getDataStorage();
                    if (cmodel.equals(model)) {
                        int sstride = iraster.getScanlineStride();
                        int doff = y*sstride + x;
                        for (yoff=0; yoff < h; yoff++, lineOff += scansize) {
                            System.arraycopy(pix, lineOff, data, doff, w);
                            doff += sstride;
                        }
                        // Note: manual modification of pixels, mark the
                        // raster as changed
                        iraster.markDirty();
                    }
                    else {
                        for (yoff=y; yoff < y+h; yoff++, lineOff += scansize) {
                            poff = lineOff;
                            for (int i=0; i < w; i++) {
                                storage[i]=model.getRGB(pix[poff++]);
                            }
                            iraster.setDataElements(x, yoff, w, 1, storage);
                        }
                    }

                    availinfo |= ImageObserver.SOMEBITS;
                }
                else {
                    Object tmp = null;

                    for (yoff=y; yoff < y+h; yoff++, lineOff += scansize) {
                        poff = lineOff;
                        for (int xoff=x; xoff < x+w; xoff++) {
                            pixel = model.getRGB(pix[poff++]);
                            tmp = cmodel.getDataElements(pixel,tmp);
                            biRaster.setDataElements(xoff, yoff,tmp);
                        }
                    }
                    availinfo |= ImageObserver.SOMEBITS;
                }
            }
        }

        // Can't do this here since we might need to transform/clip
        // the region
        if (((availinfo & ImageObserver.FRAMEBITS) == 0)) {
            newInfo(image, ImageObserver.SOMEBITS, x, y, w, h);
        }
    }

    public BufferedImage getOpaqueRGBImage() {
        if (bimage.getType() == BufferedImage.TYPE_INT_ARGB) {
            int w = bimage.getWidth();
            int h = bimage.getHeight();
            int size = w * h;

            // Note that we steal the data array here, but only for reading...
            DataBufferInt db = (DataBufferInt)biRaster.getDataBuffer();
            int[] pixels = SunWritableRaster.stealData(db, 0);

            for (int i = 0; i < size; i++) {
                if ((pixels[i] >>> 24) != 0xff) {
                    return bimage;
                }
            }

            ColorModel opModel = new DirectColorModel(24,
                                                      0x00ff0000,
                                                      0x0000ff00,
                                                      0x000000ff);

            int[] bandmasks = {0x00ff0000, 0x0000ff00, 0x000000ff};
            WritableRaster opRaster = Raster.createPackedRaster(db, w, h, w,
                                                                bandmasks,
                                                                null);

            try {
                BufferedImage opImage = createImage(opModel, opRaster,
                                                    false, null);
                return opImage;
            } catch (Exception e) {
                return bimage;
            }
        }
        return bimage;
    }

    private boolean consuming = false;

    public void imageComplete(int status) {
        if (src != null) {
            src.checkSecurity(null, false);
        }
        boolean done;
        int info;
        switch (status) {
        default:
        case ImageConsumer.IMAGEABORTED:
            done = true;
            info = ImageObserver.ABORT;
            break;
        case ImageConsumer.IMAGEERROR:
            image.addInfo(ImageObserver.ERROR);
            done = true;
            info = ImageObserver.ERROR;
            dispose();
            break;
        case ImageConsumer.STATICIMAGEDONE:
            done = true;
            info = ImageObserver.ALLBITS;
            break;
        case ImageConsumer.SINGLEFRAMEDONE:
            done = false;
            info = ImageObserver.FRAMEBITS;
            break;
        }
        synchronized (this) {
            if (done) {
                image.getSource().removeConsumer(this);
                consuming = false;
                newbits = null;

                if (bimage != null) {
                    bimage = getOpaqueRGBImage();
                }
            }
            availinfo |= info;
            notifyAll();
        }

        newInfo(image, info, 0, 0, width, height);

        image.infoDone(status);
    }

    /*synchronized*/ void startProduction() {
        if (!consuming) {
            consuming = true;
            image.getSource().startProduction(this);
        }
    }

    private int numWaiters;

    private synchronized void checkConsumption() {
        if (isWatcherListEmpty() && numWaiters == 0 &&
            ((availinfo & ImageObserver.ALLBITS) == 0))
        {
            dispose();
        }
    }

    public synchronized void notifyWatcherListEmpty() {
        checkConsumption();
    }

    private synchronized void decrementWaiters() {
        --numWaiters;
        checkConsumption();
    }

    public boolean prepare(ImageObserver iw) {
        if (src != null) {
            src.checkSecurity(null, false);
        }
        if ((availinfo & ImageObserver.ERROR) != 0) {
            if (iw != null) {
                iw.imageUpdate(image, ImageObserver.ERROR|ImageObserver.ABORT,
                               -1, -1, -1, -1);
            }
            return false;
        }
        boolean done = ((availinfo & ImageObserver.ALLBITS) != 0);
        if (!done) {
            addWatcher(iw);
            startProduction();
            // Some producers deliver image data synchronously
            done = ((availinfo & ImageObserver.ALLBITS) != 0);
        }
        return done;
    }

    public int check(ImageObserver iw) {

        if (src != null) {
            src.checkSecurity(null, false);
        }
        if ((availinfo & (ImageObserver.ERROR | ImageObserver.ALLBITS)) == 0) {
            addWatcher(iw);
        }

        return availinfo;
    }

    public boolean drawToBufImage(Graphics g, ToolkitImage img,
                                  int x, int y, Color bg,
                                  ImageObserver iw) {

        if (src != null) {
            src.checkSecurity(null, false);
        }
        if ((availinfo & ImageObserver.ERROR) != 0) {
            if (iw != null) {
                iw.imageUpdate(image, ImageObserver.ERROR|ImageObserver.ABORT,
                               -1, -1, -1, -1);
            }
            return false;
        }
        boolean done  = ((availinfo & ImageObserver.ALLBITS) != 0);
        boolean abort = ((availinfo & ImageObserver.ABORT) != 0);

        if (!done && !abort) {
            addWatcher(iw);
            startProduction();
            // Some producers deliver image data synchronously
            done = ((availinfo & ImageObserver.ALLBITS) != 0);
        }

        if (done || (0 != (availinfo & ImageObserver.FRAMEBITS))) {
            g.drawImage (bimage, x, y, bg, null);
        }

        return done;
    }

    public boolean drawToBufImage(Graphics g, ToolkitImage img,
                                  int x, int y, int w, int h,
                                  Color bg, ImageObserver iw) {

        if (src != null) {
            src.checkSecurity(null, false);
        }
        if ((availinfo & ImageObserver.ERROR) != 0) {
            if (iw != null) {
                iw.imageUpdate(image, ImageObserver.ERROR|ImageObserver.ABORT,
                               -1, -1, -1, -1);
            }
            return false;
        }

        boolean done  = ((availinfo & ImageObserver.ALLBITS) != 0);
        boolean abort = ((availinfo & ImageObserver.ABORT) != 0);

        if (!done && !abort) {
            addWatcher(iw);
            startProduction();
            // Some producers deliver image data synchronously
            done = ((availinfo & ImageObserver.ALLBITS) != 0);
        }

        if (done || (0 != (availinfo & ImageObserver.FRAMEBITS))) {
            g.drawImage (bimage, x, y, w, h, bg, null);
        }

        return done;
    }

    public boolean drawToBufImage(Graphics g, ToolkitImage img,
                                  int dx1, int dy1, int dx2, int dy2,
                                  int sx1, int sy1, int sx2, int sy2,
                                  Color bg, ImageObserver iw) {

        if (src != null) {
            src.checkSecurity(null, false);
        }
        if ((availinfo & ImageObserver.ERROR) != 0) {
            if (iw != null) {
                iw.imageUpdate(image, ImageObserver.ERROR|ImageObserver.ABORT,
                               -1, -1, -1, -1);
            }
            return false;
        }
        boolean done  = ((availinfo & ImageObserver.ALLBITS) != 0);
        boolean abort = ((availinfo & ImageObserver.ABORT) != 0);

        if (!done && !abort) {
            addWatcher(iw);
            startProduction();
            // Some producers deliver image data synchronously
            done = ((availinfo & ImageObserver.ALLBITS) != 0);
        }

        if (done || (0 != (availinfo & ImageObserver.FRAMEBITS))) {
            g.drawImage (bimage,
                         dx1, dy1, dx2, dy2,
                         sx1, sy1, sx2, sy2,
                         bg, null);
        }

        return done;
    }

    public boolean drawToBufImage(Graphics g, ToolkitImage img,
                                  AffineTransform xform,
                                  ImageObserver iw)
    {
        Graphics2D g2 = (Graphics2D) g;

        if (src != null) {
            src.checkSecurity(null, false);
        }
        if ((availinfo & ImageObserver.ERROR) != 0) {
            if (iw != null) {
                iw.imageUpdate(image, ImageObserver.ERROR|ImageObserver.ABORT,
                               -1, -1, -1, -1);
            }
            return false;
        }
        boolean done  = ((availinfo & ImageObserver.ALLBITS) != 0);
        boolean abort = ((availinfo & ImageObserver.ABORT) != 0);

        if (!done && !abort) {
            addWatcher(iw);
            startProduction();
            // Some producers deliver image data synchronously
            done = ((availinfo & ImageObserver.ALLBITS) != 0);
        }

        if (done || (0 != (availinfo & ImageObserver.FRAMEBITS))) {
            g2.drawImage (bimage, xform, null);
        }

        return done;
    }

    synchronized void abort() {
        image.getSource().removeConsumer(this);
        consuming = false;
        newbits = null;
        bimage = null;
        biRaster = null;
        cmodel = null;
        srcLUT = null;
        isDefaultBI = false;
        isSameCM = false;

        newInfo(image, ImageObserver.ABORT, -1, -1, -1, -1);
        availinfo &= ~(ImageObserver.SOMEBITS
                       | ImageObserver.FRAMEBITS
                       | ImageObserver.ALLBITS
                       | ImageObserver.ERROR);
    }

    synchronized void dispose() {
        image.getSource().removeConsumer(this);
        consuming = false;
        newbits = null;
        availinfo &= ~(ImageObserver.SOMEBITS
                       | ImageObserver.FRAMEBITS
                       | ImageObserver.ALLBITS);
    }

    public void setAccelerationPriority(float priority) {
        if (bimage != null) {
            bimage.setAccelerationPriority(priority);
        }
    }
}
