Java tutorial
/* * Copyright (c) 1997, 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 java.awt.image; import java.awt.Graphics2D; import java.awt.GraphicsEnvironment; import java.awt.Point; import java.awt.Rectangle; import java.awt.Transparency; import java.awt.color.ColorSpace; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Hashtable; import java.util.Set; import java.util.Vector; import sun.awt.image.ByteComponentRaster; import sun.awt.image.BytePackedRaster; import sun.awt.image.IntegerComponentRaster; import sun.awt.image.OffScreenImageSource; import sun.awt.image.ShortComponentRaster; /** * * The {@code BufferedImage} subclass describes an {@link * java.awt.Image Image} with an accessible buffer of image data. * A {@code BufferedImage} is comprised of a {@link ColorModel} and a * {@link Raster} of image data. * The number and types of bands in the {@link SampleModel} of the * {@code Raster} must match the number and types required by the * {@code ColorModel} to represent its color and alpha components. * All {@code BufferedImage} objects have an upper left corner * coordinate of (0, 0). Any {@code Raster} used to construct a * {@code BufferedImage} must therefore have minX=0 and minY=0. * * <p> * This class relies on the data fetching and setting methods * of {@code Raster}, * and on the color characterization methods of {@code ColorModel}. * * @see ColorModel * @see Raster * @see WritableRaster */ public class BufferedImage extends java.awt.Image implements WritableRenderedImage, Transparency { private int imageType = TYPE_CUSTOM; private ColorModel colorModel; private final WritableRaster raster; private OffScreenImageSource osis; private Hashtable<String, Object> properties; /** * Image Type Constants */ /** * Image type is not recognized so it must be a customized * image. This type is only used as a return value for the getType() * method. */ public static final int TYPE_CUSTOM = 0; /** * Represents an image with 8-bit RGB color components packed into * integer pixels. The image has a {@link DirectColorModel} without * alpha. * When data with non-opaque alpha is stored * in an image of this type, * the color data must be adjusted to a non-premultiplied form * and the alpha discarded, * as described in the * {@link java.awt.AlphaComposite} documentation. */ public static final int TYPE_INT_RGB = 1; /** * Represents an image with 8-bit RGBA color components packed into * integer pixels. The image has a {@code DirectColorModel} * with alpha. The color data in this image is considered not to be * premultiplied with alpha. When this type is used as the * {@code imageType} argument to a {@code BufferedImage} * constructor, the created image is consistent with images * created in the JDK1.1 and earlier releases. */ public static final int TYPE_INT_ARGB = 2; /** * Represents an image with 8-bit RGBA color components packed into * integer pixels. The image has a {@code DirectColorModel} * with alpha. The color data in this image is considered to be * premultiplied with alpha. */ public static final int TYPE_INT_ARGB_PRE = 3; /** * Represents an image with 8-bit RGB color components, corresponding * to a Windows- or Solaris- style BGR color model, with the colors * Blue, Green, and Red packed into integer pixels. There is no alpha. * The image has a {@link DirectColorModel}. * When data with non-opaque alpha is stored * in an image of this type, * the color data must be adjusted to a non-premultiplied form * and the alpha discarded, * as described in the * {@link java.awt.AlphaComposite} documentation. */ public static final int TYPE_INT_BGR = 4; /** * Represents an image with 8-bit RGB color components, corresponding * to a Windows-style BGR color model) with the colors Blue, Green, * and Red stored in 3 bytes. There is no alpha. The image has a * {@code ComponentColorModel}. * When data with non-opaque alpha is stored * in an image of this type, * the color data must be adjusted to a non-premultiplied form * and the alpha discarded, * as described in the * {@link java.awt.AlphaComposite} documentation. */ public static final int TYPE_3BYTE_BGR = 5; /** * Represents an image with 8-bit RGBA color components with the colors * Blue, Green, and Red stored in 3 bytes and 1 byte of alpha. The * image has a {@code ComponentColorModel} with alpha. The * color data in this image is considered not to be premultiplied with * alpha. The byte data is interleaved in a single * byte array in the order A, B, G, R * from lower to higher byte addresses within each pixel. */ public static final int TYPE_4BYTE_ABGR = 6; /** * Represents an image with 8-bit RGBA color components with the colors * Blue, Green, and Red stored in 3 bytes and 1 byte of alpha. The * image has a {@code ComponentColorModel} with alpha. The color * data in this image is considered to be premultiplied with alpha. * The byte data is interleaved in a single byte array in the order * A, B, G, R from lower to higher byte addresses within each pixel. */ public static final int TYPE_4BYTE_ABGR_PRE = 7; /** * Represents an image with 5-6-5 RGB color components (5-bits red, * 6-bits green, 5-bits blue) with no alpha. This image has * a {@code DirectColorModel}. * When data with non-opaque alpha is stored * in an image of this type, * the color data must be adjusted to a non-premultiplied form * and the alpha discarded, * as described in the * {@link java.awt.AlphaComposite} documentation. */ public static final int TYPE_USHORT_565_RGB = 8; /** * Represents an image with 5-5-5 RGB color components (5-bits red, * 5-bits green, 5-bits blue) with no alpha. This image has * a {@code DirectColorModel}. * When data with non-opaque alpha is stored * in an image of this type, * the color data must be adjusted to a non-premultiplied form * and the alpha discarded, * as described in the * {@link java.awt.AlphaComposite} documentation. */ public static final int TYPE_USHORT_555_RGB = 9; /** * Represents a unsigned byte grayscale image, non-indexed. This * image has a {@code ComponentColorModel} with a CS_GRAY * {@link ColorSpace}. * When data with non-opaque alpha is stored * in an image of this type, * the color data must be adjusted to a non-premultiplied form * and the alpha discarded, * as described in the * {@link java.awt.AlphaComposite} documentation. */ public static final int TYPE_BYTE_GRAY = 10; /** * Represents an unsigned short grayscale image, non-indexed). This * image has a {@code ComponentColorModel} with a CS_GRAY * {@code ColorSpace}. * When data with non-opaque alpha is stored * in an image of this type, * the color data must be adjusted to a non-premultiplied form * and the alpha discarded, * as described in the * {@link java.awt.AlphaComposite} documentation. */ public static final int TYPE_USHORT_GRAY = 11; /** * Represents an opaque byte-packed 1, 2, or 4 bit image. The * image has an {@link IndexColorModel} without alpha. When this * type is used as the {@code imageType} argument to the * {@code BufferedImage} constructor that takes an * {@code imageType} argument but no {@code ColorModel} * argument, a 1-bit image is created with an * {@code IndexColorModel} with two colors in the default * sRGB {@code ColorSpace}: {0, 0, 0} and * {255, 255, 255}. * * <p> Images with 2 or 4 bits per pixel may be constructed via * the {@code BufferedImage} constructor that takes a * {@code ColorModel} argument by supplying a * {@code ColorModel} with an appropriate map size. * * <p> Images with 8 bits per pixel should use the image types * {@code TYPE_BYTE_INDEXED} or {@code TYPE_BYTE_GRAY} * depending on their {@code ColorModel}. * <p> When color data is stored in an image of this type, * the closest color in the colormap is determined * by the {@code IndexColorModel} and the resulting index is stored. * Approximation and loss of alpha or color components * can result, depending on the colors in the * {@code IndexColorModel} colormap. */ public static final int TYPE_BYTE_BINARY = 12; /** * Represents an indexed byte image. When this type is used as the * {@code imageType} argument to the {@code BufferedImage} * constructor that takes an {@code imageType} argument * but no {@code ColorModel} argument, an * {@code IndexColorModel} is created with * a 256-color 6/6/6 color cube palette with the rest of the colors * from 216-255 populated by grayscale values in the * default sRGB ColorSpace. * * <p> When color data is stored in an image of this type, * the closest color in the colormap is determined * by the {@code IndexColorModel} and the resulting index is stored. * Approximation and loss of alpha or color components * can result, depending on the colors in the * {@code IndexColorModel} colormap. */ public static final int TYPE_BYTE_INDEXED = 13; private static final int DCM_RED_MASK = 0x00ff0000; private static final int DCM_GREEN_MASK = 0x0000ff00; private static final int DCM_BLUE_MASK = 0x000000ff; private static final int DCM_ALPHA_MASK = 0xff000000; private static final int DCM_565_RED_MASK = 0xf800; private static final int DCM_565_GRN_MASK = 0x07E0; private static final int DCM_565_BLU_MASK = 0x001F; private static final int DCM_555_RED_MASK = 0x7C00; private static final int DCM_555_GRN_MASK = 0x03E0; private static final int DCM_555_BLU_MASK = 0x001F; private static final int DCM_BGR_RED_MASK = 0x0000ff; private static final int DCM_BGR_GRN_MASK = 0x00ff00; private static final int DCM_BGR_BLU_MASK = 0xff0000; private static native void initIDs(); static { ColorModel.loadLibraries(); initIDs(); } /** * Constructs a {@code BufferedImage} of one of the predefined * image types. The {@code ColorSpace} for the image is the * default sRGB space. * @param width width of the created image * @param height height of the created image * @param imageType type of the created image * @see ColorSpace * @see #TYPE_INT_RGB * @see #TYPE_INT_ARGB * @see #TYPE_INT_ARGB_PRE * @see #TYPE_INT_BGR * @see #TYPE_3BYTE_BGR * @see #TYPE_4BYTE_ABGR * @see #TYPE_4BYTE_ABGR_PRE * @see #TYPE_BYTE_GRAY * @see #TYPE_USHORT_GRAY * @see #TYPE_BYTE_BINARY * @see #TYPE_BYTE_INDEXED * @see #TYPE_USHORT_565_RGB * @see #TYPE_USHORT_555_RGB */ public BufferedImage(int width, int height, int imageType) { switch (imageType) { case TYPE_INT_RGB: { colorModel = new DirectColorModel(24, 0x00ff0000, // Red 0x0000ff00, // Green 0x000000ff, // Blue 0x0 // Alpha ); raster = colorModel.createCompatibleWritableRaster(width, height); } break; case TYPE_INT_ARGB: { colorModel = ColorModel.getRGBdefault(); raster = colorModel.createCompatibleWritableRaster(width, height); } break; case TYPE_INT_ARGB_PRE: { colorModel = new DirectColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), 32, 0x00ff0000, // Red 0x0000ff00, // Green 0x000000ff, // Blue 0xff000000, // Alpha true, // Alpha Premultiplied DataBuffer.TYPE_INT); raster = colorModel.createCompatibleWritableRaster(width, height); } break; case TYPE_INT_BGR: { colorModel = new DirectColorModel(24, 0x000000ff, // Red 0x0000ff00, // Green 0x00ff0000 // Blue ); raster = colorModel.createCompatibleWritableRaster(width, height); } break; case TYPE_3BYTE_BGR: { ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); int[] nBits = { 8, 8, 8 }; int[] bOffs = { 2, 1, 0 }; colorModel = new ComponentColorModel(cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height, width * 3, 3, bOffs, null); } break; case TYPE_4BYTE_ABGR: { ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); int[] nBits = { 8, 8, 8, 8 }; int[] bOffs = { 3, 2, 1, 0 }; colorModel = new ComponentColorModel(cs, nBits, true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE); raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height, width * 4, 4, bOffs, null); } break; case TYPE_4BYTE_ABGR_PRE: { ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); int[] nBits = { 8, 8, 8, 8 }; int[] bOffs = { 3, 2, 1, 0 }; colorModel = new ComponentColorModel(cs, nBits, true, true, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE); raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height, width * 4, 4, bOffs, null); } break; case TYPE_BYTE_GRAY: { ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); int[] nBits = { 8 }; colorModel = new ComponentColorModel(cs, nBits, false, true, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); raster = colorModel.createCompatibleWritableRaster(width, height); } break; case TYPE_USHORT_GRAY: { ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); int[] nBits = { 16 }; colorModel = new ComponentColorModel(cs, nBits, false, true, Transparency.OPAQUE, DataBuffer.TYPE_USHORT); raster = colorModel.createCompatibleWritableRaster(width, height); } break; case TYPE_BYTE_BINARY: { byte[] arr = { (byte) 0, (byte) 0xff }; colorModel = new IndexColorModel(1, 2, arr, arr, arr); raster = Raster.createPackedRaster(DataBuffer.TYPE_BYTE, width, height, 1, 1, null); } break; case TYPE_BYTE_INDEXED: { // Create a 6x6x6 color cube int[] cmap = new int[256]; int i = 0; for (int r = 0; r < 256; r += 51) { for (int g = 0; g < 256; g += 51) { for (int b = 0; b < 256; b += 51) { cmap[i++] = (r << 16) | (g << 8) | b; } } } // And populate the rest of the cmap with gray values int grayIncr = 256 / (256 - i); // The gray ramp will be between 18 and 252 int gray = grayIncr * 3; for (; i < 256; i++) { cmap[i] = (gray << 16) | (gray << 8) | gray; gray += grayIncr; } colorModel = new IndexColorModel(8, 256, cmap, 0, false, -1, DataBuffer.TYPE_BYTE); raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height, 1, null); } break; case TYPE_USHORT_565_RGB: { colorModel = new DirectColorModel(16, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK); raster = colorModel.createCompatibleWritableRaster(width, height); } break; case TYPE_USHORT_555_RGB: { colorModel = new DirectColorModel(15, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK); raster = colorModel.createCompatibleWritableRaster(width, height); } break; default: throw new IllegalArgumentException("Unknown image type " + imageType); } this.imageType = imageType; } /** * Constructs a {@code BufferedImage} of one of the predefined * image types: * TYPE_BYTE_BINARY or TYPE_BYTE_INDEXED. * * <p> If the image type is TYPE_BYTE_BINARY, the number of * entries in the color model is used to determine whether the * image should have 1, 2, or 4 bits per pixel. If the color model * has 1 or 2 entries, the image will have 1 bit per pixel. If it * has 3 or 4 entries, the image with have 2 bits per pixel. If * it has between 5 and 16 entries, the image will have 4 bits per * pixel. Otherwise, an IllegalArgumentException will be thrown. * * @param width width of the created image * @param height height of the created image * @param imageType type of the created image * @param cm {@code IndexColorModel} of the created image * @throws IllegalArgumentException if the imageType is not * TYPE_BYTE_BINARY or TYPE_BYTE_INDEXED or if the imageType is * TYPE_BYTE_BINARY and the color map has more than 16 entries. * @see #TYPE_BYTE_BINARY * @see #TYPE_BYTE_INDEXED */ public BufferedImage(int width, int height, int imageType, IndexColorModel cm) { if (cm.hasAlpha() && cm.isAlphaPremultiplied()) { throw new IllegalArgumentException("This image types do not have " + "premultiplied alpha."); } switch (imageType) { case TYPE_BYTE_BINARY: int bits; // Will be set below int mapSize = cm.getMapSize(); if (mapSize <= 2) { bits = 1; } else if (mapSize <= 4) { bits = 2; } else if (mapSize <= 16) { bits = 4; } else { throw new IllegalArgumentException( "Color map for TYPE_BYTE_BINARY " + "must have no more than 16 entries"); } raster = Raster.createPackedRaster(DataBuffer.TYPE_BYTE, width, height, 1, bits, null); break; case TYPE_BYTE_INDEXED: raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height, 1, null); break; default: throw new IllegalArgumentException("Invalid image type (" + imageType + "). Image type must" + " be either TYPE_BYTE_BINARY or " + " TYPE_BYTE_INDEXED"); } if (!cm.isCompatibleRaster(raster)) { throw new IllegalArgumentException("Incompatible image type and IndexColorModel"); } colorModel = cm; this.imageType = imageType; } /** * Constructs a new {@code BufferedImage} with a specified * {@code ColorModel} and {@code Raster}. If the number and * types of bands in the {@code SampleModel} of the * {@code Raster} do not match the number and types required by * the {@code ColorModel} to represent its color and alpha * components, a {@link RasterFormatException} is thrown. This * method can multiply or divide the color {@code Raster} data by * alpha to match the {@code alphaPremultiplied} state * in the {@code ColorModel}. Properties for this * {@code BufferedImage} can be established by passing * in a {@link Hashtable} of {@code String}/{@code Object} * pairs. * @param cm {@code ColorModel} for the new image * @param raster {@code Raster} for the image data * @param isRasterPremultiplied if {@code true}, the data in * the raster has been premultiplied with alpha. * @param properties {@code Hashtable} of * {@code String}/{@code Object} pairs. * @exception RasterFormatException if the number and * types of bands in the {@code SampleModel} of the * {@code Raster} do not match the number and types required by * the {@code ColorModel} to represent its color and alpha * components. * @exception IllegalArgumentException if * {@code raster} is incompatible with {@code cm} * @see ColorModel * @see Raster * @see WritableRaster */ /* * * FOR NOW THE CODE WHICH DEFINES THE RASTER TYPE IS DUPLICATED BY DVF * SEE THE METHOD DEFINERASTERTYPE @ RASTEROUTPUTMANAGER * */ public BufferedImage(ColorModel cm, WritableRaster raster, boolean isRasterPremultiplied, Hashtable<?, ?> properties) { if (!cm.isCompatibleRaster(raster)) { throw new IllegalArgumentException("Raster " + raster + " is incompatible with ColorModel " + cm); } if ((raster.minX != 0) || (raster.minY != 0)) { throw new IllegalArgumentException( "Raster " + raster + " has minX or minY not equal to zero: " + raster.minX + " " + raster.minY); } colorModel = cm; this.raster = raster; if (properties != null && !properties.isEmpty()) { this.properties = new Hashtable<>(); for (final Object key : properties.keySet()) { if (key instanceof String) { this.properties.put((String) key, properties.get(key)); } } } int numBands = raster.getNumBands(); boolean isAlphaPre = cm.isAlphaPremultiplied(); final boolean isStandard = isStandard(cm, raster); ColorSpace cs; // Force the raster data alpha state to match the premultiplied // state in the color model coerceData(isRasterPremultiplied); SampleModel sm = raster.getSampleModel(); cs = cm.getColorSpace(); int csType = cs.getType(); if (csType != ColorSpace.TYPE_RGB) { if (csType == ColorSpace.TYPE_GRAY && isStandard && cm instanceof ComponentColorModel) { // Check if this might be a child raster (fix for bug 4240596) if (sm instanceof ComponentSampleModel && ((ComponentSampleModel) sm).getPixelStride() != numBands) { imageType = TYPE_CUSTOM; } else if (raster instanceof ByteComponentRaster && raster.getNumBands() == 1 && cm.getComponentSize(0) == 8 && ((ByteComponentRaster) raster).getPixelStride() == 1) { imageType = TYPE_BYTE_GRAY; } else if (raster instanceof ShortComponentRaster && raster.getNumBands() == 1 && cm.getComponentSize(0) == 16 && ((ShortComponentRaster) raster).getPixelStride() == 1) { imageType = TYPE_USHORT_GRAY; } } else { imageType = TYPE_CUSTOM; } return; } if ((raster instanceof IntegerComponentRaster) && (numBands == 3 || numBands == 4)) { IntegerComponentRaster iraster = (IntegerComponentRaster) raster; // Check if the raster params and the color model // are correct int pixSize = cm.getPixelSize(); if (iraster.getPixelStride() == 1 && isStandard && cm instanceof DirectColorModel && (pixSize == 32 || pixSize == 24)) { // Now check on the DirectColorModel params DirectColorModel dcm = (DirectColorModel) cm; int rmask = dcm.getRedMask(); int gmask = dcm.getGreenMask(); int bmask = dcm.getBlueMask(); if (rmask == DCM_RED_MASK && gmask == DCM_GREEN_MASK && bmask == DCM_BLUE_MASK) { if (dcm.getAlphaMask() == DCM_ALPHA_MASK) { imageType = (isAlphaPre ? TYPE_INT_ARGB_PRE : TYPE_INT_ARGB); } else { // No Alpha if (!dcm.hasAlpha()) { imageType = TYPE_INT_RGB; } } } // if (dcm.getRedMask() == DCM_RED_MASK && else if (rmask == DCM_BGR_RED_MASK && gmask == DCM_BGR_GRN_MASK && bmask == DCM_BGR_BLU_MASK) { if (!dcm.hasAlpha()) { imageType = TYPE_INT_BGR; } } // if (rmask == DCM_BGR_RED_MASK && } // if (iraster.getPixelStride() == 1 } // ((raster instanceof IntegerComponentRaster) && else if ((cm instanceof IndexColorModel) && (numBands == 1) && isStandard && (!cm.hasAlpha() || !isAlphaPre)) { IndexColorModel icm = (IndexColorModel) cm; int pixSize = icm.getPixelSize(); if (raster instanceof BytePackedRaster) { imageType = TYPE_BYTE_BINARY; } // if (raster instanceof BytePackedRaster) else if (raster instanceof ByteComponentRaster) { ByteComponentRaster braster = (ByteComponentRaster) raster; if (braster.getPixelStride() == 1 && pixSize <= 8) { imageType = TYPE_BYTE_INDEXED; } } } // else if (cm instanceof IndexColorModel) && (numBands == 1)) else if ((raster instanceof ShortComponentRaster) && (cm instanceof DirectColorModel) && isStandard && (numBands == 3) && !cm.hasAlpha()) { DirectColorModel dcm = (DirectColorModel) cm; if (dcm.getRedMask() == DCM_565_RED_MASK) { if (dcm.getGreenMask() == DCM_565_GRN_MASK && dcm.getBlueMask() == DCM_565_BLU_MASK) { imageType = TYPE_USHORT_565_RGB; } } else if (dcm.getRedMask() == DCM_555_RED_MASK) { if (dcm.getGreenMask() == DCM_555_GRN_MASK && dcm.getBlueMask() == DCM_555_BLU_MASK) { imageType = TYPE_USHORT_555_RGB; } } } // else if ((cm instanceof IndexColorModel) && (numBands == 1)) else if ((raster instanceof ByteComponentRaster) && (cm instanceof ComponentColorModel) && isStandard && (raster.getSampleModel() instanceof PixelInterleavedSampleModel) && (numBands == 3 || numBands == 4)) { ComponentColorModel ccm = (ComponentColorModel) cm; PixelInterleavedSampleModel csm = (PixelInterleavedSampleModel) raster.getSampleModel(); ByteComponentRaster braster = (ByteComponentRaster) raster; int[] offs = csm.getBandOffsets(); if (ccm.getNumComponents() != numBands) { throw new RasterFormatException("Number of components in " + "ColorModel (" + ccm.getNumComponents() + ") does not match # in " + " Raster (" + numBands + ")"); } int[] nBits = ccm.getComponentSize(); boolean is8bit = true; for (int i = 0; i < numBands; i++) { if (nBits[i] != 8) { is8bit = false; break; } } if (is8bit && braster.getPixelStride() == numBands && offs[0] == numBands - 1 && offs[1] == numBands - 2 && offs[2] == numBands - 3) { if (numBands == 3 && !ccm.hasAlpha()) { imageType = TYPE_3BYTE_BGR; } else if (offs[3] == 0 && ccm.hasAlpha()) { imageType = (isAlphaPre ? TYPE_4BYTE_ABGR_PRE : TYPE_4BYTE_ABGR); } } } // else if ((raster instanceof ByteComponentRaster) && } private static boolean isStandard(ColorModel cm, WritableRaster wr) { final Class<? extends ColorModel> cmClass = cm.getClass(); final Class<? extends WritableRaster> wrClass = wr.getClass(); final Class<? extends SampleModel> smClass = wr.getSampleModel().getClass(); final PrivilegedAction<Boolean> checkClassLoadersAction = new PrivilegedAction<Boolean>() { @Override public Boolean run() { final ClassLoader std = System.class.getClassLoader(); return (cmClass.getClassLoader() == std) && (smClass.getClassLoader() == std) && (wrClass.getClassLoader() == std); } }; return AccessController.doPrivileged(checkClassLoadersAction); } /** * Returns the image type. If it is not one of the known types, * TYPE_CUSTOM is returned. * @return the image type of this {@code BufferedImage}. * @see #TYPE_INT_RGB * @see #TYPE_INT_ARGB * @see #TYPE_INT_ARGB_PRE * @see #TYPE_INT_BGR * @see #TYPE_3BYTE_BGR * @see #TYPE_4BYTE_ABGR * @see #TYPE_4BYTE_ABGR_PRE * @see #TYPE_BYTE_GRAY * @see #TYPE_BYTE_BINARY * @see #TYPE_BYTE_INDEXED * @see #TYPE_USHORT_GRAY * @see #TYPE_USHORT_565_RGB * @see #TYPE_USHORT_555_RGB * @see #TYPE_CUSTOM */ public int getType() { return imageType; } /** * Returns the {@code ColorModel}. * @return the {@code ColorModel} of this * {@code BufferedImage}. */ public ColorModel getColorModel() { return colorModel; } /** * Returns the {@link WritableRaster}. * @return the {@code WritableRaster} of this * {@code BufferedImage}. */ public WritableRaster getRaster() { return raster; } /** * Returns a {@code WritableRaster} representing the alpha * channel for {@code BufferedImage} objects * with {@code ColorModel} objects that support a separate * spatial alpha channel, such as {@code ComponentColorModel} and * {@code DirectColorModel}. Returns {@code null} if there * is no alpha channel associated with the {@code ColorModel} in * this image. This method assumes that for all * {@code ColorModel} objects other than * {@code IndexColorModel}, if the {@code ColorModel} * supports alpha, there is a separate alpha channel * which is stored as the last band of image data. * If the image uses an {@code IndexColorModel} that * has alpha in the lookup table, this method returns * {@code null} since there is no spatially discrete alpha * channel. This method creates a new * {@code WritableRaster}, but shares the data array. * @return a {@code WritableRaster} or {@code null} if this * {@code BufferedImage} has no alpha channel associated * with its {@code ColorModel}. */ public WritableRaster getAlphaRaster() { return colorModel.getAlphaRaster(raster); } /** * Returns an integer pixel in the default RGB color model * (TYPE_INT_ARGB) and default sRGB colorspace. Color * conversion takes place if this default model does not match * the image {@code ColorModel}. There are only 8-bits of * precision for each color component in the returned data when using * this method. * * <p> * * An {@code ArrayOutOfBoundsException} may be thrown * if the coordinates are not in bounds. * However, explicit bounds checking is not guaranteed. * * @param x the X coordinate of the pixel from which to get * the pixel in the default RGB color model and sRGB * color space * @param y the Y coordinate of the pixel from which to get * the pixel in the default RGB color model and sRGB * color space * @return an integer pixel in the default RGB color model and * default sRGB colorspace. * @see #setRGB(int, int, int) * @see #setRGB(int, int, int, int, int[], int, int) */ public int getRGB(int x, int y) { return colorModel.getRGB(raster.getDataElements(x, y, null)); } /** * Returns an array of integer pixels in the default RGB color model * (TYPE_INT_ARGB) and default sRGB color space, * from a portion of the image data. Color conversion takes * place if the default model does not match the image * {@code ColorModel}. There are only 8-bits of precision for * each color component in the returned data when * using this method. With a specified coordinate (x, y) in the * image, the ARGB pixel can be accessed in this way: * * <pre> * pixel = rgbArray[offset + (y-startY)*scansize + (x-startX)]; </pre> * * <p> * * An {@code ArrayOutOfBoundsException} may be thrown * if the region is not in bounds. * However, explicit bounds checking is not guaranteed. * * @param startX the starting X coordinate * @param startY the starting Y coordinate * @param w width of region * @param h height of region * @param rgbArray if not {@code null}, the rgb pixels are * written here * @param offset offset into the {@code rgbArray} * @param scansize scanline stride for the {@code rgbArray} * @return array of RGB pixels. * @see #setRGB(int, int, int) * @see #setRGB(int, int, int, int, int[], int, int) */ public int[] getRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize) { int yoff = offset; int off; Object data; int nbands = raster.getNumBands(); int dataType = raster.getDataBuffer().getDataType(); switch (dataType) { case DataBuffer.TYPE_BYTE: data = new byte[nbands]; break; case DataBuffer.TYPE_USHORT: data = new short[nbands]; break; case DataBuffer.TYPE_INT: data = new int[nbands]; break; case DataBuffer.TYPE_FLOAT: data = new float[nbands]; break; case DataBuffer.TYPE_DOUBLE: data = new double[nbands]; break; default: throw new IllegalArgumentException("Unknown data buffer type: " + dataType); } if (rgbArray == null) { rgbArray = new int[offset + h * scansize]; } for (int y = startY; y < startY + h; y++, yoff += scansize) { off = yoff; for (int x = startX; x < startX + w; x++) { rgbArray[off++] = colorModel.getRGB(raster.getDataElements(x, y, data)); } } return rgbArray; } /** * Sets a pixel in this {@code BufferedImage} to the specified * RGB value. The pixel is assumed to be in the default RGB color * model, TYPE_INT_ARGB, and default sRGB color space. For images * with an {@code IndexColorModel}, the index with the nearest * color is chosen. * * <p> * * An {@code ArrayOutOfBoundsException} may be thrown * if the coordinates are not in bounds. * However, explicit bounds checking is not guaranteed. * * @param x the X coordinate of the pixel to set * @param y the Y coordinate of the pixel to set * @param rgb the RGB value * @see #getRGB(int, int) * @see #getRGB(int, int, int, int, int[], int, int) */ public void setRGB(int x, int y, int rgb) { raster.setDataElements(x, y, colorModel.getDataElements(rgb, null)); } /** * Sets an array of integer pixels in the default RGB color model * (TYPE_INT_ARGB) and default sRGB color space, * into a portion of the image data. Color conversion takes place * if the default model does not match the image * {@code ColorModel}. There are only 8-bits of precision for * each color component in the returned data when * using this method. With a specified coordinate (x, y) in the * this image, the ARGB pixel can be accessed in this way: * <pre> * pixel = rgbArray[offset + (y-startY)*scansize + (x-startX)]; * </pre> * WARNING: No dithering takes place. * * <p> * * An {@code ArrayOutOfBoundsException} may be thrown * if the region is not in bounds. * However, explicit bounds checking is not guaranteed. * * @param startX the starting X coordinate * @param startY the starting Y coordinate * @param w width of the region * @param h height of the region * @param rgbArray the rgb pixels * @param offset offset into the {@code rgbArray} * @param scansize scanline stride for the {@code rgbArray} * @see #getRGB(int, int) * @see #getRGB(int, int, int, int, int[], int, int) */ public void setRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize) { int yoff = offset; int off; Object pixel = null; for (int y = startY; y < startY + h; y++, yoff += scansize) { off = yoff; for (int x = startX; x < startX + w; x++) { pixel = colorModel.getDataElements(rgbArray[off++], pixel); raster.setDataElements(x, y, pixel); } } } /** * Returns the width of the {@code BufferedImage}. * @return the width of this {@code BufferedImage} */ public int getWidth() { return raster.getWidth(); } /** * Returns the height of the {@code BufferedImage}. * @return the height of this {@code BufferedImage} */ public int getHeight() { return raster.getHeight(); } /** * Returns the width of the {@code BufferedImage}. * @param observer ignored * @return the width of this {@code BufferedImage} */ public int getWidth(ImageObserver observer) { return raster.getWidth(); } /** * Returns the height of the {@code BufferedImage}. * @param observer ignored * @return the height of this {@code BufferedImage} */ public int getHeight(ImageObserver observer) { return raster.getHeight(); } /** * Returns the object that produces the pixels for the image. * @return the {@link ImageProducer} that is used to produce the * pixels for this image. * @see ImageProducer */ public ImageProducer getSource() { if (osis == null) { if (properties == null) { properties = new Hashtable<>(); } osis = new OffScreenImageSource(this, properties); } return osis; } /** * Returns a property of the image by name. Individual property names * are defined by the various image formats. If a property is not * defined for a particular image, this method returns the * {@code UndefinedProperty} field. If the properties * for this image are not yet known, then this method returns * {@code null} and the {@code ImageObserver} object is * notified later. The property name "comment" should be used to * store an optional comment that can be presented to the user as a * description of the image, its source, or its author. * @param name the property name * @param observer the {@code ImageObserver} that receives * notification regarding image information * @return an {@link Object} that is the property referred to by the * specified {@code name} or {@code null} if the * properties of this image are not yet known. * @throws NullPointerException if the property name is null. * @see ImageObserver * @see java.awt.Image#UndefinedProperty */ public Object getProperty(String name, ImageObserver observer) { return getProperty(name); } /** * Returns a property of the image by name. * @param name the property name * @return an {@code Object} that is the property referred to by * the specified {@code name}. * @throws NullPointerException if the property name is null. */ public Object getProperty(String name) { if (name == null) { throw new NullPointerException("null property name is not allowed"); } if (properties == null) { return java.awt.Image.UndefinedProperty; } Object o = properties.get(name); if (o == null) { o = java.awt.Image.UndefinedProperty; } return o; } /** * This method returns a {@link Graphics2D}, but is here * for backwards compatibility. {@link #createGraphics() createGraphics} is more * convenient, since it is declared to return a * {@code Graphics2D}. * @return a {@code Graphics2D}, which can be used to draw into * this image. */ public java.awt.Graphics getGraphics() { return createGraphics(); } /** * Creates a {@code Graphics2D}, which can be used to draw into * this {@code BufferedImage}. * @return a {@code Graphics2D}, used for drawing into this * image. */ public Graphics2D createGraphics() { GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); return env.createGraphics(this); } /** * Returns a subimage defined by a specified rectangular region. * The returned {@code BufferedImage} shares the same * data array as the original image. * @param x the X coordinate of the upper-left corner of the * specified rectangular region * @param y the Y coordinate of the upper-left corner of the * specified rectangular region * @param w the width of the specified rectangular region * @param h the height of the specified rectangular region * @return a {@code BufferedImage} that is the subimage of this * {@code BufferedImage}. * @exception RasterFormatException if the specified * area is not contained within this {@code BufferedImage}. */ public BufferedImage getSubimage(int x, int y, int w, int h) { return new BufferedImage(colorModel, raster.createWritableChild(x, y, w, h, 0, 0, null), colorModel.isAlphaPremultiplied(), properties); } /** * Returns whether or not the alpha has been premultiplied. It * returns {@code false} if there is no alpha. * @return {@code true} if the alpha has been premultiplied; * {@code false} otherwise. */ public boolean isAlphaPremultiplied() { return colorModel.isAlphaPremultiplied(); } /** * Forces the data to match the state specified in the * {@code isAlphaPremultiplied} variable. It may multiply or * divide the color raster data by alpha, or do nothing if the data is * in the correct state. * @param isAlphaPremultiplied {@code true} if the alpha has been * premultiplied; {@code false} otherwise. */ public void coerceData(boolean isAlphaPremultiplied) { if (colorModel.hasAlpha() && colorModel.isAlphaPremultiplied() != isAlphaPremultiplied) { // Make the color model do the conversion colorModel = colorModel.coerceData(raster, isAlphaPremultiplied); } } /** * Returns a {@code String} representation of this * {@code BufferedImage} object and its values. * @return a {@code String} representing this * {@code BufferedImage}. */ public String toString() { return "BufferedImage@" + Integer.toHexString(hashCode()) + ": type = " + imageType + " " + colorModel + " " + raster; } /** * Returns a {@link Vector} of {@link RenderedImage} objects that are * the immediate sources, not the sources of these immediate sources, * of image data for this {@code BufferedImage}. This * method returns {@code null} if the {@code BufferedImage} * has no information about its immediate sources. It returns an * empty {@code Vector} if the {@code BufferedImage} has no * immediate sources. * @return a {@code Vector} containing immediate sources of * this {@code BufferedImage} object's image date, or * {@code null} if this {@code BufferedImage} has * no information about its immediate sources, or an empty * {@code Vector} if this {@code BufferedImage} * has no immediate sources. */ public Vector<RenderedImage> getSources() { return null; } /** * Returns an array of names recognized by * {@link #getProperty(String) getProperty(String)} * or {@code null}, if no property names are recognized. * @return a {@code String} array containing all of the property * names that {@code getProperty(String)} recognizes; * or {@code null} if no property names are recognized. */ public String[] getPropertyNames() { if (properties == null || properties.isEmpty()) { return null; } final Set<String> keys = properties.keySet(); return keys.toArray(new String[keys.size()]); } /** * Returns the minimum x coordinate of this * {@code BufferedImage}. This is always zero. * @return the minimum x coordinate of this * {@code BufferedImage}. */ public int getMinX() { return raster.getMinX(); } /** * Returns the minimum y coordinate of this * {@code BufferedImage}. This is always zero. * @return the minimum y coordinate of this * {@code BufferedImage}. */ public int getMinY() { return raster.getMinY(); } /** * Returns the {@code SampleModel} associated with this * {@code BufferedImage}. * @return the {@code SampleModel} of this * {@code BufferedImage}. */ public SampleModel getSampleModel() { return raster.getSampleModel(); } /** * Returns the number of tiles in the x direction. * This is always one. * @return the number of tiles in the x direction. */ public int getNumXTiles() { return 1; } /** * Returns the number of tiles in the y direction. * This is always one. * @return the number of tiles in the y direction. */ public int getNumYTiles() { return 1; } /** * Returns the minimum tile index in the x direction. * This is always zero. * @return the minimum tile index in the x direction. */ public int getMinTileX() { return 0; } /** * Returns the minimum tile index in the y direction. * This is always zero. * @return the minimum tile index in the y direction. */ public int getMinTileY() { return 0; } /** * Returns the tile width in pixels. * @return the tile width in pixels. */ public int getTileWidth() { return raster.getWidth(); } /** * Returns the tile height in pixels. * @return the tile height in pixels. */ public int getTileHeight() { return raster.getHeight(); } /** * Returns the x offset of the tile grid relative to the origin, * For example, the x coordinate of the location of tile * (0, 0). This is always zero. * @return the x offset of the tile grid. */ public int getTileGridXOffset() { return raster.getSampleModelTranslateX(); } /** * Returns the y offset of the tile grid relative to the origin, * For example, the y coordinate of the location of tile * (0, 0). This is always zero. * @return the y offset of the tile grid. */ public int getTileGridYOffset() { return raster.getSampleModelTranslateY(); } /** * Returns tile ({@code tileX}, {@code tileY}). Note * that {@code tileX} and {@code tileY} are indices * into the tile array, not pixel locations. The {@code Raster} * that is returned is live, which means that it is updated if the * image is changed. * @param tileX the x index of the requested tile in the tile array * @param tileY the y index of the requested tile in the tile array * @return a {@code Raster} that is the tile defined by the * arguments {@code tileX} and {@code tileY}. * @exception ArrayIndexOutOfBoundsException if both * {@code tileX} and {@code tileY} are not * equal to 0 */ public Raster getTile(int tileX, int tileY) { if (tileX == 0 && tileY == 0) { return raster; } throw new ArrayIndexOutOfBoundsException("BufferedImages only have" + " one tile with index 0,0"); } /** * Returns the image as one large tile. The {@code Raster} * returned is a copy of the image data is not updated if the * image is changed. * @return a {@code Raster} that is a copy of the image data. * @see #setData(Raster) */ public Raster getData() { // REMIND : this allocates a whole new tile if raster is a // subtile. (It only copies in the requested area) // We should do something smarter. int width = raster.getWidth(); int height = raster.getHeight(); int startX = raster.getMinX(); int startY = raster.getMinY(); WritableRaster wr = Raster.createWritableRaster(raster.getSampleModel(), new Point(raster.getSampleModelTranslateX(), raster.getSampleModelTranslateY())); Object tdata = null; for (int i = startY; i < startY + height; i++) { tdata = raster.getDataElements(startX, i, width, 1, tdata); wr.setDataElements(startX, i, width, 1, tdata); } return wr; } /** * Computes and returns an arbitrary region of the * {@code BufferedImage}. The {@code Raster} returned is a * copy of the image data and is not updated if the image is * changed. * @param rect the region of the {@code BufferedImage} to be * returned. * @return a {@code Raster} that is a copy of the image data of * the specified region of the {@code BufferedImage} * @see #setData(Raster) */ public Raster getData(Rectangle rect) { SampleModel sm = raster.getSampleModel(); SampleModel nsm = sm.createCompatibleSampleModel(rect.width, rect.height); WritableRaster wr = Raster.createWritableRaster(nsm, rect.getLocation()); int width = rect.width; int height = rect.height; int startX = rect.x; int startY = rect.y; Object tdata = null; for (int i = startY; i < startY + height; i++) { tdata = raster.getDataElements(startX, i, width, 1, tdata); wr.setDataElements(startX, i, width, 1, tdata); } return wr; } /** * Computes an arbitrary rectangular region of the * {@code BufferedImage} and copies it into a specified * {@code WritableRaster}. The region to be computed is * determined from the bounds of the specified * {@code WritableRaster}. The specified * {@code WritableRaster} must have a * {@code SampleModel} that is compatible with this image. If * {@code outRaster} is {@code null}, * an appropriate {@code WritableRaster} is created. * @param outRaster a {@code WritableRaster} to hold the returned * part of the image, or {@code null} * @return a reference to the supplied or created * {@code WritableRaster}. */ public WritableRaster copyData(WritableRaster outRaster) { if (outRaster == null) { return (WritableRaster) getData(); } int width = outRaster.getWidth(); int height = outRaster.getHeight(); int startX = outRaster.getMinX(); int startY = outRaster.getMinY(); Object tdata = null; for (int i = startY; i < startY + height; i++) { tdata = raster.getDataElements(startX, i, width, 1, tdata); outRaster.setDataElements(startX, i, width, 1, tdata); } return outRaster; } /** * Sets a rectangular region of the image to the contents of the * specified {@code Raster r}, which is * assumed to be in the same coordinate space as the * {@code BufferedImage}. The operation is clipped to the bounds * of the {@code BufferedImage}. * @param r the specified {@code Raster} * @see #getData * @see #getData(Rectangle) */ public void setData(Raster r) { int width = r.getWidth(); int height = r.getHeight(); int startX = r.getMinX(); int startY = r.getMinY(); int[] tdata = null; // Clip to the current Raster Rectangle rclip = new Rectangle(startX, startY, width, height); Rectangle bclip = new Rectangle(0, 0, raster.width, raster.height); Rectangle intersect = rclip.intersection(bclip); if (intersect.isEmpty()) { return; } width = intersect.width; height = intersect.height; startX = intersect.x; startY = intersect.y; // remind use get/setDataElements for speed if Rasters are // compatible for (int i = startY; i < startY + height; i++) { tdata = r.getPixels(startX, i, width, 1, tdata); raster.setPixels(startX, i, width, 1, tdata); } } /** * Adds a tile observer. If the observer is already present, * it receives multiple notifications. * @param to the specified {@link TileObserver} */ public void addTileObserver(TileObserver 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 specified {@code TileObserver}. */ public void removeTileObserver(TileObserver to) { } /** * Returns whether or not a tile is currently checked out for writing. * @param tileX the x index of the tile. * @param tileY the y index of the tile. * @return {@code true} if the tile specified by the specified * indices is checked out for writing; {@code false} * otherwise. * @exception ArrayIndexOutOfBoundsException if both * {@code tileX} and {@code tileY} are not equal * to 0 */ public boolean isTileWritable(int tileX, int tileY) { if (tileX == 0 && tileY == 0) { return true; } throw new IllegalArgumentException("Only 1 tile in image"); } /** * Returns an array of {@link Point} objects indicating which tiles * are checked out for writing. Returns {@code null} if none are * checked out. * @return a {@code Point} array that indicates the tiles that * are checked out for writing, or {@code null} if no * tiles are checked out for writing. */ public Point[] getWritableTileIndices() { Point[] p = new Point[1]; p[0] = new Point(0, 0); return p; } /** * Returns whether or not any tile is checked out for writing. * Semantically equivalent to * <pre> * (getWritableTileIndices() != null). * </pre> * @return {@code true} if any tile is checked out for writing; * {@code false} otherwise. */ public boolean hasTileWriters() { return true; } /** * Checks out a tile for writing. All registered * {@code TileObservers} are notified when a tile goes from having * no writers to having one writer. * @param tileX the x index of the tile * @param tileY the y index of the tile * @return a {@code WritableRaster} that is the tile, indicated by * the specified indices, to be checked out for writing. */ public WritableRaster getWritableTile(int tileX, int tileY) { return raster; } /** * Relinquishes permission to write to a tile. If the caller * continues to write to the tile, the results are undefined. * Calls to this method should only appear in matching pairs * with calls to {@link #getWritableTile(int, int) getWritableTile(int, int)}. Any other leads * to undefined results. All registered {@code TileObservers} * are notified when a tile goes from having one writer to having no * writers. * @param tileX the x index of the tile * @param tileY the y index of the tile */ public void releaseWritableTile(int tileX, int tileY) { } /** * Returns the transparency. Returns either OPAQUE, BITMASK, * or TRANSLUCENT. * @return the transparency of this {@code BufferedImage}. * @see Transparency#OPAQUE * @see Transparency#BITMASK * @see Transparency#TRANSLUCENT * @since 1.5 */ public int getTransparency() { return colorModel.getTransparency(); } }