/*
 * Copyright (c) 2011, 2017, 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.java2d;

import java.awt.*;
import java.awt.color.*;
import java.awt.image.*;
import java.nio.*;

import sun.awt.image.*;
import sun.java2d.loops.*;

public class OSXOffScreenSurfaceData extends OSXSurfaceData // implements RasterListener
{
    private static native void initIDs();

    static {
        initIDs();
    }

    // the image associated with this surface
    BufferedImage bim;
    // the image associated with this custom surface
    BufferedImage bimBackup;
    // <rdar://problem/4177639> nio based images use ARGB_PRE
    static DirectColorModel dcmBackup = new DirectColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000, true, DataBuffer.TYPE_INT);

    Object lock;

    // cached rasters for easy access
    WritableRaster bufImgRaster;
    SunWritableRaster bufImgSunRaster;

    // these are extra image types we can handle
    private static final int TYPE_3BYTE_RGB = BufferedImage.TYPE_BYTE_INDEXED + 1;

    // these are for callbacks when pixes have been touched
    protected ByteBuffer fImageInfo;
    IntBuffer fImageInfoInt;
    private static final int kNeedToSyncFromJavaPixelsIndex = 0;
    private static final int kNativePixelsChangedIndex = 1;
    private static final int kImageStolenIndex = 2;
    private static final int kSizeOfParameters = kImageStolenIndex + 1;

    public static native SurfaceData getSurfaceData(BufferedImage bufImg);

    protected static native void setSurfaceData(BufferedImage bufImg, SurfaceData sData);

    public static SurfaceData createData(BufferedImage bufImg) {
        /*
         * if ((bufImg.getWidth() == 32) && (bufImg.getHeight() == 32)) { Thread.dumpStack(); }
         */
        // This could be called from multiple threads. We need to synchronized on the image so that
        // we can ensure that only one surface data is created per image. (<rdar://4564873>)
        // Note: Eventually, we should switch to using the same mechanism (CachingSurfaceManager) that Sun uses
        // <rdar://4563741>
        synchronized (bufImg) {
            SurfaceData sData = getSurfaceData(bufImg);
            if (sData != null) { return sData; }

            OSXOffScreenSurfaceData osData = OSXOffScreenSurfaceData.createNewSurface(bufImg);

            OSXOffScreenSurfaceData.setSurfaceData(bufImg, osData);
            osData.cacheRasters(bufImg);
//            osData.setRasterListener();

            return osData;
        }
    }

    public static SurfaceData createData(Raster ras, ColorModel cm) {
        throw new InternalError("SurfaceData not implemented for Raster/CM");
    }

    static OSXOffScreenSurfaceData createNewSurface(BufferedImage bufImg) {
        SurfaceData sData = null;

        ColorModel cm = bufImg.getColorModel();
        int type = bufImg.getType();
        // REMIND: Check the image type and pick an appropriate subclass
        switch (type) {
            case BufferedImage.TYPE_INT_BGR:
                sData = createDataIC(bufImg, SurfaceType.IntBgr);
                break;
            case BufferedImage.TYPE_INT_RGB:
                sData = createDataIC(bufImg, SurfaceType.IntRgb);
                break;
            case BufferedImage.TYPE_INT_ARGB:
                sData = createDataIC(bufImg, SurfaceType.IntArgb);
                break;
            case BufferedImage.TYPE_INT_ARGB_PRE:
                sData = createDataIC(bufImg, SurfaceType.IntArgbPre);
                break;
            case BufferedImage.TYPE_3BYTE_BGR:
                sData = createDataBC(bufImg, SurfaceType.ThreeByteBgr, 2);
                break;
            case BufferedImage.TYPE_4BYTE_ABGR:
                sData = createDataBC(bufImg, SurfaceType.FourByteAbgr, 3);
                break;
            case BufferedImage.TYPE_4BYTE_ABGR_PRE:
                sData = createDataBC(bufImg, SurfaceType.FourByteAbgrPre, 3);
                break;
            case BufferedImage.TYPE_USHORT_565_RGB:
                sData = createDataSC(bufImg, SurfaceType.Ushort565Rgb, null);
                break;
            case BufferedImage.TYPE_USHORT_555_RGB:
                sData = createDataSC(bufImg, SurfaceType.Ushort555Rgb, null);
                break;
            case BufferedImage.TYPE_BYTE_INDEXED: {
                SurfaceType sType;
                switch (cm.getTransparency()) {
                    case OPAQUE:
                        if (isOpaqueGray((IndexColorModel) cm)) {
                            sType = SurfaceType.Index8Gray;
                        } else {
                            sType = SurfaceType.ByteIndexedOpaque;
                        }
                        break;
                    case BITMASK:
                        sType = SurfaceType.ByteIndexedBm;
                        break;
                    case TRANSLUCENT:
                        sType = SurfaceType.ByteIndexed;
                        break;
                    default:
                        throw new InternalError("Unrecognized transparency");
                }
                sData = createDataBC(bufImg, sType, 0);
            }
                break;
            case BufferedImage.TYPE_BYTE_GRAY:
                sData = createDataBC(bufImg, SurfaceType.ByteGray, 0);
                break;
            case BufferedImage.TYPE_USHORT_GRAY:
                sData = createDataSC(bufImg, SurfaceType.UshortGray, null);
                break;
            case BufferedImage.TYPE_BYTE_BINARY:
            case BufferedImage.TYPE_CUSTOM:
            default: {
                Raster raster = bufImg.getRaster();

                // we try to fit a custom image into one of the predefined BufferedImages (BufferedImage does that
                // first, we further refine it here)
                // we can do that because a pointer in C is a pointer (pixel pointer not dependent on DataBuffer type)
                SampleModel sm = bufImg.getSampleModel();
                SurfaceType sType = SurfaceType.Custom;
                int transferType = cm.getTransferType();
                int pixelSize = cm.getPixelSize();
                int numOfComponents = cm.getNumColorComponents();
                if ((numOfComponents == 3) && (cm instanceof ComponentColorModel) && (sm instanceof PixelInterleavedSampleModel)) {
                    int sizes[] = cm.getComponentSize();
                    boolean validsizes = (sizes[0] == 8) && (sizes[1] == 8) && (sizes[2] == 8);
                    int[] offs = ((ComponentSampleModel) sm).getBandOffsets();
                    int numBands = raster.getNumBands();
                    boolean bigendian = (offs[0] == numBands - 3) && (offs[1] == numBands - 2) && (offs[2] == numBands - 1);
                    boolean littleendian = (offs[0] == numBands - 1) && (offs[1] == numBands - 2) && (offs[2] == numBands - 3);

                    if ((pixelSize == 32) && (transferType == DataBuffer.TYPE_INT)) {
                        if (validsizes && bigendian && cm.hasAlpha() && cm.isAlphaPremultiplied() && sizes[3] == 8) {
                            try {
                                sData = createDataIC(bufImg, sType, BufferedImage.TYPE_INT_ARGB_PRE);
                            } catch (ClassCastException e) {
                                sData = null;
                            }
                        } else if (validsizes && bigendian && cm.hasAlpha() && sizes[3] == 8) {
                            try {
                                sData = createDataIC(bufImg, sType, BufferedImage.TYPE_INT_ARGB);
                            } catch (ClassCastException e) {
                                sData = null;
                            }
                        } else if (validsizes && littleendian && cm.hasAlpha() && cm.isAlphaPremultiplied() && sizes[3] == 8) {
                            try {
                                sData = createDataIC(bufImg, sType, BufferedImage.TYPE_4BYTE_ABGR_PRE);
                            } catch (ClassCastException e) {
                                sData = null;
                            }
                        } else if (validsizes && littleendian && cm.hasAlpha() && sizes[3] == 8) {
                            try {
                                sData = createDataIC(bufImg, sType, BufferedImage.TYPE_4BYTE_ABGR);
                            } catch (ClassCastException e) {
                                sData = null;
                            }
                        } else if (validsizes && bigendian) {
                            try {
                                sData = createDataIC(bufImg, sType, BufferedImage.TYPE_INT_RGB);
                            } catch (ClassCastException e) {
                                sData = null;
                            }
                        }
                    } else if ((pixelSize == 32) && (transferType == DataBuffer.TYPE_BYTE)) {
                        if (validsizes && bigendian && cm.hasAlpha() && cm.isAlphaPremultiplied() && sizes[3] == 8) {
                            try {
                                sData = createDataBC(bufImg, sType, 3, BufferedImage.TYPE_INT_ARGB_PRE);
                            } catch (ClassCastException e) {
                                sData = null;
                            }
                        }
                        if (validsizes && bigendian && cm.hasAlpha() && sizes[3] == 8) {
                            try {
                                sData = createDataBC(bufImg, sType, 3, BufferedImage.TYPE_INT_ARGB);
                            } catch (ClassCastException e) {
                                sData = null;
                            }
                        } else if (validsizes && littleendian && cm.hasAlpha() && cm.isAlphaPremultiplied() && sizes[3] == 8) {
                            try {
                                sData = createDataBC(bufImg, sType, 3, BufferedImage.TYPE_4BYTE_ABGR_PRE);
                            } catch (ClassCastException e) {
                                sData = null;
                            }
                        } else if (validsizes && littleendian && cm.hasAlpha() && sizes[3] == 8) {
                            try {
                                sData = createDataBC(bufImg, sType, 3, BufferedImage.TYPE_4BYTE_ABGR);
                            } catch (ClassCastException e) {
                                sData = null;
                            }
                        } else if (validsizes && littleendian) {
                            try {
                                sData = createDataBC(bufImg, sType, 3, BufferedImage.TYPE_INT_BGR);
                            } catch (ClassCastException e) {
                                sData = null;
                            }
                        } else if (validsizes && bigendian) {
                            try {
                                sData = createDataBC(bufImg, sType, 3, BufferedImage.TYPE_INT_RGB);
                            } catch (ClassCastException e) {
                                sData = null;
                            }
                        }
                    } else if ((pixelSize == 24) && (transferType == DataBuffer.TYPE_INT)) {
                        if (validsizes && bigendian) {
                            try {
                                sData = createDataIC(bufImg, sType, BufferedImage.TYPE_INT_RGB);
                            } catch (ClassCastException e) {
                                sData = null;
                            }
                        } else if (validsizes && littleendian) {
                            try {
                                sData = createDataIC(bufImg, sType, BufferedImage.TYPE_INT_BGR);
                            } catch (ClassCastException e) {
                                sData = null;
                            }
                        }
                    } else if ((pixelSize == 24) && (transferType == DataBuffer.TYPE_BYTE)) {
                        if (validsizes && bigendian) {
                            try {
                                sData = createDataBC(bufImg, sType, 0, TYPE_3BYTE_RGB);
                            } catch (ClassCastException e) {
                                sData = null;
                            }
                        } else if (validsizes && littleendian) {
                            try {
                                sData = createDataBC(bufImg, sType, 0, BufferedImage.TYPE_3BYTE_BGR);
                            } catch (ClassCastException e) {
                                sData = null;
                            }
                        }
                    } else if ((pixelSize == 16) && (transferType == DataBuffer.TYPE_USHORT)) {
                        validsizes = (sizes[0] == 5) && (sizes[1] == 6) && (sizes[2] == 5);
                        if (validsizes && bigendian) {
                            try {
                                sData = createDataSC(bufImg, sType, null, BufferedImage.TYPE_USHORT_565_RGB);
                            } catch (ClassCastException e) {
                                sData = null;
                            }
                        }
                    } else if ((pixelSize == 16) && (transferType == DataBuffer.TYPE_BYTE)) {
                        validsizes = (sizes[0] == 5) && (sizes[1] == 6) && (sizes[2] == 5);
                        if (validsizes && bigendian) {
                            try {
                                sData = createDataBC(bufImg, sType, 1, BufferedImage.TYPE_USHORT_565_RGB);
                            } catch (ClassCastException e) {
                                sData = null;
                            }
                        }
                    } else if ((pixelSize == 15) && (transferType == DataBuffer.TYPE_USHORT)) {
                        validsizes = (sizes[0] == 5) && (sizes[1] == 5) && (sizes[2] == 5);
                        if (validsizes && bigendian) {
                            try {
                                sData = createDataSC(bufImg, sType, null, BufferedImage.TYPE_USHORT_555_RGB);
                            } catch (ClassCastException e) {
                                sData = null;
                            }
                        }
                    } else if ((pixelSize == 15) && (transferType == DataBuffer.TYPE_BYTE)) {
                        validsizes = (sizes[0] == 5) && (sizes[1] == 5) && (sizes[2] == 5);
                        if (validsizes && bigendian) {
                            try {
                                sData = createDataBC(bufImg, sType, 1, BufferedImage.TYPE_USHORT_555_RGB);
                            } catch (ClassCastException e) {
                                sData = null;
                            }
                        }
                    }
                }
            }
                break;
        }

        // we failed to match
        if (sData == null) {
            sData = new OSXOffScreenSurfaceData(bufImg, SurfaceType.Custom);
            OSXOffScreenSurfaceData offsd = (OSXOffScreenSurfaceData) sData;

            // 2004_03_26 cmc: We used to use createCompatibleImage here. Now that createCompatibleImage returns
            // an INT_ARGB_PRE instead of an NIO-based image, we need to explicitly create an NIO-based image.
            IntegerNIORaster backupRaster = (IntegerNIORaster) IntegerNIORaster.createNIORaster(bufImg.getWidth(), bufImg.getHeight(), dcmBackup.getMasks(), null);
            offsd.bimBackup = new BufferedImage(dcmBackup, backupRaster, dcmBackup.isAlphaPremultiplied(), null);

            // the trick that makes it work - assign the raster from backup to the surface data of the original image
            offsd.initCustomRaster(backupRaster.getBuffer(),
                                    backupRaster.getWidth(),
                                    backupRaster.getHeight(),
                                    offsd.fGraphicsStates,
                                    offsd.fGraphicsStatesObject,
                                    offsd.fImageInfo);

            //offsd.checkIfLazyPixelConversionDisabled();
            offsd.fImageInfoInt.put(kImageStolenIndex, 1);
        }

        return (OSXOffScreenSurfaceData) sData;
    }

    private static SurfaceData createDataIC(BufferedImage bImg, SurfaceType sType, int iType) {
        OSXOffScreenSurfaceData offsd = new OSXOffScreenSurfaceData(bImg, sType);

        IntegerComponentRaster icRaster = (IntegerComponentRaster) bImg.getRaster();
        offsd.initRaster(icRaster.getDataStorage(),
                            icRaster.getDataOffset(0) * 4,
                            icRaster.getWidth(),
                            icRaster.getHeight(),
                            icRaster.getPixelStride() * 4,
                            icRaster.getScanlineStride() * 4,
                            null,
                            iType,
                            offsd.fGraphicsStates,
                            offsd.fGraphicsStatesObject,
                            offsd.fImageInfo);

       // offsd.checkIfLazyPixelConversionDisabled();
        offsd.fImageInfoInt.put(kImageStolenIndex, 1);
        return offsd;
    }

    public static SurfaceData createDataIC(BufferedImage bImg, SurfaceType sType) {
        return createDataIC(bImg, sType, bImg.getType());
    }

    private static SurfaceData createDataSC(BufferedImage bImg, SurfaceType sType, IndexColorModel icm, int iType) {
        OSXOffScreenSurfaceData offsd = new OSXOffScreenSurfaceData(bImg, sType);

        ShortComponentRaster scRaster = (ShortComponentRaster) bImg.getRaster();
        offsd.initRaster(scRaster.getDataStorage(),
                            scRaster.getDataOffset(0) * 2,
                            scRaster.getWidth(),
                            scRaster.getHeight(),
                            scRaster.getPixelStride() * 2,
                            scRaster.getScanlineStride() * 2,
                            icm,
                            iType,
                            offsd.fGraphicsStates,
                            offsd.fGraphicsStatesObject,
                            offsd.fImageInfo);

        //offsd.checkIfLazyPixelConversionDisabled();
        offsd.fImageInfoInt.put(kImageStolenIndex, 1);
        return offsd;
    }

    public static SurfaceData createDataSC(BufferedImage bImg, SurfaceType sType, IndexColorModel icm) {
        return createDataSC(bImg, sType, icm, bImg.getType());
    }

    private static SurfaceData createDataBC(BufferedImage bImg, SurfaceType sType, int primaryBank, int iType) {
        OSXOffScreenSurfaceData offsd = new OSXOffScreenSurfaceData(bImg, sType);

        ByteComponentRaster bcRaster = (ByteComponentRaster) bImg.getRaster();
        ColorModel cm = bImg.getColorModel();
        IndexColorModel icm = ((cm instanceof IndexColorModel) ? (IndexColorModel) cm : null);
        offsd.initRaster(bcRaster.getDataStorage(),
                            bcRaster.getDataOffset(primaryBank),
                            bcRaster.getWidth(),
                            bcRaster.getHeight(),
                            bcRaster.getPixelStride(),
                            bcRaster.getScanlineStride(),
                            icm,
                            iType,
                            offsd.fGraphicsStates,
                            offsd.fGraphicsStatesObject,
                            offsd.fImageInfo);

        offsd.fImageInfoInt.put(kImageStolenIndex, 1);

        return offsd;
    }

    public static SurfaceData createDataBC(BufferedImage bImg, SurfaceType sType, int primaryBank) {
        return createDataBC(bImg, sType, primaryBank, bImg.getType());
    }

    private static SurfaceData createDataBP(BufferedImage bImg, SurfaceType sType, int iType) {
        OSXOffScreenSurfaceData offsd = new OSXOffScreenSurfaceData(bImg, sType);

        BytePackedRaster bpRaster = (BytePackedRaster) bImg.getRaster();
        ColorModel cm = bImg.getColorModel();
        IndexColorModel icm = ((cm instanceof IndexColorModel) ? (IndexColorModel) cm : null);
        offsd.initRaster(bpRaster.getDataStorage(),
                            bpRaster.getDataBitOffset(), // in bits, NOT bytes! (needs special attention in native
                                                         // code!)
                bpRaster.getWidth(),
                            bpRaster.getHeight(),
                            bpRaster.getPixelBitStride(),
                            bpRaster.getScanlineStride() * 8,
                            icm,
                            iType,
                            offsd.fGraphicsStates,
                            offsd.fGraphicsStatesObject,
                            offsd.fImageInfo);

        //offsd.checkIfLazyPixelConversionDisabled();
        offsd.fImageInfoInt.put(kImageStolenIndex, 1);
        return offsd;
    }

    protected native void initRaster(Object theArray, int offset, int width, int height, int pixStr, int scanStr, IndexColorModel icm, int type, ByteBuffer graphicsStates, Object graphicsStatesObjects, ByteBuffer imageInfo);

    protected native void initCustomRaster(IntBuffer buffer, int width, int height, ByteBuffer graphicsStates, Object graphicsStatesObjects, ByteBuffer imageInfo);

    public Object getLockObject() {
        return this.lock;
    }

    // Makes the constructor package private instead of public.
    OSXOffScreenSurfaceData(BufferedImage bufImg, SurfaceType sType) {
        super(sType, bufImg.getColorModel());
        setBounds(0, 0, bufImg.getWidth(), bufImg.getHeight());

        this.bim = bufImg;

        this.fImageInfo = ByteBuffer.allocateDirect(4 * kSizeOfParameters);
        this.fImageInfo.order(ByteOrder.nativeOrder());
        this.fImageInfoInt = this.fImageInfo.asIntBuffer();

        this.fImageInfoInt.put(kNeedToSyncFromJavaPixelsIndex, 1); // need to sync from Java the very first time
        this.fImageInfoInt.put(kNativePixelsChangedIndex, 0);
        this.fImageInfoInt.put(kImageStolenIndex, 0);

        this.lock = new Object();
    }

    /**
     * Performs a copyArea within this surface.
     */
    public boolean copyArea(SunGraphics2D sg2d, int x, int y, int w, int h, int dx, int dy) {
        // <rdar://problem/4488745> For the Sun2D renderer we should rely on the implementation of the super class.
        // BufImageSurfaceData.java doesn't have an implementation of copyArea() and relies on the super class.

        int offsetX = 0;
        int offsetY = 0;
        if (sg2d.transformState == SunGraphics2D.TRANSFORM_ANY_TRANSLATE ||
                    sg2d.transformState == SunGraphics2D.TRANSFORM_INT_TRANSLATE) {
            offsetX = (int) sg2d.transform.getTranslateX();
            offsetY = (int) sg2d.transform.getTranslateY();
        } else if (sg2d.transformState != SunGraphics2D.TRANSFORM_ISIDENT) { return false; }

        // reset the clip (this is how it works on windows)
        // we actually can handle a case with any clips but windows ignores the light clip
        Shape clip = sg2d.getClip();
        sg2d.setClip(getBounds());

        // clip copyArea
        Rectangle clippedCopyAreaRect = clipCopyArea(sg2d, x, y, w, h, dx, dy);
        if (clippedCopyAreaRect == null) {
            // clipped out
            return true;
        }

        // the rectangle returned from clipCopyArea() is in the coordinate space of the surface (image)
        // we need to substract the offsetX and offsetY to move it to the coordinate space of the graphics2d.
        // sg2d.drawImage expects the destination rect to be in the coord space of the graphics2d. <rdar://3746194>
        // (vm)
        x = clippedCopyAreaRect.x - offsetX;
        y = clippedCopyAreaRect.y - offsetY;
        w = clippedCopyAreaRect.width;
        h = clippedCopyAreaRect.height;

        // copy (dst coordinates are in the coord space of the graphics2d, and src coordinates are
        // in the coordinate space of the image)
        sg2d.drawImage(this.bim, x + dx, y + dy, x + dx + w, y + dy + h, x + offsetX, y + offsetY, x + w + offsetX, y + h + offsetY, null);

        // restore the clip
        sg2d.setClip(clip);

        return true;
    }

    /**
     * Performs a copyarea from this surface to a buffered image. If null is passed in for the image a new image will be
     * created.
     *
     * Only used by compositor code (private API)
     */
    public BufferedImage copyArea(SunGraphics2D sg2d, int x, int y, int w, int h, BufferedImage dstImage) {
        // create the destination image if needed
        if (dstImage == null) {
            dstImage = getDeviceConfiguration().createCompatibleImage(w, h);
        }

        // copy
        Graphics g = dstImage.createGraphics();
        g.drawImage(this.bim, 0, 0, w, h, x, y, x + w, y + h, null);
        g.dispose();

        return dstImage;
    }

    public boolean xorSurfacePixels(SunGraphics2D sg2d, BufferedImage srcPixels, int x, int y, int w, int h, int colorXOR) {

        int type = this.bim.getType();

        if ((type == BufferedImage.TYPE_INT_ARGB_PRE) || (type == BufferedImage.TYPE_INT_ARGB) || (type == BufferedImage.TYPE_INT_RGB)) { return xorSurfacePixels(createData(srcPixels), colorXOR, x, y, w, h); }

        return false;
    }

    native boolean xorSurfacePixels(SurfaceData src, int colorXOR, int x, int y, int w, int h);

    public void clearRect(BufferedImage bim, int w, int h) {
        OSXOffScreenSurfaceData offsd = (OSXOffScreenSurfaceData) (OSXOffScreenSurfaceData.createData(bim));
        // offsd.clear();
        if (offsd.clearSurfacePixels(w, h) == false) {
            Graphics2D g = bim.createGraphics();
            g.setComposite(AlphaComposite.Clear);
            g.fillRect(0, 0, w, h);
            g.dispose();
        }
    }

    native boolean clearSurfacePixels(int w, int h);

    // 04/06/04 cmc: radr://3612381 Graphics.drawImage ignores bgcolor parameter.
    // getCopyWithBgColor returns a new version of an image, drawn with a background
    // color. Called by blitImage in OSXSurfaceData.java.
    BufferedImage copyWithBgColor_cache = null;

    public SurfaceData getCopyWithBgColor(Color bgColor) {
        int bimW = this.bim.getWidth();
        int bimH = this.bim.getHeight();

        if ((this.copyWithBgColor_cache == null)
                || (this.copyWithBgColor_cache.getWidth() < bimW) || (this.copyWithBgColor_cache.getHeight() < bimH)) {
            GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
            this.copyWithBgColor_cache = gc.createCompatibleImage(bimW, bimH);
        }

        Graphics g2 = this.copyWithBgColor_cache.createGraphics();
        g2.setColor(bgColor);
        g2.fillRect(0, 0, bimW, bimH);
        g2.drawImage(this.bim, 0, 0, bimW, bimH, null);
        g2.dispose();

        return getSurfaceData(this.copyWithBgColor_cache);
    }

    /**
     * Invoked before the raster's contents are to be read (via one of the modifier methods in Raster such as
     * getPixel())
     */
    public void rasterRead() {
        if (fImageInfoInt.get(kNativePixelsChangedIndex) == 1) {
            syncToJavaPixels();
        }
    }

    /**
     * Invoked before the raster's contents are to be written to (via one of the modifier methods in Raster such as
     * setPixel())
     */
    public void rasterWrite() {
        if (fImageInfoInt.get(kNativePixelsChangedIndex) == 1) {
            syncToJavaPixels();
        }

        fImageInfoInt.put(kNeedToSyncFromJavaPixelsIndex, 1); // the pixels will change
    }

    private void syncFromCustom() {

    }

    private void syncToCustom() {

    }
