| /* BufferedImage.java -- |
| Copyright (C) 2000, 2002, 2003, 2004, 2005, 2006, Free Software Foundation |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath 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 for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package java.awt.image; |
| |
| import gnu.java.awt.ComponentDataBlitOp; |
| |
| import java.awt.Graphics; |
| import java.awt.Graphics2D; |
| import java.awt.GraphicsEnvironment; |
| import java.awt.Image; |
| import java.awt.Point; |
| import java.awt.Rectangle; |
| import java.awt.Transparency; |
| import java.awt.color.ColorSpace; |
| import java.util.Hashtable; |
| import java.util.Vector; |
| |
| /** |
| * A buffered image always starts at coordinates (0, 0). |
| * |
| * The buffered image is not subdivided into multiple tiles. Instead, |
| * the image consists of one large tile (0,0) with the width and |
| * height of the image. This tile is always considered to be checked |
| * out. |
| * |
| * @author Rolf W. Rasmussen (rolfwr@ii.uib.no) |
| */ |
| public class BufferedImage extends Image |
| implements WritableRenderedImage, Transparency |
| { |
| public static final int TYPE_CUSTOM = 0, |
| TYPE_INT_RGB = 1, |
| TYPE_INT_ARGB = 2, |
| TYPE_INT_ARGB_PRE = 3, |
| TYPE_INT_BGR = 4, |
| TYPE_3BYTE_BGR = 5, |
| TYPE_4BYTE_ABGR = 6, |
| TYPE_4BYTE_ABGR_PRE = 7, |
| TYPE_USHORT_565_RGB = 8, |
| TYPE_USHORT_555_RGB = 9, |
| TYPE_BYTE_GRAY = 10, |
| TYPE_USHORT_GRAY = 11, |
| TYPE_BYTE_BINARY = 12, |
| TYPE_BYTE_INDEXED = 13; |
| |
| static final int[] bits3 = { 8, 8, 8 }; |
| static final int[] bits4 = { 8, 8, 8, 8 }; |
| static final int[] bits1byte = { 8 }; |
| static final int[] bits1ushort = { 16 }; |
| |
| static final int[] masks_int = { 0x00ff0000, |
| 0x0000ff00, |
| 0x000000ff, |
| DataBuffer.TYPE_INT }; |
| static final int[] masks_565 = { 0xf800, |
| 0x07e0, |
| 0x001f, |
| DataBuffer.TYPE_USHORT}; |
| static final int[] masks_555 = { 0x7c00, |
| 0x03e0, |
| 0x001f, |
| DataBuffer.TYPE_USHORT}; |
| |
| Vector observers; |
| |
| /** |
| * Creates a new <code>BufferedImage</code> with the specified width, height |
| * and type. Valid <code>type</code> values are: |
| * |
| * <ul> |
| * <li>{@link #TYPE_INT_RGB}</li> |
| * <li>{@link #TYPE_INT_ARGB}</li> |
| * <li>{@link #TYPE_INT_ARGB_PRE}</li> |
| * <li>{@link #TYPE_INT_BGR}</li> |
| * <li>{@link #TYPE_3BYTE_BGR}</li> |
| * <li>{@link #TYPE_4BYTE_ABGR}</li> |
| * <li>{@link #TYPE_4BYTE_ABGR_PRE}</li> |
| * <li>{@link #TYPE_USHORT_565_RGB}</li> |
| * <li>{@link #TYPE_USHORT_555_RGB}</li> |
| * <li>{@link #TYPE_BYTE_GRAY}</li> |
| * <li>{@link #TYPE_USHORT_GRAY}</li> |
| * <li>{@link #TYPE_BYTE_BINARY}</li> |
| * <li>{@link #TYPE_BYTE_INDEXED}</li> |
| * </ul> |
| * |
| * @param w the width (must be > 0). |
| * @param h the height (must be > 0). |
| * @param type the image type (see the list of valid types above). |
| * |
| * @throws IllegalArgumentException if <code>w</code> or <code>h</code> is |
| * less than or equal to zero. |
| * @throws IllegalArgumentException if <code>type</code> is not one of the |
| * specified values. |
| */ |
| public BufferedImage(int w, int h, int type) |
| { |
| ColorModel cm = null; |
| |
| boolean alpha = false; |
| boolean premultiplied = false; |
| switch (type) |
| { |
| case TYPE_4BYTE_ABGR_PRE: |
| case TYPE_INT_ARGB_PRE: |
| premultiplied = true; |
| // fall through |
| case TYPE_INT_ARGB: |
| case TYPE_4BYTE_ABGR: |
| alpha = true; |
| } |
| |
| ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); |
| switch (type) |
| { |
| case TYPE_INT_RGB: |
| case TYPE_INT_ARGB: |
| case TYPE_INT_ARGB_PRE: |
| case TYPE_USHORT_565_RGB: |
| case TYPE_USHORT_555_RGB: |
| int[] masks = null; |
| switch (type) |
| { |
| case TYPE_INT_RGB: |
| case TYPE_INT_ARGB: |
| case TYPE_INT_ARGB_PRE: |
| masks = masks_int; |
| break; |
| case TYPE_USHORT_565_RGB: |
| masks = masks_565; |
| break; |
| case TYPE_USHORT_555_RGB: |
| masks = masks_555; |
| break; |
| } |
| |
| cm = new DirectColorModel(cs, |
| 32, // 32 bits in an int |
| masks[0], // r |
| masks[1], // g |
| masks[2], // b |
| alpha ? 0xff000000 : 0, |
| premultiplied, |
| masks[3] // data type |
| ); |
| break; |
| |
| case TYPE_INT_BGR: |
| String msg = |
| "FIXME: Programmer is confused. Why (and how) does a " + |
| "TYPE_INT_BGR image use ComponentColorModel to store " + |
| "8-bit values? Is data type TYPE_INT or TYPE_BYTE. What " + |
| "is the difference between TYPE_INT_BGR and TYPE_3BYTE_BGR?"; |
| throw new UnsupportedOperationException(msg); |
| |
| case TYPE_3BYTE_BGR: |
| case TYPE_4BYTE_ABGR: |
| case TYPE_4BYTE_ABGR_PRE: |
| case TYPE_BYTE_GRAY: |
| case TYPE_USHORT_GRAY: |
| int[] bits = null; |
| int dataType = DataBuffer.TYPE_BYTE; |
| switch (type) { |
| case TYPE_3BYTE_BGR: |
| bits = bits3; |
| break; |
| case TYPE_4BYTE_ABGR: |
| case TYPE_4BYTE_ABGR_PRE: |
| bits = bits4; |
| break; |
| case TYPE_BYTE_GRAY: |
| bits = bits1byte; |
| cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); |
| break; |
| case TYPE_USHORT_GRAY: |
| bits = bits1ushort; |
| cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); |
| dataType = DataBuffer.TYPE_USHORT; |
| break; |
| } |
| cm = new ComponentColorModel(cs, bits, alpha, premultiplied, |
| alpha ? |
| Transparency.TRANSLUCENT: |
| Transparency.OPAQUE, |
| dataType); |
| break; |
| case TYPE_BYTE_BINARY: |
| byte[] vals = { 0, (byte) 0xff }; |
| cm = new IndexColorModel(8, 2, vals, vals, vals); |
| break; |
| case TYPE_BYTE_INDEXED: |
| String msg2 = "type not implemented yet"; |
| throw new UnsupportedOperationException(msg2); |
| // FIXME: build color-cube and create color model |
| default: |
| throw new IllegalArgumentException("Unknown image type " + type); |
| } |
| |
| init(cm, |
| cm.createCompatibleWritableRaster(w, h), |
| premultiplied, |
| null, // no properties |
| type |
| ); |
| } |
| |
| public BufferedImage(int w, int h, int type, |
| IndexColorModel indexcolormodel) |
| { |
| if ((type != TYPE_BYTE_BINARY) && (type != TYPE_BYTE_INDEXED)) |
| throw new IllegalArgumentException("type must be binary or indexed"); |
| |
| init(indexcolormodel, |
| indexcolormodel.createCompatibleWritableRaster(w, h), |
| false, // not premultiplied (guess) |
| null, // no properties |
| type); |
| } |
| |
| public BufferedImage(ColorModel colormodel, |
| WritableRaster writableraster, |
| boolean premultiplied, |
| Hashtable properties) |
| { |
| init(colormodel, writableraster, premultiplied, properties, |
| TYPE_CUSTOM); |
| // TODO: perhaps try to identify type? |
| } |
| |
| WritableRaster raster; |
| ColorModel colorModel; |
| Hashtable properties; |
| boolean isPremultiplied; |
| int type; |
| |
| private void init(ColorModel cm, |
| WritableRaster writableraster, |
| boolean premultiplied, |
| Hashtable properties, |
| int type) |
| { |
| raster = writableraster; |
| colorModel = cm; |
| this.properties = properties; |
| isPremultiplied = premultiplied; |
| this.type = type; |
| } |
| |
| //public void addTileObserver(TileObserver tileobserver) {} |
| |
| public void coerceData(boolean premultiplied) |
| { |
| colorModel = colorModel.coerceData(raster, premultiplied); |
| } |
| |
| public WritableRaster copyData(WritableRaster dest) |
| { |
| if (dest == null) |
| dest = raster.createCompatibleWritableRaster(getMinX(), getMinY(), |
| getWidth(),getHeight()); |
| |
| int x = dest.getMinX(); |
| int y = dest.getMinY(); |
| int w = dest.getWidth(); |
| int h = dest.getHeight(); |
| |
| // create a src child that has the right bounds... |
| WritableRaster src = |
| raster.createWritableChild(x, y, w, h, x, y, |
| null // same bands |
| ); |
| if (src.getSampleModel () instanceof ComponentSampleModel |
| && dest.getSampleModel () instanceof ComponentSampleModel) |
| // Refer to ComponentDataBlitOp for optimized data blitting: |
| ComponentDataBlitOp.INSTANCE.filter(src, dest); |
| else |
| { |
| // slower path |
| int samples[] = src.getPixels (x, y, w, h, (int [])null); |
| dest.setPixels (x, y, w, h, samples); |
| } |
| return dest; |
| } |
| |
| public Graphics2D createGraphics() |
| { |
| GraphicsEnvironment env; |
| env = GraphicsEnvironment.getLocalGraphicsEnvironment (); |
| return env.createGraphics (this); |
| } |
| |
| public void flush() { |
| } |
| |
| public WritableRaster getAlphaRaster() |
| { |
| return colorModel.getAlphaRaster(raster); |
| } |
| |
| public ColorModel getColorModel() |
| { |
| return colorModel; |
| } |
| |
| public Raster getData() |
| { |
| return copyData(null); |
| /* TODO: this might be optimized by returning the same |
| raster (not writable) as long as image data doesn't change. */ |
| } |
| |
| public Raster getData(Rectangle rectangle) |
| { |
| WritableRaster dest = |
| raster.createCompatibleWritableRaster(rectangle); |
| return copyData(dest); |
| } |
| |
| public Graphics getGraphics() |
| { |
| return createGraphics(); |
| } |
| |
| public int getHeight() |
| { |
| return raster.getHeight(); |
| } |
| |
| public int getHeight(ImageObserver imageobserver) |
| { |
| return getHeight(); |
| } |
| |
| public int getMinTileX() |
| { |
| return 0; |
| } |
| |
| public int getMinTileY() |
| { |
| return 0; |
| } |
| |
| public int getMinX() |
| { |
| return 0; |
| } |
| |
| public int getMinY() |
| { |
| return 0; |
| } |
| |
| public int getNumXTiles() |
| { |
| return 1; |
| } |
| |
| public int getNumYTiles() |
| { |
| return 1; |
| } |
| |
| /** |
| * Returns the value of the specified property, or |
| * {@link Image#UndefinedProperty} if the property is not defined. |
| * |
| * @param string the property key (<code>null</code> not permitted). |
| * |
| * @return The property value. |
| * |
| * @throws NullPointerException if <code>string</code> is <code>null</code>. |
| */ |
| public Object getProperty(String string) |
| { |
| if (string == null) |
| throw new NullPointerException("The property name cannot be null."); |
| Object result = Image.UndefinedProperty; |
| if (properties != null) |
| { |
| Object v = properties.get(string); |
| if (v != null) |
| result = v; |
| } |
| return result; |
| } |
| |
| public Object getProperty(String string, ImageObserver imageobserver) |
| { |
| return getProperty(string); |
| } |
| |
| /** |
| * Returns <code>null</code> always. |
| * |
| * @return <code>null</code> always. |
| */ |
| public String[] getPropertyNames() |
| { |
| // This method should always return null, see: |
| // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4640609 |
| return null; |
| } |
| |
| public int getRGB(int x, int y) |
| { |
| Object rgbElem = raster.getDataElements(x, y, |
| null // create as needed |
| ); |
| return colorModel.getRGB(rgbElem); |
| } |
| |
| public int[] getRGB(int startX, int startY, int w, int h, |
| int[] rgbArray, |
| int offset, int scanlineStride) |
| { |
| if (rgbArray == null) |
| { |
| /* |
| 000000000000000000 |
| 00000[#######----- [ = start |
| -----########----- ] = end |
| -----#######]00000 |
| 000000000000000000 */ |
| int size = (h-1)*scanlineStride + w; |
| rgbArray = new int[size]; |
| } |
| |
| int endX = startX + w; |
| int endY = startY + h; |
| |
| /* *TODO*: |
| Opportunity for optimization by examining color models... |
| |
| Perhaps wrap the rgbArray up in a WritableRaster with packed |
| sRGB color model and perform optimized rendering into the |
| array. */ |
| |
| Object rgbElem = null; |
| for (int y=startY; y<endY; y++) |
| { |
| int xoffset = offset; |
| for (int x=startX; x<endX; x++) |
| { |
| int rgb; |
| rgbElem = raster.getDataElements(x, y, rgbElem); |
| rgb = colorModel.getRGB(rgbElem); |
| rgbArray[xoffset++] = rgb; |
| } |
| offset += scanlineStride; |
| } |
| return rgbArray; |
| } |
| |
| public WritableRaster getRaster() |
| { |
| return raster; |
| } |
| |
| public SampleModel getSampleModel() |
| { |
| return raster.getSampleModel(); |
| } |
| |
| public ImageProducer getSource() |
| { |
| return new ImageProducer() { |
| |
| Vector consumers = new Vector(); |
| |
| public void addConsumer(ImageConsumer ic) |
| { |
| if(!consumers.contains(ic)) |
| consumers.add(ic); |
| } |
| |
| public boolean isConsumer(ImageConsumer ic) |
| { |
| return consumers.contains(ic); |
| } |
| |
| public void removeConsumer(ImageConsumer ic) |
| { |
| consumers.remove(ic); |
| } |
| |
| public void startProduction(ImageConsumer ic) |
| { |
| int x = 0; |
| int y = 0; |
| int width = getWidth(); |
| int height = getHeight(); |
| int stride = width; |
| int offset = 0; |
| int[] pixels = getRGB(x, y, |
| width, height, |
| (int[])null, offset, stride); |
| // We already convert the color to RGB in the getRGB call, so |
| // we pass a simple RGB color model to the consumers. |
| ColorModel model = new DirectColorModel(32, 0xff0000, 0xff00, 0xff, |
| 0xff000000); |
| |
| consumers.add(ic); |
| |
| for(int i=0;i<consumers.size();i++) |
| { |
| ImageConsumer c = (ImageConsumer) consumers.elementAt(i); |
| c.setHints(ImageConsumer.SINGLEPASS); |
| c.setDimensions(getWidth(), getHeight()); |
| c.setPixels(x, y, width, height, model, pixels, offset, stride); |
| c.imageComplete(ImageConsumer.STATICIMAGEDONE); |
| } |
| } |
| |
| public void requestTopDownLeftRightResend(ImageConsumer ic) |
| { |
| startProduction(ic); |
| } |
| |
| }; |
| } |
| |
| public Vector getSources() |
| { |
| return null; |
| } |
| |
| public BufferedImage getSubimage(int x, int y, int w, int h) |
| { |
| WritableRaster subRaster = |
| getRaster().createWritableChild(x, y, w, h, 0, 0, null); |
| |
| return new BufferedImage(getColorModel(), |
| subRaster, |
| isPremultiplied, |
| properties); |
| } |
| |
| public Raster getTile(int tileX, int tileY) |
| { |
| return getWritableTile(tileX, tileY); |
| } |
| |
| public int getTileGridXOffset() |
| { |
| return 0; // according to javadocs |
| } |
| |
| public int getTileGridYOffset() |
| { |
| return 0; // according to javadocs |
| } |
| |
| public int getTileHeight() |
| { |
| return getHeight(); // image is one big tile |
| } |
| |
| public int getTileWidth() |
| { |
| return getWidth(); // image is one big tile |
| } |
| |
| public int getType() |
| { |
| return type; |
| } |
| |
| public int getWidth() |
| { |
| return raster.getWidth(); |
| } |
| |
| public int getWidth(ImageObserver imageobserver) |
| { |
| return getWidth(); |
| } |
| |
| public WritableRaster getWritableTile(int tileX, int tileY) |
| { |
| isTileWritable(tileX, tileY); // for exception |
| return raster; |
| } |
| |
| private static final Point[] tileIndices = { new Point() }; |
| |
| public Point[] getWritableTileIndices() |
| { |
| return tileIndices; |
| } |
| |
| public boolean hasTileWriters() |
| { |
| return true; |
| } |
| |
| public boolean isAlphaPremultiplied() |
| { |
| return isPremultiplied; |
| } |
| |
| public boolean isTileWritable(int tileX, int tileY) |
| { |
| if ((tileX != 0) || (tileY != 0)) |
| throw new ArrayIndexOutOfBoundsException("only tile is (0,0)"); |
| return true; |
| } |
| |
| public void releaseWritableTile(int tileX, int tileY) |
| { |
| isTileWritable(tileX, tileY); // for exception |
| } |
| |
| //public void removeTileObserver(TileObserver tileobserver) {} |
| |
| public void setData(Raster src) |
| { |
| int x = src.getMinX(); |
| int y = src.getMinY(); |
| int w = src.getWidth(); |
| int h = src.getHeight(); |
| |
| // create a dest child that has the right bounds... |
| WritableRaster dest = |
| raster.createWritableChild(x, y, w, h, x, y, |
| null // same bands |
| ); |
| |
| if (src.getSampleModel () instanceof ComponentSampleModel |
| && dest.getSampleModel () instanceof ComponentSampleModel) |
| |
| // Refer to ComponentDataBlitOp for optimized data blitting: |
| ComponentDataBlitOp.INSTANCE.filter(src, dest); |
| else |
| { |
| // slower path |
| int samples[] = src.getPixels (x, y, w, h, (int [])null); |
| dest.setPixels (x, y, w, h, samples); |
| } |
| } |
| |
| public void setRGB(int x, int y, int argb) |
| { |
| Object rgbElem = colorModel.getDataElements(argb, null); |
| raster.setDataElements(x, y, rgbElem); |
| } |
| |
| public void setRGB(int startX, int startY, int w, int h, |
| int[] argbArray, int offset, int scanlineStride) |
| { |
| int endX = startX + w; |
| int endY = startY + h; |
| |
| Object rgbElem = null; |
| for (int y=startY; y<endY; y++) |
| { |
| int xoffset = offset; |
| for (int x=startX; x<endX; x++) |
| { |
| int argb = argbArray[xoffset++]; |
| rgbElem = colorModel.getDataElements(argb, rgbElem); |
| raster.setDataElements(x, y, rgbElem); |
| } |
| offset += scanlineStride; |
| } |
| } |
| |
| public String toString() |
| { |
| StringBuffer buf; |
| |
| buf = new StringBuffer(/* estimated length */ 120); |
| buf.append("BufferedImage@"); |
| buf.append(Integer.toHexString(hashCode())); |
| buf.append(": type="); |
| buf.append(type); |
| buf.append(' '); |
| buf.append(colorModel); |
| buf.append(' '); |
| buf.append(raster); |
| |
| return buf.toString(); |
| } |
| |
| |
| /** |
| * Adds a tile observer. If the observer is already present, it receives |
| * multiple notifications. |
| * |
| * @param to The TileObserver to add. |
| */ |
| public void addTileObserver (TileObserver to) |
| { |
| if (observers == null) |
| observers = new Vector (); |
| |
| observers.add (to); |
| } |
| |
| /** |
| * Removes a tile observer. If the observer was not registered, |
| * nothing happens. If the observer was registered for multiple |
| * notifications, it is now registered for one fewer notification. |
| * |
| * @param to The TileObserver to remove. |
| */ |
| public void removeTileObserver (TileObserver to) |
| { |
| if (observers == null) |
| return; |
| |
| observers.remove (to); |
| } |
| |
| /** |
| * Return the transparency type. |
| * |
| * @return One of {@link #OPAQUE}, {@link #BITMASK}, or {@link #TRANSLUCENT}. |
| * @see Transparency#getTransparency() |
| * @since 1.5 |
| */ |
| public int getTransparency() |
| { |
| return colorModel.getTransparency(); |
| } |
| } |