/* * 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 native static 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) == 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 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); } } }