//    /**
//     * Invoked when the raster's contents will be taken (via the Raster.getDataBuffer() method)
//     */
//    public void rasterStolen() {
//        fImageInfoInt.put(kImageStolenIndex, 1); // this means we must convert between Java and native pixels every
//                                                 // single primitive! (very expensive)
//        if (fImageInfoInt.get(kNativePixelsChangedIndex) == 1) {
//            syncToJavaPixels();
//        }
//
//        // we know the pixels have been stolen, no need to listen for changes any more
////        if (this.bufImgSunRaster != null) {
////            this.bufImgSunRaster.setRasterListener(null);
////        }
//    }

    private native void syncToJavaPixels();

    // we need to refer to rasters often, so cache them
    void cacheRasters(BufferedImage bim) {
        this.bufImgRaster = bim.getRaster();
        if (this.bufImgRaster instanceof SunWritableRaster) {
            this.bufImgSunRaster = (SunWritableRaster) this.bufImgRaster;
        }
    }

//    void setRasterListener() {
//        if (this.bufImgSunRaster != null) {
//            this.bufImgSunRaster.setRasterListener(this);
//
//            Raster parentRaster = this.bufImgSunRaster.getParent();
//            if (parentRaster != null) {
//                if (parentRaster instanceof SunWritableRaster) {
//                    // mark subimages stolen to turn off lazy pixel conversion (gznote: can we do better here?)
//                    ((SunWritableRaster) parentRaster).notifyStolen();
//                }
//                rasterStolen();
//            }
//        } else {
//            // it's a custom image (non-natively supported) and we can not set a raster listener
//            // so mark the image as stolen - this will turn off LazyPixelConversion optimization (slow, but correct)
//            rasterStolen();
//        }
//    }
}
