/* * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistribution of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistribution in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Sun Microsystems, Inc. or the names of * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed or intended for use * in the design, construction, operation or maintenance of any nuclear * facility. */ package com.sun.opengl.util.texture.awt; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Transparency; import java.awt.color.*; import java.awt.image.*; import java.nio.*; import javax.media.opengl.*; import com.sun.opengl.util.texture.*; public class AWTTextureData extends TextureData { // Mechanism for lazily converting input BufferedImages with custom // ColorModels to standard ones for uploading to OpenGL, as well as // backing off from the optimizations of hoping that either // GL_EXT_abgr or OpenGL 1.2 are present private BufferedImage imageForLazyCustomConversion; private boolean expectingEXTABGR; private boolean expectingGL12; private static final ColorModel rgbaColorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8, 8, 8, 8}, true, true, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE); private static final ColorModel rgbColorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8, 8, 8, 0}, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); /** * Constructs a new TextureData object with the specified parameters * and data contained in the given BufferedImage. The resulting * TextureData "wraps" the contents of the BufferedImage, so if a * modification is made to the BufferedImage between the time the * TextureData is constructed and when a Texture is made from the * TextureData, that modification will be visible in the resulting * Texture. * * @param internalFormat the OpenGL internal format for the * resulting texture; may be 0, in which case * it is inferred from the image's type * @param pixelFormat the OpenGL internal format for the * resulting texture; may be 0, in which case * it is inferred from the image's type (note: * this argument is currently always ignored) * @param mipmap indicates whether mipmaps should be * autogenerated (using GLU) for the resulting * texture * @param image the image containing the texture data */ public AWTTextureData(int internalFormat, int pixelFormat, boolean mipmap, BufferedImage image) { super(); if (internalFormat == 0) { this.internalFormat = image.getColorModel().hasAlpha() ? GL.GL_RGBA : GL.GL_RGB; } else { this.internalFormat = internalFormat; } createFromImage(image); this.mipmap = mipmap; if (buffer != null) { estimatedMemorySize = estimatedMemorySize(buffer); } else { // In the lazy custom conversion case we don't yet have a buffer if (imageForLazyCustomConversion != null) { estimatedMemorySize = estimatedMemorySize(wrapImageDataBuffer(imageForLazyCustomConversion)); } } } /** Returns the intended OpenGL pixel format of the texture data. */ public int getPixelFormat() { if (imageForLazyCustomConversion != null) { if (!((expectingEXTABGR && haveEXTABGR) || (expectingGL12 && haveGL12))) { revertPixelFormatAndType(); } } return pixelFormat; } /** Returns the intended OpenGL pixel type of the texture data. */ public int getPixelType() { if (imageForLazyCustomConversion != null) { if (!((expectingEXTABGR && haveEXTABGR) || (expectingGL12 && haveGL12))) { revertPixelFormatAndType(); } } return pixelType; } /** Returns the texture data, or null if it is specified as a set of mipmaps. */ public Buffer getBuffer() { if (imageForLazyCustomConversion != null) { if (!((expectingEXTABGR && haveEXTABGR) || (expectingGL12 && haveGL12))) { revertPixelFormatAndType(); // Must present the illusion to the end user that we are simply // wrapping the input BufferedImage createFromCustom(imageForLazyCustomConversion); } } return buffer; } private void createFromImage(BufferedImage image) { pixelType = 0; // Determine from image mustFlipVertically = true; width = image.getWidth(); height = image.getHeight(); int scanlineStride; SampleModel sm = image.getRaster().getSampleModel(); if (sm instanceof SinglePixelPackedSampleModel) { scanlineStride = ((SinglePixelPackedSampleModel)sm).getScanlineStride(); } else if (sm instanceof MultiPixelPackedSampleModel) { scanlineStride = ((MultiPixelPackedSampleModel)sm).getScanlineStride(); } else if (sm instanceof ComponentSampleModel) { scanlineStride = ((ComponentSampleModel)sm).getScanlineStride(); } else { // This will only happen for TYPE_CUSTOM anyway setupLazyCustomConversion(image); return; } if (GLProfile.isGL2()) { switch (image.getType()) { case BufferedImage.TYPE_INT_RGB: pixelFormat = GL2.GL_BGRA; pixelType = GL2.GL_UNSIGNED_INT_8_8_8_8_REV; rowLength = scanlineStride; alignment = 4; expectingGL12 = true; setupLazyCustomConversion(image); break; case BufferedImage.TYPE_INT_ARGB_PRE: pixelFormat = GL2.GL_BGRA; pixelType = GL2.GL_UNSIGNED_INT_8_8_8_8_REV; rowLength = scanlineStride; alignment = 4; expectingGL12 = true; setupLazyCustomConversion(image); break; case BufferedImage.TYPE_INT_BGR: pixelFormat = GL.GL_RGBA; pixelType = GL2.GL_UNSIGNED_INT_8_8_8_8_REV; rowLength = scanlineStride; alignment = 4; expectingGL12 = true; setupLazyCustomConversion(image); break; case BufferedImage.TYPE_3BYTE_BGR: { // we can pass the image data directly to OpenGL only if // we have an integral number of pixels in each scanline if ((scanlineStride % 3) == 0) { pixelFormat = GL2.GL_BGR; pixelType = GL.GL_UNSIGNED_BYTE; rowLength = scanlineStride / 3; alignment = 1; } else { setupLazyCustomConversion(image); return; } } break; case BufferedImage.TYPE_4BYTE_ABGR_PRE: { // we can pass the image data directly to OpenGL only if // we have an integral number of pixels in each scanline // and only if the GL_EXT_abgr extension is present // NOTE: disabling this code path for now as it appears it's // buggy at least on some NVidia drivers and doesn't perform // the necessary byte swapping (FIXME: needs more // investigation) if ((scanlineStride % 4) == 0 && false) { pixelFormat = GL2.GL_ABGR_EXT; pixelType = GL.GL_UNSIGNED_BYTE; rowLength = scanlineStride / 4; alignment = 4; // Store a reference to the original image for later in // case it turns out that we don't have GL_EXT_abgr at the // time we're going to do the texture upload to OpenGL setupLazyCustomConversion(image); expectingEXTABGR = true; break; } else { setupLazyCustomConversion(image); return; } } case BufferedImage.TYPE_USHORT_565_RGB: pixelFormat = GL.GL_RGB; pixelType = GL.GL_UNSIGNED_SHORT_5_6_5; rowLength = scanlineStride; alignment = 2; expectingGL12 = true; setupLazyCustomConversion(image); break; case BufferedImage.TYPE_USHORT_555_RGB: pixelFormat = GL2.GL_BGRA; pixelType = GL2.GL_UNSIGNED_SHORT_1_5_5_5_REV; rowLength = scanlineStride; alignment = 2; expectingGL12 = true; setupLazyCustomConversion(image); break; case BufferedImage.TYPE_BYTE_GRAY: pixelFormat = GL.GL_LUMINANCE; pixelType = GL.GL_UNSIGNED_BYTE; rowLength = scanlineStride; alignment = 1; break; case BufferedImage.TYPE_USHORT_GRAY: pixelFormat = GL.GL_LUMINANCE; pixelType = GL.GL_UNSIGNED_SHORT; rowLength = scanlineStride; alignment = 2; break; // Note: TYPE_INT_ARGB and TYPE_4BYTE_ABGR images go down the // custom code path to satisfy the invariant that images with an // alpha channel always go down with premultiplied alpha. case BufferedImage.TYPE_INT_ARGB: case BufferedImage.TYPE_4BYTE_ABGR: case BufferedImage.TYPE_BYTE_BINARY: case BufferedImage.TYPE_BYTE_INDEXED: case BufferedImage.TYPE_CUSTOM: default: ColorModel cm = image.getColorModel(); if (cm.equals(rgbColorModel)) { pixelFormat = GL.GL_RGB; pixelType = GL.GL_UNSIGNED_BYTE; rowLength = scanlineStride / 3; alignment = 1; } else if (cm.equals(rgbaColorModel)) { pixelFormat = GL.GL_RGBA; pixelType = GL.GL_UNSIGNED_BYTE; rowLength = scanlineStride / 4; // FIXME: correct? alignment = 4; } else { setupLazyCustomConversion(image); return; } break; } } else { switch (image.getType()) { case BufferedImage.TYPE_INT_RGB: pixelFormat = GL.GL_RGB; pixelType = GL.GL_UNSIGNED_BYTE; rowLength = scanlineStride; alignment = 3; expectingGL12 = true; setupLazyCustomConversion(image); break; case BufferedImage.TYPE_INT_ARGB_PRE: throw new GLException("INT_ARGB_PRE n.a."); case BufferedImage.TYPE_INT_BGR: throw new GLException("INT_BGR n.a."); case BufferedImage.TYPE_3BYTE_BGR: throw new GLException("INT_BGR n.a."); case BufferedImage.TYPE_4BYTE_ABGR_PRE: throw new GLException("INT_BGR n.a."); case BufferedImage.TYPE_USHORT_565_RGB: pixelFormat = GL.GL_RGB; pixelType = GL.GL_UNSIGNED_SHORT_5_6_5; rowLength = scanlineStride; alignment = 2; expectingGL12 = true; setupLazyCustomConversion(image); break; case BufferedImage.TYPE_USHORT_555_RGB: pixelFormat = GL.GL_RGBA; pixelType = GL.GL_UNSIGNED_SHORT_5_5_5_1; rowLength = scanlineStride; alignment = 2; expectingGL12 = true; setupLazyCustomConversion(image); break; case BufferedImage.TYPE_BYTE_GRAY: pixelFormat = GL.GL_LUMINANCE; pixelType = GL.GL_UNSIGNED_BYTE; rowLength = scanlineStride; alignment = 1; break; case BufferedImage.TYPE_USHORT_GRAY: throw new GLException("USHORT_GRAY n.a."); // Note: TYPE_INT_ARGB and TYPE_4BYTE_ABGR images go down the // custom code path to satisfy the invariant that images with an // alpha channel always go down with premultiplied alpha. case BufferedImage.TYPE_INT_ARGB: case BufferedImage.TYPE_4BYTE_ABGR: case BufferedImage.TYPE_BYTE_BINARY: case BufferedImage.TYPE_BYTE_INDEXED: case BufferedImage.TYPE_CUSTOM: default: ColorModel cm = image.getColorModel(); if (cm.equals(rgbColorModel)) { pixelFormat = GL.GL_RGB; pixelType = GL.GL_UNSIGNED_BYTE; rowLength = scanlineStride / 3; alignment = 1; } else if (cm.equals(rgbaColorModel)) { pixelFormat = GL.GL_RGBA; pixelType = GL.GL_UNSIGNED_BYTE; rowLength = scanlineStride / 4; // FIXME: correct? alignment = 4; } else { setupLazyCustomConversion(image); return; } break; } } createNIOBufferFromImage(image); } private void setupLazyCustomConversion(BufferedImage image) { imageForLazyCustomConversion = image; boolean hasAlpha = image.getColorModel().hasAlpha(); if (pixelFormat == 0) { pixelFormat = hasAlpha ? GL.GL_RGBA : GL.GL_RGB; } alignment = 1; // FIXME: do we need better? rowLength = width; // FIXME: correct in all cases? // Allow previously-selected pixelType (if any) to override that // we can infer from the DataBuffer DataBuffer data = image.getRaster().getDataBuffer(); if (data instanceof DataBufferByte || isPackedInt(image)) { // Don't use GL_UNSIGNED_INT for BufferedImage packed int images if (pixelType == 0) pixelType = GL.GL_UNSIGNED_BYTE; } else if (data instanceof DataBufferDouble) { throw new RuntimeException("DataBufferDouble rasters not supported by OpenGL"); } else if (data instanceof DataBufferFloat) { if (pixelType == 0) pixelType = GL.GL_FLOAT; } else if (data instanceof DataBufferInt) { // FIXME: should we support signed ints? if (pixelType == 0) pixelType = GL2.GL_UNSIGNED_INT; } else if (data instanceof DataBufferShort) { if (pixelType == 0) pixelType = GL.GL_SHORT; } else if (data instanceof DataBufferUShort) { if (pixelType == 0) pixelType = GL.GL_UNSIGNED_SHORT; } else { throw new RuntimeException("Unexpected DataBuffer type?"); } } private void createFromCustom(BufferedImage image) { int width = image.getWidth(); int height = image.getHeight(); // create a temporary image that is compatible with OpenGL boolean hasAlpha = image.getColorModel().hasAlpha(); ColorModel cm = null; int dataBufferType = image.getRaster().getDataBuffer().getDataType(); // Don't use integer components for packed int images if (isPackedInt(image)) { dataBufferType = DataBuffer.TYPE_BYTE; } if (dataBufferType == DataBuffer.TYPE_BYTE) { cm = hasAlpha ? rgbaColorModel : rgbColorModel; } else { if (hasAlpha) { cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), null, true, true, Transparency.TRANSLUCENT, dataBufferType); } else { cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), null, false, false, Transparency.OPAQUE, dataBufferType); } } boolean premult = cm.isAlphaPremultiplied(); WritableRaster raster = cm.createCompatibleWritableRaster(width, height); BufferedImage texImage = new BufferedImage(cm, raster, premult, null); // copy the source image into the temporary image Graphics2D g = texImage.createGraphics(); g.setComposite(AlphaComposite.Src); g.drawImage(image, 0, 0, null); g.dispose(); // Wrap the buffer from the temporary image createNIOBufferFromImage(texImage); } private boolean isPackedInt(BufferedImage image) { int imgType = image.getType(); return (imgType == BufferedImage.TYPE_INT_RGB || imgType == BufferedImage.TYPE_INT_BGR || imgType == BufferedImage.TYPE_INT_ARGB || imgType == BufferedImage.TYPE_INT_ARGB_PRE); } private void revertPixelFormatAndType() { // Knowing we don't have e.g. OpenGL 1.2 functionality available, // and knowing we're in the process of doing the fallback code // path, re-infer a vanilla pixel format and type compatible with // OpenGL 1.1 pixelFormat = 0; pixelType = 0; setupLazyCustomConversion(imageForLazyCustomConversion); } private void createNIOBufferFromImage(BufferedImage image) { buffer = wrapImageDataBuffer(image); } private Buffer wrapImageDataBuffer(BufferedImage image) { // // Note: Grabbing the DataBuffer will defeat Java2D's image // management mechanism (as of JDK 5/6, at least). This shouldn't // be a problem for most JOGL apps, but those that try to upload // the image into an OpenGL texture and then use the same image in // Java2D rendering might find the 2D rendering is not as fast as // it could be. // DataBuffer data = image.getRaster().getDataBuffer(); if (data instanceof DataBufferByte) { return ByteBuffer.wrap(((DataBufferByte) data).getData()); } else if (data instanceof DataBufferDouble) { throw new RuntimeException("DataBufferDouble rasters not supported by OpenGL"); } else if (data instanceof DataBufferFloat) { return FloatBuffer.wrap(((DataBufferFloat) data).getData()); } else if (data instanceof DataBufferInt) { return IntBuffer.wrap(((DataBufferInt) data).getData()); } else if (data instanceof DataBufferShort) { return ShortBuffer.wrap(((DataBufferShort) data).getData()); } else if (data instanceof DataBufferUShort) { return ShortBuffer.wrap(((DataBufferUShort) data).getData()); } else { throw new RuntimeException("Unexpected DataBuffer type?"); } } }