/* * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package sun.java2d.windows; import java.awt.Color; import java.awt.Transparency; import java.awt.Rectangle; import java.awt.image.ColorModel; import java.awt.image.IndexColorModel; import java.awt.image.DirectColorModel; import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; import java.awt.image.DataBufferInt; import sun.awt.Win32GraphicsConfig; import sun.awt.Win32GraphicsDevice; import sun.awt.image.BufImgSurfaceData; import sun.awt.image.SunWritableRaster; import sun.java2d.SurfaceData; import sun.java2d.SurfaceDataProxy; import sun.java2d.SunGraphics2D; import sun.java2d.StateTracker; import sun.java2d.InvalidPipeException; import sun.java2d.loops.CompositeType; /** * The proxy class contains the logic for when to replace a * SurfaceData with a cached X11 Pixmap and the code to create * the accelerated surfaces. */ public abstract class Win32SurfaceDataProxy extends SurfaceDataProxy { /** * Represents the maximum size (width * height) of an image that we should * scan for an unused color. Any image larger than this would probably * require too much computation time. */ private static final int MAX_SIZE = 65536; public static SurfaceDataProxy createProxy(SurfaceData srcData, Win32GraphicsConfig dstConfig) { Win32GraphicsDevice wgd = (Win32GraphicsDevice) dstConfig.getDevice(); if (!wgd.isDDEnabledOnDevice() || srcData instanceof Win32SurfaceData || srcData instanceof Win32OffScreenSurfaceData) { // If they are not on the same screen then we could cache the // blit by returning an instance of Opaque below, but this // only happens for VolatileImage blits to the wrong screen // which we make no promises on so we just punt to UNCACHED... return UNCACHED; } ColorModel srcCM = srcData.getColorModel(); int srcTransparency = srcCM.getTransparency(); if (srcTransparency == Transparency.OPAQUE) { return new Opaque(dstConfig); } else if (srcTransparency == Transparency.BITMASK) { if (Bitmask.isCompatible(srcCM, srcData)) { return new Bitmask(dstConfig); } } return UNCACHED; } int srcTransparency; Win32GraphicsConfig wgc; public Win32SurfaceDataProxy(Win32GraphicsConfig wgc, int srcTransparency) { this.wgc = wgc; this.srcTransparency = srcTransparency; activateDisplayListener(); } @Override public SurfaceData validateSurfaceData(SurfaceData srcData, SurfaceData cachedData, int w, int h) { if (cachedData == null || !cachedData.isValid() || cachedData.isSurfaceLost()) { // use the device's color model for ddraw surfaces ColorModel dstScreenCM = wgc.getDeviceColorModel(); try { cachedData = Win32OffScreenSurfaceData.createData(w, h, dstScreenCM, wgc, null, srcTransparency); } catch (InvalidPipeException e) { Win32GraphicsDevice wgd = (Win32GraphicsDevice) wgc.getDevice(); if (!wgd.isDDEnabledOnDevice()) { invalidate(); flush(); return null; } } } return cachedData; } /** * Proxy for opaque source images. */ public static class Opaque extends Win32SurfaceDataProxy { static int TXMAX = (WindowsFlags.isDDScaleEnabled() ? SunGraphics2D.TRANSFORM_TRANSLATESCALE : SunGraphics2D.TRANSFORM_ANY_TRANSLATE); public Opaque(Win32GraphicsConfig wgc) { super(wgc, Transparency.OPAQUE); } @Override public boolean isSupportedOperation(SurfaceData srcData, int txtype, CompositeType comp, Color bgColor) { // we save a read from video memory for compositing // operations by copying from the buffered image sd return (txtype <= TXMAX && (CompositeType.SrcOverNoEa.equals(comp) || CompositeType.SrcNoEa.equals(comp))); } } /** * Proxy for bitmask transparent source images. * This proxy can accelerate unscaled SrcOver copies with no bgColor. * * Note that this proxy plays some games with returning the srcData * from the validate method. It needs to do this since the conditions * for caching an accelerated copy depend on many factors that can * change over time, including: * * - the depth of the display * - the availability of a transparent pixel */ public static class Bitmask extends Win32SurfaceDataProxy { /** * Tests a source image ColorModel and SurfaceData to * see if they are of an appropriate size and type to * perform our transparent pixel searches. * * Note that some dynamic factors may occur which prevent * us from finding or using a transparent pixel. These * are detailed above in the class comments. We do not * test those conditions here, but rely on the Bitmask * proxy to verify those conditions on the fly. */ public static boolean isCompatible(ColorModel srcCM, SurfaceData srcData) { if (srcCM instanceof IndexColorModel) { return true; } else if (srcCM instanceof DirectColorModel) { return isCompatibleDCM((DirectColorModel) srcCM, srcData); } return false; } /** * Tests a given DirectColorModel to make sure it is * compatible with the assumptions we make when scanning * a DCM image for a transparent pixel. */ public static boolean isCompatibleDCM(DirectColorModel dcm, SurfaceData srcData) { // The BISD restriction is because we need to // examine the pixels to find a tranparent color if (!(srcData instanceof BufImgSurfaceData)) { return false; } // The size restriction prevents us from wasting too // much time scanning large images for unused pixel values. Rectangle bounds = srcData.getBounds(); // Using division instead of multiplication avoids overflow if (bounds.width <= 0 || MAX_SIZE / bounds.width < bounds.height) { return false; } // Below we use the pixels from the data buffer to map // directly to pixel values using the dstData.pixelFor() // method so the pixel format must be compatible with // ARGB or we could end up with bad results. We assume // here that the destination is opaque and so only the // red, green, and blue masks matter. // These new checks for RGB masks are more correct, // but potentially reject the acceleration of some images // that we used to allow just because we cannot prove // that they will work OK. If we ever had an INT_BGR // image for instance, would that have really failed here? // 565 and 555 screens will both keep equal numbers of // bits of red and blue, but will differ in the amount of // green they keep so INT_BGR might be safe, but if anyone // ever created an INT_RBG image then 555 and 565 might // differ in whether they thought a transparent pixel // was available. Also, are there any other strange // screen formats where bizarre orderings of the RGB // would cause the tests below to make mistakes? return ((dcm.getPixelSize() == 25) && (dcm.getTransferType() == DataBuffer.TYPE_INT) && (dcm.getRedMask() == 0x00ff0000) && (dcm.getGreenMask() == 0x0000ff00) && (dcm.getBlueMask() == 0x000000ff)); } int transPixel; Color transColor; // The real accelerated surface - only used when we can find // a transparent color. SurfaceData accelData; public Bitmask(Win32GraphicsConfig wgc) { super(wgc, Transparency.BITMASK); } @Override public boolean isSupportedOperation(SurfaceData srcData, int txtype, CompositeType comp, Color bgColor) { // We have accelerated loops only for blits with SrcOverNoEa // (no blit bg loops or blit loops with SrcNoEa) return (CompositeType.SrcOverNoEa.equals(comp) && bgColor == null && txtype < SunGraphics2D.TRANSFORM_TRANSLATESCALE); } /** * Note that every time we update the surface we may or may * not find a transparent pixel depending on what was modified * in the source image since the last time we looked. * Our validation method saves the accelerated surface aside * in a different field so we can switch back and forth between * the accelerated version and null depending on whether we * find a transparent pixel. * Note that we also override getRetryTracker() and return a * tracker that tracks the source pixels so that we do not * try to revalidate until there are new pixels to be scanned. */ @Override public SurfaceData validateSurfaceData(SurfaceData srcData, SurfaceData cachedData, int w, int h) { // Evaluate the dest screen pixel size every time ColorModel dstScreenCM = wgc.getDeviceColorModel(); if (dstScreenCM.getPixelSize() <= 8) { return null; } accelData = super.validateSurfaceData(srcData, accelData, w, h); return (accelData != null && findTransparentPixel(srcData, accelData)) ? accelData : null; } @Override public StateTracker getRetryTracker(SurfaceData srcData) { // If we failed to validate, it is permanent until the // next change to srcData... return srcData.getStateTracker(); } @Override public void updateSurfaceData(SurfaceData srcData, SurfaceData dstData, int w, int h) { updateSurfaceDataBg(srcData, dstData, w, h, transColor); } /** * Invoked when the cached surface should be dropped. * Overrides the base class implementation so we can invalidate * the accelData field instead of the cachedSD field. */ @Override public synchronized void flush() { SurfaceData accelData = this.accelData; if (accelData != null) { this.accelData = null; accelData.flush(); } super.flush(); } /** * The following constants determine the size of the histograms * used when searching for an unused color */ private static final int ICM_HISTOGRAM_SIZE = 256; private static final int ICM_HISTOGRAM_MASK = ICM_HISTOGRAM_SIZE - 1; private static final int DCM_HISTOGRAM_SIZE = 1024; private static final int DCM_HISTOGRAM_MASK = DCM_HISTOGRAM_SIZE - 1; /** * Attempts to find an unused pixel value in the image and if * successful, sets up the DirectDraw surface so that it uses * this value as its color key. */ public boolean findTransparentPixel(SurfaceData srcData, SurfaceData accelData) { ColorModel srcCM = srcData.getColorModel(); boolean success = false; if (srcCM instanceof IndexColorModel) { success = findUnusedPixelICM((IndexColorModel) srcCM, accelData); } else if (srcCM instanceof DirectColorModel) { success = findUnusedPixelDCM((BufImgSurfaceData) srcData, accelData); } if (success) { int rgb = accelData.rgbFor(transPixel); transColor = new Color(rgb); Win32OffScreenSurfaceData wossd = (Win32OffScreenSurfaceData) accelData; wossd.setTransparentPixel(transPixel); } else { transColor = null; } return success; } /** * Attempts to find an unused pixel value in the color map of an * IndexColorModel. If successful, it returns that value (in the * ColorModel of the destination surface) or null otherwise. */ private boolean findUnusedPixelICM(IndexColorModel icm, SurfaceData accelData) { int mapsize = icm.getMapSize(); int[] histogram = new int[ICM_HISTOGRAM_SIZE]; int[] cmap = new int[mapsize]; icm.getRGBs(cmap); // load up the histogram for (int i = 0; i < mapsize; i++) { int pixel = accelData.pixelFor(cmap[i]); histogram[pixel & ICM_HISTOGRAM_MASK]++; } // find an empty histo-bucket for (int j = 0; j < histogram.length; j++) { if (histogram[j] == 0) { transPixel = j; return true; } } return false; } /** * Attempts to find an unused pixel value in an image with a * 25-bit DirectColorModel and a DataBuffer of TYPE_INT. * If successful, it returns that value (in the ColorModel * of the destination surface) or null otherwise. */ private boolean findUnusedPixelDCM(BufImgSurfaceData bisd, SurfaceData accelData) { BufferedImage bimg = (BufferedImage) bisd.getDestination(); DataBufferInt db = (DataBufferInt) bimg.getRaster().getDataBuffer(); int[] pixels = SunWritableRaster.stealData(db, 0); int[] histogram = new int[DCM_HISTOGRAM_SIZE]; // load up the histogram // REMIND: we could possibly make this faster by keeping track // of the unique colors found, and only doing a pixelFor() // when we come across a new unique color // REMIND: We are assuming pixels are in ARGB format. Is that // a safe assumption here? for (int i = 0; i < pixels.length; i++) { int pixel = accelData.pixelFor(pixels[i]); histogram[pixel & DCM_HISTOGRAM_MASK]++; } // find an empty histo-bucket for (int j = 0; j < histogram.length; j++) { if (histogram[j] == 0) { transPixel = j; return true; } } return false; } } }