Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* $Id: GraphicsUtil.java 603243 2007-12-11 13:49:04Z jeremias $ */ import java.awt.Point; import java.awt.Rectangle; import java.awt.color.ColorSpace; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.ComponentSampleModel; import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.DataBufferInt; import java.awt.image.DataBufferShort; import java.awt.image.DataBufferUShort; import java.awt.image.DirectColorModel; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import java.awt.image.SinglePixelPackedSampleModel; import java.awt.image.WritableRaster; /** * Set of utility methods for Graphics. * These generally bypass broken methods in Java2D or provide tweaked * implementations. * * @author <a href="mailto:Thomas.DeWeeese@Kodak.com">Thomas DeWeese</a> * @version $Id: GraphicsUtil.java 603243 2007-12-11 13:49:04Z jeremias $ */ public class GraphicsUtil { public static AffineTransform IDENTITY = new AffineTransform(); /** * Standard prebuilt Linear_sRGB color model with no alpha */ public static final ColorModel Linear_sRGB = new DirectColorModel( ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB), 24, 0x00FF0000, 0x0000FF00, 0x000000FF, 0x0, false, DataBuffer.TYPE_INT); /** * Standard prebuilt Linear_sRGB color model with premultiplied alpha. */ public static final ColorModel Linear_sRGB_Pre = new DirectColorModel( ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB), 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000, true, DataBuffer.TYPE_INT); /** * Standard prebuilt Linear_sRGB color model with unpremultiplied alpha. */ public static final ColorModel Linear_sRGB_Unpre = new DirectColorModel( ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB), 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000, false, DataBuffer.TYPE_INT); /** * Standard prebuilt sRGB color model with no alpha. */ public static final ColorModel sRGB = new DirectColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), 24, 0x00FF0000, 0x0000FF00, 0x000000FF, 0x0, false, DataBuffer.TYPE_INT); /** * Standard prebuilt sRGB color model with premultiplied alpha. */ public static final ColorModel sRGB_Pre = new DirectColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000, true, DataBuffer.TYPE_INT); /** * Standard prebuilt sRGB color model with unpremultiplied alpha. */ public static final ColorModel sRGB_Unpre = new DirectColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000, false, DataBuffer.TYPE_INT); /** * Method that returns either Linear_sRGB_Pre or Linear_sRGB_UnPre * based on premult flag. * @param premult True if the ColorModel should have premultiplied alpha. * @return a ColorMdoel with Linear sRGB colorSpace and * the alpha channel set in accordance with * <tt>premult</tt> */ public static ColorModel makeLinear_sRGBCM(boolean premult) { return premult ? Linear_sRGB_Pre : Linear_sRGB_Unpre; } /** * Constructs a BufferedImage with a linear sRGB colorModel, and alpha. * @param width The desired width of the BufferedImage * @param height The desired height of the BufferedImage * @param premult The desired state of alpha premultiplied * @return The requested BufferedImage. */ public static BufferedImage makeLinearBufferedImage(int width, int height, boolean premult) { ColorModel cm = makeLinear_sRGBCM(premult); WritableRaster wr = cm.createCompatibleWritableRaster(width, height); return new BufferedImage(cm, wr, premult, null); } /** * Creates a new raster that has a <b>copy</b> of the data in * <tt>ras</tt>. This is highly optimized for speed. There is * no provision for changing any aspect of the SampleModel. * * This method should be used when you need to change the contents * of a Raster that you do not "own" (ie the result of a * <tt>getData</tt> call). * @param ras The Raster to copy. * @return A writable copy of <tt>ras</tt> */ public static WritableRaster copyRaster(Raster ras) { return copyRaster(ras, ras.getMinX(), ras.getMinY()); } /** * Creates a new raster that has a <b>copy</b> of the data in * <tt>ras</tt>. This is highly optimized for speed. There is * no provision for changing any aspect of the SampleModel. * However you can specify a new location for the returned raster. * * This method should be used when you need to change the contents * of a Raster that you do not "own" (ie the result of a * <tt>getData</tt> call). * * @param ras The Raster to copy. * * @param minX The x location for the upper left corner of the * returned WritableRaster. * * @param minY The y location for the upper left corner of the * returned WritableRaster. * * @return A writable copy of <tt>ras</tt> */ public static WritableRaster copyRaster(Raster ras, int minX, int minY) { WritableRaster ret = Raster.createWritableRaster(ras.getSampleModel(), new Point(0, 0)); ret = ret.createWritableChild(ras.getMinX() - ras.getSampleModelTranslateX(), ras.getMinY() - ras.getSampleModelTranslateY(), ras.getWidth(), ras.getHeight(), minX, minY, null); // Use System.arraycopy to copy the data between the two... DataBuffer srcDB = ras.getDataBuffer(); DataBuffer retDB = ret.getDataBuffer(); if (srcDB.getDataType() != retDB.getDataType()) { throw new IllegalArgumentException("New DataBuffer doesn't match original"); } int len = srcDB.getSize(); int banks = srcDB.getNumBanks(); int[] offsets = srcDB.getOffsets(); for (int b = 0; b < banks; b++) { switch (srcDB.getDataType()) { case DataBuffer.TYPE_BYTE: { DataBufferByte srcDBT = (DataBufferByte) srcDB; DataBufferByte retDBT = (DataBufferByte) retDB; System.arraycopy(srcDBT.getData(b), offsets[b], retDBT.getData(b), offsets[b], len); break; } case DataBuffer.TYPE_INT: { DataBufferInt srcDBT = (DataBufferInt) srcDB; DataBufferInt retDBT = (DataBufferInt) retDB; System.arraycopy(srcDBT.getData(b), offsets[b], retDBT.getData(b), offsets[b], len); break; } case DataBuffer.TYPE_SHORT: { DataBufferShort srcDBT = (DataBufferShort) srcDB; DataBufferShort retDBT = (DataBufferShort) retDB; System.arraycopy(srcDBT.getData(b), offsets[b], retDBT.getData(b), offsets[b], len); break; } case DataBuffer.TYPE_USHORT: { DataBufferUShort srcDBT = (DataBufferUShort) srcDB; DataBufferUShort retDBT = (DataBufferUShort) retDB; System.arraycopy(srcDBT.getData(b), offsets[b], retDBT.getData(b), offsets[b], len); break; } } } return ret; } /** * Coerces <tt>ras</tt> to be writable. The returned Raster continues to * reference the DataBuffer from ras, so modifications to the returned * WritableRaster will be seen in ras.<p> * * This method should only be used if you need a WritableRaster due to * an interface (such as to construct a BufferedImage), but have no * intention of modifying the contents of the returned Raster. If * you have any doubt about other users of the data in <tt>ras</tt>, * use copyRaster (above). * @param ras The raster to make writable. * @return A Writable version of ras (shares DataBuffer with * <tt>ras</tt>). */ public static WritableRaster makeRasterWritable(Raster ras) { return makeRasterWritable(ras, ras.getMinX(), ras.getMinY()); } /** * Coerces <tt>ras</tt> to be writable. The returned Raster continues to * reference the DataBuffer from ras, so modifications to the returned * WritableRaster will be seen in ras.<p> * * You can specify a new location for the returned WritableRaster, this * is especially useful for constructing BufferedImages which require * the Raster to be at (0,0). * * This method should only be used if you need a WritableRaster due to * an interface (such as to construct a BufferedImage), but have no * intention of modifying the contents of the returned Raster. If * you have any doubt about other users of the data in <tt>ras</tt>, * use copyRaster (above). * * @param ras The raster to make writable. * * @param minX The x location for the upper left corner of the * returned WritableRaster. * * @param minY The y location for the upper left corner of the * returned WritableRaster. * * @return A Writable version of <tT>ras</tt> with it's upper left * hand coordinate set to minX, minY (shares it's DataBuffer * with <tt>ras</tt>). */ public static WritableRaster makeRasterWritable(Raster ras, int minX, int minY) { WritableRaster ret = Raster.createWritableRaster(ras.getSampleModel(), ras.getDataBuffer(), new Point(0, 0)); ret = ret.createWritableChild(ras.getMinX() - ras.getSampleModelTranslateX(), ras.getMinY() - ras.getSampleModelTranslateY(), ras.getWidth(), ras.getHeight(), minX, minY, null); return ret; } /** * Create a new ColorModel with it's alpha premultiplied state matching * newAlphaPreMult. * @param cm The ColorModel to change the alpha premult state of. * @param newAlphaPreMult The new state of alpha premult. * @return A new colorModel that has isAlphaPremultiplied() * equal to newAlphaPreMult. */ public static ColorModel coerceColorModel(ColorModel cm, boolean newAlphaPreMult) { if (cm.isAlphaPremultiplied() == newAlphaPreMult) return cm; // Easiest way to build proper colormodel for new Alpha state... // Eventually this should switch on known ColorModel types and // only fall back on this hack when the CM type is unknown. WritableRaster wr = cm.createCompatibleWritableRaster(1, 1); return cm.coerceData(wr, newAlphaPreMult); } /** * Coerces data within a bufferedImage to match newAlphaPreMult, * Note that this can not change the colormodel of bi so you * * @param wr The raster to change the state of. * @param cm The colormodel currently associated with data in wr. * @param newAlphaPreMult The desired state of alpha Premult for raster. * @return A new colormodel that matches newAlphaPreMult. */ public static ColorModel coerceData(WritableRaster wr, ColorModel cm, boolean newAlphaPreMult) { // System.out.println("CoerceData: " + cm.isAlphaPremultiplied() + // " Out: " + newAlphaPreMult); if (!cm.hasAlpha()) // Nothing to do no alpha channel return cm; if (cm.isAlphaPremultiplied() == newAlphaPreMult) // nothing to do alpha state matches... return cm; // System.out.println("CoerceData: " + wr.getSampleModel()); if (newAlphaPreMult) { multiplyAlpha(wr); } else { divideAlpha(wr); } return coerceColorModel(cm, newAlphaPreMult); } public static void multiplyAlpha(WritableRaster wr) { if (is_BYTE_COMP_Data(wr.getSampleModel())) mult_BYTE_COMP_Data(wr); else if (is_INT_PACK_Data(wr.getSampleModel(), true)) mult_INT_PACK_Data(wr); else { int[] pixel = null; int bands = wr.getNumBands(); float norm = 1f / 255f; int x0, x1, y0, y1, a, b; float alpha; x0 = wr.getMinX(); x1 = x0 + wr.getWidth(); y0 = wr.getMinY(); y1 = y0 + wr.getHeight(); for (int y = y0; y < y1; y++) for (int x = x0; x < x1; x++) { pixel = wr.getPixel(x, y, pixel); a = pixel[bands - 1]; if ((a >= 0) && (a < 255)) { alpha = a * norm; for (b = 0; b < bands - 1; b++) pixel[b] = (int) (pixel[b] * alpha + 0.5f); wr.setPixel(x, y, pixel); } } } } public static void divideAlpha(WritableRaster wr) { if (is_BYTE_COMP_Data(wr.getSampleModel())) divide_BYTE_COMP_Data(wr); else if (is_INT_PACK_Data(wr.getSampleModel(), true)) divide_INT_PACK_Data(wr); else { int x0, x1, y0, y1, a, b; float ialpha; int bands = wr.getNumBands(); int[] pixel = null; x0 = wr.getMinX(); x1 = x0 + wr.getWidth(); y0 = wr.getMinY(); y1 = y0 + wr.getHeight(); for (int y = y0; y < y1; y++) for (int x = x0; x < x1; x++) { pixel = wr.getPixel(x, y, pixel); a = pixel[bands - 1]; if ((a > 0) && (a < 255)) { ialpha = 255 / (float) a; for (b = 0; b < bands - 1; b++) pixel[b] = (int) (pixel[b] * ialpha + 0.5f); wr.setPixel(x, y, pixel); } } } } public static void copyBand(Raster src, int srcBand, WritableRaster dst, int dstBand) { Rectangle sR = src.getBounds(); Rectangle dR = dst.getBounds(); Rectangle cpR = sR.intersection(dR); copyBand(src, cpR, srcBand, dst, cpR, dstBand); } public static void copyBand(Raster src, Rectangle sR, int sBand, WritableRaster dst, Rectangle dR, int dBand) { int dy = dR.y - sR.y; int dx = dR.x - sR.x; sR = sR.intersection(src.getBounds()); dR = dR.intersection(dst.getBounds()); int width, height; if (dR.width < sR.width) width = dR.width; else width = sR.width; if (dR.height < sR.height) height = dR.height; else height = sR.height; int x = sR.x + dx; int[] samples = null; for (int y = sR.y; y < sR.y + height; y++) { samples = src.getSamples(sR.x, y, width, 1, sBand, samples); dst.setSamples(x, y + dy, width, 1, dBand, samples); } } public static boolean is_INT_PACK_Data(SampleModel sm, boolean requireAlpha) { // Check ColorModel is of type DirectColorModel if (!(sm instanceof SinglePixelPackedSampleModel)) return false; // Check transfer type if (sm.getDataType() != DataBuffer.TYPE_INT) return false; SinglePixelPackedSampleModel sppsm; sppsm = (SinglePixelPackedSampleModel) sm; int[] masks = sppsm.getBitMasks(); if (masks.length == 3) { if (requireAlpha) return false; } else if (masks.length != 4) return false; if (masks[0] != 0x00ff0000) return false; if (masks[1] != 0x0000ff00) return false; if (masks[2] != 0x000000ff) return false; if ((masks.length == 4) && (masks[3] != 0xff000000)) return false; return true; } public static boolean is_BYTE_COMP_Data(SampleModel sm) { // Check ColorModel is of type DirectColorModel if (!(sm instanceof ComponentSampleModel)) return false; // Check transfer type if (sm.getDataType() != DataBuffer.TYPE_BYTE) return false; return true; } protected static void divide_INT_PACK_Data(WritableRaster wr) { // System.out.println("Divide Int"); SinglePixelPackedSampleModel sppsm; sppsm = (SinglePixelPackedSampleModel) wr.getSampleModel(); final int width = wr.getWidth(); final int scanStride = sppsm.getScanlineStride(); DataBufferInt db = (DataBufferInt) wr.getDataBuffer(); final int base = (db.getOffset() + sppsm.getOffset(wr.getMinX() - wr.getSampleModelTranslateX(), wr.getMinY() - wr.getSampleModelTranslateY())); // Access the pixel data array final int[] pixels = db.getBankData()[0]; for (int y = 0; y < wr.getHeight(); y++) { int sp = base + y * scanStride; final int end = sp + width; while (sp < end) { int pixel = pixels[sp]; int a = pixel >>> 24; if (a <= 0) { pixels[sp] = 0x00FFFFFF; } else if (a < 255) { int aFP = (0x00FF0000 / a); pixels[sp] = ((a << 24) | (((((pixel & 0xFF0000) >> 16) * aFP) & 0xFF0000)) | (((((pixel & 0x00FF00) >> 8) * aFP) & 0xFF0000) >> 8) | (((((pixel & 0x0000FF)) * aFP) & 0xFF0000) >> 16)); } sp++; } } } protected static void mult_INT_PACK_Data(WritableRaster wr) { // System.out.println("Multiply Int: " + wr); SinglePixelPackedSampleModel sppsm; sppsm = (SinglePixelPackedSampleModel) wr.getSampleModel(); final int width = wr.getWidth(); final int scanStride = sppsm.getScanlineStride(); DataBufferInt db = (DataBufferInt) wr.getDataBuffer(); final int base = (db.getOffset() + sppsm.getOffset(wr.getMinX() - wr.getSampleModelTranslateX(), wr.getMinY() - wr.getSampleModelTranslateY())); // Access the pixel data array final int[] pixels = db.getBankData()[0]; for (int y = 0; y < wr.getHeight(); y++) { int sp = base + y * scanStride; final int end = sp + width; while (sp < end) { int pixel = pixels[sp]; int a = pixel >>> 24; if ((a >= 0) && (a < 255)) { // this does NOT include a == 255 (0xff) ! pixels[sp] = ((a << 24) | ((((pixel & 0xFF0000) * a) >> 8) & 0xFF0000) | ((((pixel & 0x00FF00) * a) >> 8) & 0x00FF00) | ((((pixel & 0x0000FF) * a) >> 8) & 0x0000FF)); } sp++; } } } protected static void divide_BYTE_COMP_Data(WritableRaster wr) { // System.out.println("Multiply Int: " + wr); ComponentSampleModel csm; csm = (ComponentSampleModel) wr.getSampleModel(); final int width = wr.getWidth(); final int scanStride = csm.getScanlineStride(); final int pixStride = csm.getPixelStride(); final int[] bandOff = csm.getBandOffsets(); DataBufferByte db = (DataBufferByte) wr.getDataBuffer(); final int base = (db.getOffset() + csm.getOffset(wr.getMinX() - wr.getSampleModelTranslateX(), wr.getMinY() - wr.getSampleModelTranslateY())); int aOff = bandOff[bandOff.length - 1]; int bands = bandOff.length - 1; // Access the pixel data array final byte[] pixels = db.getBankData()[0]; for (int y = 0; y < wr.getHeight(); y++) { int sp = base + y * scanStride; final int end = sp + width * pixStride; while (sp < end) { int a = pixels[sp + aOff] & 0xFF; if (a == 0) { for (int b = 0; b < bands; b++) pixels[sp + bandOff[b]] = (byte) 0xFF; } else if (a < 255) { // this does NOT include a == 255 (0xff) ! int aFP = (0x00FF0000 / a); for (int b = 0; b < bands; b++) { int i = sp + bandOff[b]; pixels[i] = (byte) (((pixels[i] & 0xFF) * aFP) >>> 16); } } sp += pixStride; } } } protected static void mult_BYTE_COMP_Data(WritableRaster wr) { // System.out.println("Multiply Int: " + wr); ComponentSampleModel csm; csm = (ComponentSampleModel) wr.getSampleModel(); final int width = wr.getWidth(); final int scanStride = csm.getScanlineStride(); final int pixStride = csm.getPixelStride(); final int[] bandOff = csm.getBandOffsets(); DataBufferByte db = (DataBufferByte) wr.getDataBuffer(); final int base = (db.getOffset() + csm.getOffset(wr.getMinX() - wr.getSampleModelTranslateX(), wr.getMinY() - wr.getSampleModelTranslateY())); int aOff = bandOff[bandOff.length - 1]; int bands = bandOff.length - 1; // Access the pixel data array final byte[] pixels = db.getBankData()[0]; for (int y = 0; y < wr.getHeight(); y++) { int sp = base + y * scanStride; final int end = sp + width * pixStride; while (sp < end) { int a = pixels[sp + aOff] & 0xFF; if (a != 0xFF) for (int b = 0; b < bands; b++) { int i = sp + bandOff[b]; pixels[i] = (byte) (((pixels[i] & 0xFF) * a) >> 8); } sp += pixStride; } } } /* This is skanky debugging code that might be useful in the future: if (count == 33) { String label = "sub [" + x + ", " + y + "]: "; org.ImageDisplay.showImage (label, subBI); org.ImageDisplay.printImage (label, subBI, new Rectangle(75-iR.x, 90-iR.y, 32, 32)); } // if ((count++ % 50) == 10) // org.ImageDisplay.showImage("foo: ", subBI); Graphics2D realG2D = g2d; while (realG2D instanceof sun.java2d.ProxyGraphics2D) { realG2D = ((sun.java2d.ProxyGraphics2D)realG2D).getDelegate(); } if (realG2D instanceof sun.awt.image.BufferedImageGraphics2D) { count++; if (count == 34) { RenderedImage ri; ri = ((sun.awt.image.BufferedImageGraphics2D)realG2D).bufImg; // g2d.setComposite(SVGComposite.OVER); // org.ImageDisplay.showImage("Bar: " + count, cr); org.ImageDisplay.printImage("Bar: " + count, cr, new Rectangle(75, 90, 32, 32)); org.ImageDisplay.showImage ("Foo: " + count, ri); org.ImageDisplay.printImage("Foo: " + count, ri, new Rectangle(75, 90, 32, 32)); System.out.println("BI: " + ri); System.out.println("BISM: " + ri.getSampleModel()); System.out.println("BICM: " + ri.getColorModel()); System.out.println("BICM class: " + ri.getColorModel().getClass()); System.out.println("BICS: " + ri.getColorModel().getColorSpace()); System.out.println ("sRGB CS: " + ColorSpace.getInstance(ColorSpace.CS_sRGB)); System.out.println("G2D info"); System.out.println("\tComposite: " + g2d.getComposite()); System.out.println("\tTransform" + g2d.getTransform()); java.awt.RenderingHints rh = g2d.getRenderingHints(); java.util.Set keys = rh.keySet(); java.util.Iterator iter = keys.iterator(); while (iter.hasNext()) { Object o = iter.next(); System.out.println("\t" + o.toString() + " -> " + rh.get(o).toString()); } ri = cr; System.out.println("RI: " + ri); System.out.println("RISM: " + ri.getSampleModel()); System.out.println("RICM: " + ri.getColorModel()); System.out.println("RICM class: " + ri.getColorModel().getClass()); System.out.println("RICS: " + ri.getColorModel().getColorSpace()); } } */ /** * Extracts an alpha raster from a RenderedImage. The method tries to avoid copying data * unnecessarily by checking if the RenderedImage is a BufferedImage which offers suitable * direct methods. * @param image the image * @return the alpha raster */ public static Raster getAlphaRaster(RenderedImage image) { ColorModel cm = image.getColorModel(); if (!cm.hasAlpha() || cm.getTransparency() != ColorModel.TRANSLUCENT) { throw new IllegalStateException("Image doesn't have an alpha channel"); } Raster alpha; if (image instanceof BufferedImage) { //Optimization possible with BufferedImage (No copying) alpha = ((BufferedImage) image).getAlphaRaster(); } else { WritableRaster wraster = GraphicsUtil.makeRasterWritable(image.getData()); alpha = image.getColorModel().getAlphaRaster(wraster); } return alpha; } }