blob: d631584cdcc657690873efbb08bb95408f6c7fcb [file] [log] [blame]
/*
* 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;
}
}
}