/*
 * Copyright (c) 1995, 2014, 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.Transparency;
import java.awt.AWTException;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DirectColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.ImageConsumer;
import java.awt.image.ImageObserver;
import sun.awt.image.ByteComponentRaster;
import sun.awt.image.IntegerComponentRaster;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import sun.awt.image.ImageWatched;
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.
/** * 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) == 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.
/** * 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 everytime 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); } } }