From b464faaae70ba12e5842b901a6c6d1601af0e1c7 Mon Sep 17 00:00:00 2001 From: Kenneth Russel Date: Mon, 9 Jan 2006 02:37:43 +0000 Subject: Added output support to TGAImage, SGIImage and newly-renamed DDSImage classes. Added support to TextureIO for writing textures back to disk via new TextureWriter plug-in interface. Added TextureConvert demo which shows how an application might convert between arbitrary file formats using these APIs, including automatic compression to DXT3 format when available. git-svn-id: file:///usr/local/projects/SUN/JOGL/git-svn/svn-server-sync/jogl/trunk@525 232f8b59-042b-4e1e-8c03-345bb8c30851 --- src/classes/com/sun/opengl/utils/DDSImage.java | 722 +++++++++++++++++++++ src/classes/com/sun/opengl/utils/DDSReader.java | 508 --------------- src/classes/com/sun/opengl/utils/SGIImage.java | 283 +++++++- src/classes/com/sun/opengl/utils/TGAImage.java | 112 +++- src/classes/com/sun/opengl/utils/Texture.java | 41 +- src/classes/com/sun/opengl/utils/TextureData.java | 11 +- src/classes/com/sun/opengl/utils/TextureIO.java | 454 ++++++++++++- .../com/sun/opengl/utils/TextureWriter.java | 55 ++ 8 files changed, 1589 insertions(+), 597 deletions(-) create mode 100755 src/classes/com/sun/opengl/utils/DDSImage.java delete mode 100755 src/classes/com/sun/opengl/utils/DDSReader.java create mode 100755 src/classes/com/sun/opengl/utils/TextureWriter.java (limited to 'src/classes/com/sun/opengl/utils') diff --git a/src/classes/com/sun/opengl/utils/DDSImage.java b/src/classes/com/sun/opengl/utils/DDSImage.java new file mode 100755 index 000000000..305e77348 --- /dev/null +++ b/src/classes/com/sun/opengl/utils/DDSImage.java @@ -0,0 +1,722 @@ +/* + * 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. + * + * Sun gratefully acknowledges that this software was originally authored + * and developed by Kenneth Bradley Russell and Christopher John Kline. + */ + +package com.sun.opengl.utils; + +import java.io.*; +import java.nio.*; +import java.nio.channels.*; + +/** A reader and writer for DirectDraw Surface (.dds) files, which are + used to describe textures. These files can contain multiple mipmap + levels in one file. This class is currently minimal and does not + support all of the possible file formats. */ + +public class DDSImage { + + /** Simple class describing images and data; does not encapsulate + image format information. User is responsible for transmitting + that information in another way. */ + + public static class ImageInfo { + private ByteBuffer data; + private int width; + private int height; + private boolean isCompressed; + private int compressionFormat; + + public ImageInfo(ByteBuffer data, int width, int height, boolean compressed, int compressionFormat) { + this.data = data; this.width = width; this.height = height; + this.isCompressed = compressed; this.compressionFormat = compressionFormat; + } + public int getWidth() { return width; } + public int getHeight() { return height; } + public ByteBuffer getData() { return data; } + public boolean isCompressed() { return isCompressed; } + public int getCompressionFormat() { + if (!isCompressed()) + throw new RuntimeException("Should not call unless compressed"); + return compressionFormat; + } + } + + private FileInputStream fis; + private FileChannel chan; + private ByteBuffer buf; + private Header header; + + // + // Selected bits in header flags + // + + public static final int DDSD_CAPS = 0x00000001; // Capacities are valid + public static final int DDSD_HEIGHT = 0x00000002; // Height is valid + public static final int DDSD_WIDTH = 0x00000004; // Width is valid + public static final int DDSD_PITCH = 0x00000008; // Pitch is valid + public static final int DDSD_BACKBUFFERCOUNT = 0x00000020; // Back buffer count is valid + public static final int DDSD_ZBUFFERBITDEPTH = 0x00000040; // Z-buffer bit depth is valid (shouldn't be used in DDSURFACEDESC2) + public static final int DDSD_ALPHABITDEPTH = 0x00000080; // Alpha bit depth is valid + public static final int DDSD_LPSURFACE = 0x00000800; // lpSurface is valid + public static final int DDSD_PIXELFORMAT = 0x00001000; // ddpfPixelFormat is valid + public static final int DDSD_MIPMAPCOUNT = 0x00020000; // Mip map count is valid + public static final int DDSD_LINEARSIZE = 0x00080000; // dwLinearSize is valid + public static final int DDSD_DEPTH = 0x00800000; // dwDepth is valid + + public static final int DDPF_ALPHAPIXELS = 0x00000001; // Alpha channel is present + public static final int DDPF_ALPHA = 0x00000002; // Only contains alpha information + public static final int DDPF_FOURCC = 0x00000004; // FourCC code is valid + public static final int DDPF_PALETTEINDEXED4 = 0x00000008; // Surface is 4-bit color indexed + public static final int DDPF_PALETTEINDEXEDTO8 = 0x00000010; // Surface is indexed into a palette which stores indices + // into the destination surface's 8-bit palette + public static final int DDPF_PALETTEINDEXED8 = 0x00000020; // Surface is 8-bit color indexed + public static final int DDPF_RGB = 0x00000040; // RGB data is present + public static final int DDPF_COMPRESSED = 0x00000080; // Surface will accept pixel data in the format specified + // and compress it during the write + public static final int DDPF_RGBTOYUV = 0x00000100; // Surface will accept RGB data and translate it during + // the write to YUV data. The format of the data to be written + // will be contained in the pixel format structure. The DDPF_RGB + // flag will be set. + public static final int DDPF_YUV = 0x00000200; // Pixel format is YUV - YUV data in pixel format struct is valid + public static final int DDPF_ZBUFFER = 0x00000400; // Pixel format is a z buffer only surface + public static final int DDPF_PALETTEINDEXED1 = 0x00000800; // Surface is 1-bit color indexed + public static final int DDPF_PALETTEINDEXED2 = 0x00001000; // Surface is 2-bit color indexed + public static final int DDPF_ZPIXELS = 0x00002000; // Surface contains Z information in the pixels + + // Selected bits in DDS capabilities flags + public static final int DDSCAPS_TEXTURE = 0x00001000; // Can be used as a texture + public static final int DDSCAPS_MIPMAP = 0x00400000; // Is one level of a mip-map + + // Known pixel formats + public static final int D3DFMT_UNKNOWN = 0; + public static final int D3DFMT_R8G8B8 = 20; + public static final int D3DFMT_A8R8G8B8 = 21; + public static final int D3DFMT_X8R8G8B8 = 22; + // The following are also valid FourCC codes + public static final int D3DFMT_DXT1 = 0x31545844; + public static final int D3DFMT_DXT2 = 0x32545844; + public static final int D3DFMT_DXT3 = 0x33545844; + public static final int D3DFMT_DXT4 = 0x34545844; + public static final int D3DFMT_DXT5 = 0x35545844; + + /** Reads a DirectDraw surface from the specified file name, + returning the resulting DDSImage. */ + public static DDSImage read(String filename) throws IOException { + return read(new File(filename)); + } + + /** Reads a DirectDraw surface from the specified file, returning + the resulting DDSImage. */ + public static DDSImage read(File file) throws IOException { + DDSImage image = new DDSImage(); + image.readFromFile(file); + return image; + } + + /** Closes open files and resources associated with the open + DDSImage. No other methods may be called on this object once + this is called. */ + public void close() { + try { + if (chan != null) { + chan.close(); + chan = null; + } + if (fis != null) { + fis.close(); + fis = null; + } + buf = null; + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Creates a new DDSImage from data supplied by the user. The + * resulting DDSImage can be written to disk using the write() + * method. + * + * @param d3dFormat the D3DFMT_ constant describing the data; it is + * assumed that it is packed tightly + * @param width the width in pixels of the topmost mipmap image + * @param height the height in pixels of the topmost mipmap image + * @param mipmapData the data for each mipmap level of the resulting + * DDSImage; either only one mipmap level should + * be specified, or they all must be + * @throws IllegalArgumentException if the data does not match the + * specified arguments + */ + public static DDSImage createFromData(int d3dFormat, + int width, + int height, + ByteBuffer[] mipmapData) throws IllegalArgumentException { + DDSImage image = new DDSImage(); + image.initFromData(d3dFormat, width, height, mipmapData); + return image; + } + + /** + * Writes this DDSImage to the specified file name. + */ + public void write(String filename) throws IOException { + write(new File(filename)); + } + + /** + * Writes this DDSImage to the specified file name. + */ + public void write(File file) throws IOException { + FileOutputStream stream = new FileOutputStream(file); + FileChannel chan = stream.getChannel(); + // Create ByteBuffer for header in case the start of our + // ByteBuffer isn't actually memory-mapped + ByteBuffer hdr = ByteBuffer.allocate(Header.writtenSize()); + hdr.order(ByteOrder.LITTLE_ENDIAN); + header.write(hdr); + hdr.rewind(); + chan.write(hdr); + buf.position(Header.writtenSize()); + chan.write(buf); + chan.force(true); + chan.close(); + stream.close(); + } + + /** Test for presence/absence of surface description flags (DDSD_*) */ + public boolean isSurfaceDescFlagSet(int flag) { + return ((header.flags & flag) != 0); + } + + /** Test for presence/absence of pixel format flags (DDPF_*) */ + public boolean isPixelFormatFlagSet(int flag) { + return ((header.pfFlags & flag) != 0); + } + + /** Gets the pixel format of this texture (D3DFMT_*) based on some + heuristics. Returns D3DFMT_UNKNOWN if could not recognize the + pixel format. */ + public int getPixelFormat() { + if (isCompressed()) { + return getCompressionFormat(); + } else if (isPixelFormatFlagSet(DDPF_RGB)) { + if (isPixelFormatFlagSet(DDPF_ALPHAPIXELS)) { + if (getDepth() == 32 && + header.pfRBitMask == 0x00FF0000 && + header.pfGBitMask == 0x0000FF00 && + header.pfBBitMask == 0x000000FF && + header.pfABitMask == 0xFF000000) { + return D3DFMT_A8R8G8B8; + } + } else { + if (getDepth() == 24 && + header.pfRBitMask == 0x00FF0000 && + header.pfGBitMask == 0x0000FF00 && + header.pfBBitMask == 0x000000FF) { + return D3DFMT_R8G8B8; + } else if (getDepth() == 32 && + header.pfRBitMask == 0x00FF0000 && + header.pfGBitMask == 0x0000FF00 && + header.pfBBitMask == 0x000000FF) { + return D3DFMT_X8R8G8B8; + } + } + } + + return D3DFMT_UNKNOWN; + } + + /** Indicates whether this texture is compressed. */ + public boolean isCompressed() { + return (isPixelFormatFlagSet(DDPF_FOURCC)); + } + + /** If this surface is compressed, returns the kind of compression + used (DXT1..DXT5). */ + public int getCompressionFormat() { + return header.pfFourCC; + } + + /** Width of the texture (or the top-most mipmap if mipmaps are + present) */ + public int getWidth() { + return header.width; + } + + /** Height of the texture (or the top-most mipmap if mipmaps are + present) */ + public int getHeight() { + return header.height; + } + + /** Total number of bits per pixel. Only valid if DDPF_RGB is + present. For A8R8G8B8, would be 32. */ + public int getDepth() { + return header.pfRGBBitCount; + } + + /** Number of mip maps in the texture */ + public int getNumMipMaps() { + if (!isSurfaceDescFlagSet(DDSD_MIPMAPCOUNT)) { + return 0; + } + return header.mipMapCountOrAux; + } + + /** Gets the ith mipmap data (0..getNumMipMaps() - 1) */ + public ImageInfo getMipMap(int map) { + if (getNumMipMaps() > 0 && + ((map < 0) || (map >= getNumMipMaps()))) { + throw new RuntimeException("Illegal mipmap number " + map + " (0.." + (getNumMipMaps() - 1) + ")"); + } + + // Figure out how far to seek + int seek = Header.writtenSize(); + for (int i = 0; i < map; i++) { + seek += mipMapSizeInBytes(i); + } + buf.limit(seek + mipMapSizeInBytes(map)); + buf.position(seek); + ByteBuffer next = buf.slice(); + buf.position(0); + buf.limit(buf.capacity()); + return new ImageInfo(next, mipMapWidth(map), mipMapHeight(map), isCompressed(), getCompressionFormat()); + } + + /** Returns an array of ImageInfos corresponding to all mipmap + levels of this DDS file. */ + public ImageInfo[] getAllMipMaps() { + int numLevels = getNumMipMaps(); + if (numLevels == 0) { + numLevels = 1; + } + ImageInfo[] result = new ImageInfo[numLevels]; + for (int i = 0; i < numLevels; i++) { + result[i] = getMipMap(i); + } + return result; + } + + /** Converts e.g. DXT1 compression format constant (see {@link + #getCompressionFormat}) into "DXT1". */ + public static String getCompressionFormatName(int compressionFormat) { + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < 4; i++) { + char c = (char) (compressionFormat & 0xFF); + buf.append(c); + compressionFormat = compressionFormat >> 8; + } + return buf.toString(); + } + + public void debugPrint() { + PrintStream tty = System.err; + tty.println("Compressed texture: " + isCompressed()); + if (isCompressed()) { + int fmt = getCompressionFormat(); + String name = getCompressionFormatName(fmt); + tty.println("Compression format: 0x" + Integer.toHexString(fmt) + " (" + name + ")"); + } + tty.println("Width: " + header.width + " Height: " + header.height); + tty.println("header.pitchOrLinearSize: " + header.pitchOrLinearSize); + tty.println("header.pfRBitMask: 0x" + Integer.toHexString(header.pfRBitMask)); + tty.println("header.pfGBitMask: 0x" + Integer.toHexString(header.pfGBitMask)); + tty.println("header.pfBBitMask: 0x" + Integer.toHexString(header.pfBBitMask)); + tty.println("SurfaceDesc flags:"); + boolean recognizedAny = false; + recognizedAny |= printIfRecognized(tty, header.flags, DDSD_CAPS, "DDSD_CAPS"); + recognizedAny |= printIfRecognized(tty, header.flags, DDSD_HEIGHT, "DDSD_HEIGHT"); + recognizedAny |= printIfRecognized(tty, header.flags, DDSD_WIDTH, "DDSD_WIDTH"); + recognizedAny |= printIfRecognized(tty, header.flags, DDSD_PITCH, "DDSD_PITCH"); + recognizedAny |= printIfRecognized(tty, header.flags, DDSD_BACKBUFFERCOUNT, "DDSD_BACKBUFFERCOUNT"); + recognizedAny |= printIfRecognized(tty, header.flags, DDSD_ZBUFFERBITDEPTH, "DDSD_ZBUFFERBITDEPTH"); + recognizedAny |= printIfRecognized(tty, header.flags, DDSD_ALPHABITDEPTH, "DDSD_ALPHABITDEPTH"); + recognizedAny |= printIfRecognized(tty, header.flags, DDSD_LPSURFACE, "DDSD_LPSURFACE"); + recognizedAny |= printIfRecognized(tty, header.flags, DDSD_PIXELFORMAT, "DDSD_PIXELFORMAT"); + recognizedAny |= printIfRecognized(tty, header.flags, DDSD_MIPMAPCOUNT, "DDSD_MIPMAPCOUNT"); + recognizedAny |= printIfRecognized(tty, header.flags, DDSD_LINEARSIZE, "DDSD_LINEARSIZE"); + recognizedAny |= printIfRecognized(tty, header.flags, DDSD_DEPTH, "DDSD_DEPTH"); + if (!recognizedAny) { + tty.println("(none)"); + } + tty.println("Raw SurfaceDesc flags: 0x" + Integer.toHexString(header.flags)); + tty.println("Pixel format flags:"); + recognizedAny = false; + recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_ALPHAPIXELS, "DDPF_ALPHAPIXELS"); + recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_ALPHA, "DDPF_ALPHA"); + recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_FOURCC, "DDPF_FOURCC"); + recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXED4, "DDPF_PALETTEINDEXED4"); + recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXEDTO8, "DDPF_PALETTEINDEXEDTO8"); + recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXED8, "DDPF_PALETTEINDEXED8"); + recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_RGB, "DDPF_RGB"); + recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_COMPRESSED, "DDPF_COMPRESSED"); + recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_RGBTOYUV, "DDPF_RGBTOYUV"); + recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_YUV, "DDPF_YUV"); + recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_ZBUFFER, "DDPF_ZBUFFER"); + recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXED1, "DDPF_PALETTEINDEXED1"); + recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXED2, "DDPF_PALETTEINDEXED2"); + recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_ZPIXELS, "DDPF_ZPIXELS"); + if (!recognizedAny) { + tty.println("(none)"); + } + tty.println("Raw pixel format flags: 0x" + Integer.toHexString(header.pfFlags)); + tty.println("Depth: " + getDepth()); + tty.println("Number of mip maps: " + getNumMipMaps()); + int fmt = getPixelFormat(); + tty.print("Pixel format: "); + switch (fmt) { + case D3DFMT_R8G8B8: tty.println("D3DFMT_R8G8B8"); break; + case D3DFMT_A8R8G8B8: tty.println("D3DFMT_A8R8G8B8"); break; + case D3DFMT_X8R8G8B8: tty.println("D3DFMT_X8R8G8B8"); break; + case D3DFMT_DXT1: tty.println("D3DFMT_DXT1"); break; + case D3DFMT_DXT2: tty.println("D3DFMT_DXT2"); break; + case D3DFMT_DXT3: tty.println("D3DFMT_DXT3"); break; + case D3DFMT_DXT4: tty.println("D3DFMT_DXT4"); break; + case D3DFMT_DXT5: tty.println("D3DFMT_DXT5"); break; + case D3DFMT_UNKNOWN: tty.println("D3DFMT_UNKNOWN"); break; + default: tty.println("(unknown pixel format " + fmt + ")"); break; + } + } + + //---------------------------------------------------------------------- + // Internals only below this point + // + + private static final int MAGIC = 0x20534444; + + static class Header { + int size; // size of the DDSURFACEDESC structure + int flags; // determines what fields are valid + int height; // height of surface to be created + int width; // width of input surface + int pitchOrLinearSize; + int backBufferCountOrDepth; + int mipMapCountOrAux; // number of mip-map levels requested (in this context) + int alphaBitDepth; // depth of alpha buffer requested + int reserved1; // reserved + int surface; // pointer to the associated surface memory + // NOTE: following two entries are from DDCOLORKEY data structure + // Are overlaid with color for empty cubemap faces (unused in this reader) + int colorSpaceLowValue; + int colorSpaceHighValue; + int destBltColorSpaceLowValue; + int destBltColorSpaceHighValue; + int srcOverlayColorSpaceLowValue; + int srcOverlayColorSpaceHighValue; + int srcBltColorSpaceLowValue; + int srcBltColorSpaceHighValue; + // NOTE: following entries are from DDPIXELFORMAT data structure + // Are overlaid with flexible vertex format description of vertex + // buffers (unused in this reader) + int pfSize; // size of DDPIXELFORMAT structure + int pfFlags; // pixel format flags + int pfFourCC; // (FOURCC code) + // Following five entries have multiple interpretations, not just + // RGBA (but that's all we support right now) + int pfRGBBitCount; // how many bits per pixel + int pfRBitMask; // mask for red bits + int pfGBitMask; // mask for green bits + int pfBBitMask; // mask for blue bits + int pfABitMask; // mask for alpha channel + int ddsCaps1; // Texture and mip-map flags + int ddsCaps2; // Advanced capabilities, not yet used + int ddsCapsReserved1; + int ddsCapsReserved2; + int textureStage; // stage in multitexture cascade + + void read(ByteBuffer buf) throws IOException { + int magic = buf.getInt(); + if (magic != MAGIC) { + throw new IOException("Incorrect magic number 0x" + + Integer.toHexString(magic) + + " (expected " + MAGIC + ")"); + } + + size = buf.getInt(); + flags = buf.getInt(); + height = buf.getInt(); + width = buf.getInt(); + pitchOrLinearSize = buf.getInt(); + backBufferCountOrDepth = buf.getInt(); + mipMapCountOrAux = buf.getInt(); + alphaBitDepth = buf.getInt(); + reserved1 = buf.getInt(); + surface = buf.getInt(); + colorSpaceLowValue = buf.getInt(); + colorSpaceHighValue = buf.getInt(); + destBltColorSpaceLowValue = buf.getInt(); + destBltColorSpaceHighValue = buf.getInt(); + srcOverlayColorSpaceLowValue = buf.getInt(); + srcOverlayColorSpaceHighValue = buf.getInt(); + srcBltColorSpaceLowValue = buf.getInt(); + srcBltColorSpaceHighValue = buf.getInt(); + pfSize = buf.getInt(); + pfFlags = buf.getInt(); + pfFourCC = buf.getInt(); + pfRGBBitCount = buf.getInt(); + pfRBitMask = buf.getInt(); + pfGBitMask = buf.getInt(); + pfBBitMask = buf.getInt(); + pfABitMask = buf.getInt(); + ddsCaps1 = buf.getInt(); + ddsCaps2 = buf.getInt(); + ddsCapsReserved1 = buf.getInt(); + ddsCapsReserved2 = buf.getInt(); + textureStage = buf.getInt(); + } + + // buf must be in little-endian byte order + void write(ByteBuffer buf) { + buf.putInt(MAGIC); + buf.putInt(size); + buf.putInt(flags); + buf.putInt(height); + buf.putInt(width); + buf.putInt(pitchOrLinearSize); + buf.putInt(backBufferCountOrDepth); + buf.putInt(mipMapCountOrAux); + buf.putInt(alphaBitDepth); + buf.putInt(reserved1); + buf.putInt(surface); + buf.putInt(colorSpaceLowValue); + buf.putInt(colorSpaceHighValue); + buf.putInt(destBltColorSpaceLowValue); + buf.putInt(destBltColorSpaceHighValue); + buf.putInt(srcOverlayColorSpaceLowValue); + buf.putInt(srcOverlayColorSpaceHighValue); + buf.putInt(srcBltColorSpaceLowValue); + buf.putInt(srcBltColorSpaceHighValue); + buf.putInt(pfSize); + buf.putInt(pfFlags); + buf.putInt(pfFourCC); + buf.putInt(pfRGBBitCount); + buf.putInt(pfRBitMask); + buf.putInt(pfGBitMask); + buf.putInt(pfBBitMask); + buf.putInt(pfABitMask); + buf.putInt(ddsCaps1); + buf.putInt(ddsCaps2); + buf.putInt(ddsCapsReserved1); + buf.putInt(ddsCapsReserved2); + buf.putInt(textureStage); + } + + private static final int size() { + return 124; + } + + private static final int pfSize() { + return 32; + } + + private static final int writtenSize() { + return 128; + } + } + + private DDSImage() { + } + + private void readFromFile(File file) throws IOException { + fis = new FileInputStream(file); + chan = fis.getChannel(); + buf = chan.map(FileChannel.MapMode.READ_ONLY, + 0, (int) file.length()); + buf.order(ByteOrder.LITTLE_ENDIAN); + header = new Header(); + header.read(buf); + fixupHeader(); + } + + private void initFromData(int d3dFormat, + int width, + int height, + ByteBuffer[] mipmapData) throws IllegalArgumentException { + // Check size of mipmap data compared against format, width and + // height + int topmostMipmapSize = width * height; + int pitchOrLinearSize = width; + boolean isCompressed = false; + switch (d3dFormat) { + case D3DFMT_R8G8B8: topmostMipmapSize *= 3; pitchOrLinearSize *= 3; break; + case D3DFMT_A8R8G8B8: topmostMipmapSize *= 4; pitchOrLinearSize *= 4; break; + case D3DFMT_X8R8G8B8: topmostMipmapSize *= 4; pitchOrLinearSize *= 4; break; + case D3DFMT_DXT1: + case D3DFMT_DXT2: + case D3DFMT_DXT3: + case D3DFMT_DXT4: + case D3DFMT_DXT5: + topmostMipmapSize = computeCompressedBlockSize(width, height, 1, d3dFormat); + pitchOrLinearSize = topmostMipmapSize; + isCompressed = true; + break; + default: + throw new IllegalArgumentException("d3dFormat must be one of the known formats"); + } + + // Now check the mipmaps against this size + int curSize = topmostMipmapSize; + int totalSize = 0; + for (int i = 0; i < mipmapData.length; i++) { + if (mipmapData[i].remaining() != curSize) { + throw new IllegalArgumentException("Mipmap level " + i + + " didn't match expected data size (expected " + curSize + ", got " + + mipmapData[i].remaining() + ")"); + } + curSize /= 4; + totalSize += mipmapData[i].remaining(); + } + + // OK, create one large ByteBuffer to hold all of the mipmap data + totalSize += Header.writtenSize(); + ByteBuffer buf = ByteBuffer.allocate(totalSize); + buf.position(Header.writtenSize()); + for (int i = 0; i < mipmapData.length; i++) { + buf.put(mipmapData[i]); + } + this.buf = buf; + + // Allocate and initialize a Header + header = new Header(); + header.size = Header.size(); + header.flags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT; + if (mipmapData.length > 1) { + header.flags |= DDSD_MIPMAPCOUNT; + header.mipMapCountOrAux = mipmapData.length; + } + header.width = width; + header.height = height; + if (isCompressed) { + header.flags |= DDSD_LINEARSIZE; + header.pfFlags |= DDPF_FOURCC; + header.pfFourCC = d3dFormat; + } else { + header.flags |= DDSD_PITCH; + // Figure out the various settings from the pixel format + header.pfFlags |= DDPF_RGB; + switch (d3dFormat) { + case D3DFMT_R8G8B8: header.pfRGBBitCount = 24; break; + case D3DFMT_A8R8G8B8: header.pfRGBBitCount = 32; header.pfFlags |= DDPF_ALPHAPIXELS; break; + case D3DFMT_X8R8G8B8: header.pfRGBBitCount = 32; break; + } + header.pfRBitMask = 0x00FF0000; + header.pfGBitMask = 0x0000FF00; + header.pfBBitMask = 0x000000FF; + if (d3dFormat == D3DFMT_A8R8G8B8) { + header.pfABitMask = 0xFF000000; + } + } + header.pitchOrLinearSize = pitchOrLinearSize; + header.pfSize = Header.pfSize(); + // Not sure whether we can get away with leaving the rest of the + // header blank + } + + // Microsoft doesn't follow their own specifications and the + // simplest conversion using the DxTex tool to e.g. a DXT3 texture + // results in an illegal .dds file without either DDSD_PITCH or + // DDSD_LINEARSIZE set in the header's flags. This code, adapted + // from the DevIL library, fixes up the header in these situations. + private void fixupHeader() { + if (isCompressed() && !isSurfaceDescFlagSet(DDSD_LINEARSIZE)) { + // Figure out how big the linear size should be + int depth = header.backBufferCountOrDepth; + if (depth == 0) { + depth = 1; + } + + int blockSize = computeCompressedBlockSize(getWidth(), getHeight(), depth, getCompressionFormat()); + + header.pitchOrLinearSize = blockSize; + header.flags |= DDSD_LINEARSIZE; + } + } + + private static int computeCompressedBlockSize(int width, + int height, + int depth, + int compressionFormat) { + int blockSize = ((width + 3)/4) * ((height + 3)/4) * ((depth + 3)/4); + switch (compressionFormat) { + case D3DFMT_DXT1: blockSize *= 8; break; + default: blockSize *= 16; break; + } + return blockSize; + } + + private int mipMapWidth(int map) { + int width = getWidth(); + for (int i = 0; i < map; i++) { + width >>= 1; + } + return width; + } + + private int mipMapHeight(int map) { + int height = getHeight(); + for (int i = 0; i < map; i++) { + height >>= 1; + } + return height; + } + + private int mipMapSizeInBytes(int map) { + if (isCompressed()) { + if (!isSurfaceDescFlagSet(DDSD_LINEARSIZE)) { + throw new RuntimeException("Illegal compressed texture: DDSD_LINEARSIZE not specified in texture header"); + } + int bytes = header.pitchOrLinearSize; + for (int i = 0; i < map; i++) { + bytes >>= 2; + } + return bytes; + } else { + int width = mipMapWidth(map); + int height = mipMapHeight(map); + return width * height * (getDepth() / 8); + } + } + + private boolean printIfRecognized(PrintStream tty, int flags, int flag, String what) { + if ((flags & flag) != 0) { + tty.println(what); + return true; + } + return false; + } +} diff --git a/src/classes/com/sun/opengl/utils/DDSReader.java b/src/classes/com/sun/opengl/utils/DDSReader.java deleted file mode 100755 index dbbf8e65b..000000000 --- a/src/classes/com/sun/opengl/utils/DDSReader.java +++ /dev/null @@ -1,508 +0,0 @@ -/* - * 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. - * - * Sun gratefully acknowledges that this software was originally authored - * and developed by Kenneth Bradley Russell and Christopher John Kline. - */ - -package com.sun.opengl.utils; - -import java.io.*; -import java.nio.*; -import java.nio.channels.*; - -/** A reader for DirectDraw Surface (.dds) files, which are used to - describe textures. These files can contain multiple mipmap levels - in one file. This reader is currently minimal and does not support - all of the possible file formats. */ - -public class DDSReader { - - /** Simple class describing images and data; does not encapsulate - image format information. User is responsible for transmitting - that information in another way. */ - - public static class ImageInfo { - private ByteBuffer data; - private int width; - private int height; - private boolean isCompressed; - private int compressionFormat; - - public ImageInfo(ByteBuffer data, int width, int height, boolean compressed, int compressionFormat) { - this.data = data; this.width = width; this.height = height; - this.isCompressed = compressed; this.compressionFormat = compressionFormat; - } - public int getWidth() { return width; } - public int getHeight() { return height; } - public ByteBuffer getData() { return data; } - public boolean isCompressed() { return isCompressed; } - public int getCompressionFormat() { - if (!isCompressed()) - throw new RuntimeException("Should not call unless compressed"); - return compressionFormat; - } - } - - private FileInputStream fis; - private FileChannel chan; - private ByteBuffer buf; - private Header header; - - // - // Selected bits in header flags - // - - public static final int DDSD_CAPS = 0x00000001; // Capacities are valid - public static final int DDSD_HEIGHT = 0x00000002; // Height is valid - public static final int DDSD_WIDTH = 0x00000004; // Width is valid - public static final int DDSD_PITCH = 0x00000008; // Pitch is valid - public static final int DDSD_BACKBUFFERCOUNT = 0x00000020; // Back buffer count is valid - public static final int DDSD_ZBUFFERBITDEPTH = 0x00000040; // Z-buffer bit depth is valid (shouldn't be used in DDSURFACEDESC2) - public static final int DDSD_ALPHABITDEPTH = 0x00000080; // Alpha bit depth is valid - public static final int DDSD_LPSURFACE = 0x00000800; // lpSurface is valid - public static final int DDSD_PIXELFORMAT = 0x00001000; // ddpfPixelFormat is valid - public static final int DDSD_MIPMAPCOUNT = 0x00020000; // Mip map count is valid - public static final int DDSD_LINEARSIZE = 0x00080000; // dwLinearSize is valid - public static final int DDSD_DEPTH = 0x00800000; // dwDepth is valid - - public static final int DDPF_ALPHAPIXELS = 0x00000001; // Alpha channel is present - public static final int DDPF_ALPHA = 0x00000002; // Only contains alpha information - public static final int DDPF_FOURCC = 0x00000004; // FourCC code is valid - public static final int DDPF_PALETTEINDEXED4 = 0x00000008; // Surface is 4-bit color indexed - public static final int DDPF_PALETTEINDEXEDTO8 = 0x00000010; // Surface is indexed into a palette which stores indices - // into the destination surface's 8-bit palette - public static final int DDPF_PALETTEINDEXED8 = 0x00000020; // Surface is 8-bit color indexed - public static final int DDPF_RGB = 0x00000040; // RGB data is present - public static final int DDPF_COMPRESSED = 0x00000080; // Surface will accept pixel data in the format specified - // and compress it during the write - public static final int DDPF_RGBTOYUV = 0x00000100; // Surface will accept RGB data and translate it during - // the write to YUV data. The format of the data to be written - // will be contained in the pixel format structure. The DDPF_RGB - // flag will be set. - public static final int DDPF_YUV = 0x00000200; // Pixel format is YUV - YUV data in pixel format struct is valid - public static final int DDPF_ZBUFFER = 0x00000400; // Pixel format is a z buffer only surface - public static final int DDPF_PALETTEINDEXED1 = 0x00000800; // Surface is 1-bit color indexed - public static final int DDPF_PALETTEINDEXED2 = 0x00001000; // Surface is 2-bit color indexed - public static final int DDPF_ZPIXELS = 0x00002000; // Surface contains Z information in the pixels - - // Selected bits in DDS capabilities flags - public static final int DDSCAPS_TEXTURE = 0x00001000; // Can be used as a texture - public static final int DDSCAPS_MIPMAP = 0x00400000; // Is one level of a mip-map - - // Known pixel formats - public static final int D3DFMT_UNKNOWN = 0; - public static final int D3DFMT_R8G8B8 = 20; - public static final int D3DFMT_A8R8G8B8 = 21; - public static final int D3DFMT_X8R8G8B8 = 22; - // The following are also valid FourCC codes - public static final int D3DFMT_DXT1 = 0x31545844; - public static final int D3DFMT_DXT2 = 0x32545844; - public static final int D3DFMT_DXT3 = 0x33545844; - public static final int D3DFMT_DXT4 = 0x34545844; - public static final int D3DFMT_DXT5 = 0x35545844; - - public void loadFile(String filename) throws IOException { - loadFile(new File(filename)); - } - - public void loadFile(File file) throws IOException { - fis = new FileInputStream(file); - chan = fis.getChannel(); - buf = chan.map(FileChannel.MapMode.READ_ONLY, - 0, (int) file.length()); - buf.order(ByteOrder.LITTLE_ENDIAN); - header = new Header(); - header.read(buf); - fixupHeader(); - } - - public void close() { - try { - chan.close(); - fis.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** Test for presence/absence of surface description flags (DDSD_*) */ - public boolean isSurfaceDescFlagSet(int flag) { - return ((header.flags & flag) != 0); - } - - /** Test for presence/absence of pixel format flags (DDPF_*) */ - public boolean isPixelFormatFlagSet(int flag) { - return ((header.pfFlags & flag) != 0); - } - - /** Gets the pixel format of this texture (D3DFMT_*) based on some - heuristics. Returns D3DFMT_UNKNOWN if could not recognize the - pixel format. */ - public int getPixelFormat() { - if (isCompressed()) { - return getCompressionFormat(); - } else if (isPixelFormatFlagSet(DDPF_RGB)) { - if (isPixelFormatFlagSet(DDPF_ALPHAPIXELS)) { - if (getDepth() == 32 && - header.pfRBitMask == 0x00FF0000 && - header.pfGBitMask == 0x0000FF00 && - header.pfBBitMask == 0x000000FF && - header.pfABitMask == 0xFF000000) { - return D3DFMT_A8R8G8B8; - } - } else { - if (getDepth() == 24 && - header.pfRBitMask == 0x00FF0000 && - header.pfGBitMask == 0x0000FF00 && - header.pfBBitMask == 0x000000FF) { - return D3DFMT_R8G8B8; - } else if (getDepth() == 32 && - header.pfRBitMask == 0x00FF0000 && - header.pfGBitMask == 0x0000FF00 && - header.pfBBitMask == 0x000000FF) { - return D3DFMT_X8R8G8B8; - } - } - } - - return D3DFMT_UNKNOWN; - } - - /** Indicates whether this texture is compressed. */ - public boolean isCompressed() { - return (isPixelFormatFlagSet(DDPF_FOURCC)); - } - - /** If this surface is compressed, returns the kind of compression - used (DXT1..DXT5). */ - public int getCompressionFormat() { - return header.pfFourCC; - } - - /** Width of the texture (or the top-most mipmap if mipmaps are - present) */ - public int getWidth() { - return header.width; - } - - /** Height of the texture (or the top-most mipmap if mipmaps are - present) */ - public int getHeight() { - return header.height; - } - - /** Total number of bits per pixel. Only valid if DDPF_RGB is - present. For A8R8G8B8, would be 32. */ - public int getDepth() { - return header.pfRGBBitCount; - } - - /** Number of mip maps in the texture */ - public int getNumMipMaps() { - if (!isSurfaceDescFlagSet(DDSD_MIPMAPCOUNT)) { - return 0; - } - return header.mipMapCountOrAux; - } - - /** Gets the ith mipmap data (0..getNumMipMaps() - 1) */ - public ImageInfo getMipMap(int map) { - if (getNumMipMaps() > 0 && - ((map < 0) || (map >= getNumMipMaps()))) { - throw new RuntimeException("Illegal mipmap number " + map + " (0.." + (getNumMipMaps() - 1) + ")"); - } - - // Figure out how far to seek - int seek = 4 + header.size; - for (int i = 0; i < map; i++) { - seek += mipMapSizeInBytes(i); - } - buf.limit(seek + mipMapSizeInBytes(map)); - buf.position(seek); - ByteBuffer next = buf.slice(); - buf.position(0); - buf.limit(buf.capacity()); - return new ImageInfo(next, mipMapWidth(map), mipMapHeight(map), isCompressed(), getCompressionFormat()); - } - - /** Returns an array of ImageInfos corresponding to all mipmap - levels of this DDS file. */ - public ImageInfo[] getAllMipMaps() { - int numLevels = getNumMipMaps(); - if (numLevels == 0) { - numLevels = 1; - } - ImageInfo[] result = new ImageInfo[numLevels]; - for (int i = 0; i < numLevels; i++) { - result[i] = getMipMap(i); - } - return result; - } - - /** Converts e.g. DXT1 compression format constant (see {@link - #getCompressionFormat}) into "DXT1". */ - public static String getCompressionFormatName(int compressionFormat) { - StringBuffer buf = new StringBuffer(); - for (int i = 0; i < 4; i++) { - char c = (char) (compressionFormat & 0xFF); - buf.append(c); - compressionFormat = compressionFormat >> 8; - } - return buf.toString(); - } - - public void debugPrint() { - PrintStream tty = System.err; - tty.println("Compressed texture: " + isCompressed()); - if (isCompressed()) { - int fmt = getCompressionFormat(); - String name = getCompressionFormatName(fmt); - tty.println("Compression format: 0x" + Integer.toHexString(fmt) + " (" + name + ")"); - } - tty.println("Width: " + header.width + " Height: " + header.height); - tty.println("header.pitchOrLinearSize: " + header.pitchOrLinearSize); - tty.println("header.pfRBitMask: 0x" + Integer.toHexString(header.pfRBitMask)); - tty.println("header.pfGBitMask: 0x" + Integer.toHexString(header.pfGBitMask)); - tty.println("header.pfBBitMask: 0x" + Integer.toHexString(header.pfBBitMask)); - tty.println("SurfaceDesc flags:"); - boolean recognizedAny = false; - recognizedAny |= printIfRecognized(tty, header.flags, DDSD_CAPS, "DDSD_CAPS"); - recognizedAny |= printIfRecognized(tty, header.flags, DDSD_HEIGHT, "DDSD_HEIGHT"); - recognizedAny |= printIfRecognized(tty, header.flags, DDSD_WIDTH, "DDSD_WIDTH"); - recognizedAny |= printIfRecognized(tty, header.flags, DDSD_PITCH, "DDSD_PITCH"); - recognizedAny |= printIfRecognized(tty, header.flags, DDSD_BACKBUFFERCOUNT, "DDSD_BACKBUFFERCOUNT"); - recognizedAny |= printIfRecognized(tty, header.flags, DDSD_ZBUFFERBITDEPTH, "DDSD_ZBUFFERBITDEPTH"); - recognizedAny |= printIfRecognized(tty, header.flags, DDSD_ALPHABITDEPTH, "DDSD_ALPHABITDEPTH"); - recognizedAny |= printIfRecognized(tty, header.flags, DDSD_LPSURFACE, "DDSD_LPSURFACE"); - recognizedAny |= printIfRecognized(tty, header.flags, DDSD_PIXELFORMAT, "DDSD_PIXELFORMAT"); - recognizedAny |= printIfRecognized(tty, header.flags, DDSD_MIPMAPCOUNT, "DDSD_MIPMAPCOUNT"); - recognizedAny |= printIfRecognized(tty, header.flags, DDSD_LINEARSIZE, "DDSD_LINEARSIZE"); - recognizedAny |= printIfRecognized(tty, header.flags, DDSD_DEPTH, "DDSD_DEPTH"); - if (!recognizedAny) { - tty.println("(none)"); - } - tty.println("Raw SurfaceDesc flags: 0x" + Integer.toHexString(header.flags)); - tty.println("Pixel format flags:"); - recognizedAny = false; - recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_ALPHAPIXELS, "DDPF_ALPHAPIXELS"); - recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_ALPHA, "DDPF_ALPHA"); - recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_FOURCC, "DDPF_FOURCC"); - recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXED4, "DDPF_PALETTEINDEXED4"); - recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXEDTO8, "DDPF_PALETTEINDEXEDTO8"); - recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXED8, "DDPF_PALETTEINDEXED8"); - recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_RGB, "DDPF_RGB"); - recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_COMPRESSED, "DDPF_COMPRESSED"); - recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_RGBTOYUV, "DDPF_RGBTOYUV"); - recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_YUV, "DDPF_YUV"); - recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_ZBUFFER, "DDPF_ZBUFFER"); - recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXED1, "DDPF_PALETTEINDEXED1"); - recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_PALETTEINDEXED2, "DDPF_PALETTEINDEXED2"); - recognizedAny |= printIfRecognized(tty, header.pfFlags, DDPF_ZPIXELS, "DDPF_ZPIXELS"); - if (!recognizedAny) { - tty.println("(none)"); - } - tty.println("Raw pixel format flags: 0x" + Integer.toHexString(header.pfFlags)); - tty.println("Depth: " + getDepth()); - tty.println("Number of mip maps: " + getNumMipMaps()); - int fmt = getPixelFormat(); - tty.print("Pixel format: "); - switch (fmt) { - case D3DFMT_R8G8B8: tty.println("D3DFMT_R8G8B8"); break; - case D3DFMT_A8R8G8B8: tty.println("D3DFMT_A8R8G8B8"); break; - case D3DFMT_X8R8G8B8: tty.println("D3DFMT_X8R8G8B8"); break; - case D3DFMT_DXT1: tty.println("D3DFMT_DXT1"); break; - case D3DFMT_DXT2: tty.println("D3DFMT_DXT2"); break; - case D3DFMT_DXT3: tty.println("D3DFMT_DXT3"); break; - case D3DFMT_DXT4: tty.println("D3DFMT_DXT4"); break; - case D3DFMT_DXT5: tty.println("D3DFMT_DXT5"); break; - case D3DFMT_UNKNOWN: tty.println("D3DFMT_UNKNOWN"); break; - default: tty.println("(unknown pixel format " + fmt + ")"); break; - } - } - - //---------------------------------------------------------------------- - // Internals only below this point - // - - private static final int MAGIC = 0x20534444; - - static class Header { - int size; // size of the DDSURFACEDESC structure - int flags; // determines what fields are valid - int height; // height of surface to be created - int width; // width of input surface - int pitchOrLinearSize; - int backBufferCountOrDepth; - int mipMapCountOrAux; // number of mip-map levels requested (in this context) - int alphaBitDepth; // depth of alpha buffer requested - int reserved1; // reserved - int surface; // pointer to the associated surface memory - // NOTE: following two entries are from DDCOLORKEY data structure - // Are overlaid with color for empty cubemap faces (unused in this reader) - int colorSpaceLowValue; - int colorSpaceHighValue; - int destBltColorSpaceLowValue; - int destBltColorSpaceHighValue; - int srcOverlayColorSpaceLowValue; - int srcOverlayColorSpaceHighValue; - int srcBltColorSpaceLowValue; - int srcBltColorSpaceHighValue; - // NOTE: following entries are from DDPIXELFORMAT data structure - // Are overlaid with flexible vertex format description of vertex - // buffers (unused in this reader) - int pfSize; // size of DDPIXELFORMAT structure - int pfFlags; // pixel format flags - int pfFourCC; // (FOURCC code) - // Following five entries have multiple interpretations, not just - // RGBA (but that's all we support right now) - int pfRGBBitCount; // how many bits per pixel - int pfRBitMask; // mask for red bits - int pfGBitMask; // mask for green bits - int pfBBitMask; // mask for blue bits - int pfABitMask; // mask for alpha channel - int ddsCaps1; // Texture and mip-map flags - int ddsCaps2; // Advanced capabilities, not yet used - int ddsCapsReserved1; - int ddsCapsReserved2; - int textureStage; // stage in multitexture cascade - - void read(ByteBuffer buf) throws IOException { - int magic = buf.getInt(); - if (magic != MAGIC) { - throw new IOException("Incorrect magic number 0x" + - Integer.toHexString(magic) + - " (expected " + MAGIC + ")"); - } - - size = buf.getInt(); - flags = buf.getInt(); - height = buf.getInt(); - width = buf.getInt(); - pitchOrLinearSize = buf.getInt(); - backBufferCountOrDepth = buf.getInt(); - mipMapCountOrAux = buf.getInt(); - alphaBitDepth = buf.getInt(); - reserved1 = buf.getInt(); - surface = buf.getInt(); - colorSpaceLowValue = buf.getInt(); - colorSpaceHighValue = buf.getInt(); - destBltColorSpaceLowValue = buf.getInt(); - destBltColorSpaceHighValue = buf.getInt(); - srcOverlayColorSpaceLowValue = buf.getInt(); - srcOverlayColorSpaceHighValue = buf.getInt(); - srcBltColorSpaceLowValue = buf.getInt(); - srcBltColorSpaceHighValue = buf.getInt(); - pfSize = buf.getInt(); - pfFlags = buf.getInt(); - pfFourCC = buf.getInt(); - pfRGBBitCount = buf.getInt(); - pfRBitMask = buf.getInt(); - pfGBitMask = buf.getInt(); - pfBBitMask = buf.getInt(); - pfABitMask = buf.getInt(); - ddsCaps1 = buf.getInt(); - ddsCaps2 = buf.getInt(); - ddsCapsReserved1 = buf.getInt(); - ddsCapsReserved2 = buf.getInt(); - textureStage = buf.getInt(); - } - } - - // Microsoft doesn't follow their own specifications and the - // simplest conversion using the DxTex tool to e.g. a DXT3 texture - // results in an illegal .dds file without either DDSD_PITCH or - // DDSD_LINEARSIZE set in the header's flags. This code, adapted - // from the DevIL library, fixes up the header in these situations. - private void fixupHeader() { - if (isCompressed() && !isSurfaceDescFlagSet(DDSD_LINEARSIZE)) { - // Figure out how big the linear size should be - int depth = header.backBufferCountOrDepth; - if (depth == 0) { - depth = 1; - } - - int blockSize = ((getWidth() + 3)/4) * ((getHeight() + 3)/4) * ((depth + 3)/4); - switch (getCompressionFormat()) { - case D3DFMT_DXT1: blockSize *= 8; break; - default: blockSize *= 16; break; - } - - header.pitchOrLinearSize = blockSize; - header.flags |= DDSD_LINEARSIZE; - } - } - - private int mipMapWidth(int map) { - int width = getWidth(); - for (int i = 0; i < map; i++) { - width >>= 1; - } - return width; - } - - private int mipMapHeight(int map) { - int height = getHeight(); - for (int i = 0; i < map; i++) { - height >>= 1; - } - return height; - } - - private int mipMapSizeInBytes(int map) { - if (isCompressed()) { - if (!isSurfaceDescFlagSet(DDSD_LINEARSIZE)) { - throw new RuntimeException("Illegal compressed texture: DDSD_LINEARSIZE not specified in texture header"); - } - int bytes = header.pitchOrLinearSize; - for (int i = 0; i < map; i++) { - bytes >>= 2; - } - return bytes; - } else { - int width = mipMapWidth(map); - int height = mipMapHeight(map); - return width * height * (getDepth() / 8); - } - } - - private boolean printIfRecognized(PrintStream tty, int flags, int flag, String what) { - if ((flags & flag) != 0) { - tty.println(what); - return true; - } - return false; - } -} diff --git a/src/classes/com/sun/opengl/utils/SGIImage.java b/src/classes/com/sun/opengl/utils/SGIImage.java index d115604b1..f06c10b56 100755 --- a/src/classes/com/sun/opengl/utils/SGIImage.java +++ b/src/classes/com/sun/opengl/utils/SGIImage.java @@ -47,7 +47,7 @@ import com.sun.opengl.utils.*; import java.awt.image.*; import javax.swing.*; -/**

Reads SGI RGB/RGBA images.

+/**

Reads and writes SGI RGB/RGBA images.

Written from Paul @@ -104,6 +104,10 @@ public class SGIImage { // 404 bytes char DUMMY Ignored // Should be set to 0, makes the header 512 bytes. + Header() { + magic = MAGIC; + } + Header(DataInputStream in) throws IOException { magic = in.readShort(); storage = in.readByte(); @@ -159,6 +163,35 @@ public class SGIImage { return res; } + /** Writes this SGIImage to the specified file name. If + flipVertically is set, outputs the scanlines from top to bottom + rather than the default bottom to top order. */ + public void write(String filename, boolean flipVertically) throws IOException { + write(new File(filename), flipVertically); + } + + /** Writes this SGIImage to the specified file. If flipVertically is + set, outputs the scanlines from top to bottom rather than the + default bottom to top order. */ + public void write(File file, boolean flipVertically) throws IOException { + writeImage(file, data, header.xsize, header.ysize, header.zsize, flipVertically); + } + + /** Creates an SGIImage from the specified data in either RGB or + RGBA format. */ + public static SGIImage createFromData(int width, + int height, + boolean hasAlpha, + byte[] data) { + Header header = new Header(); + header.xsize = (short) width; + header.ysize = (short) height; + header.zsize = (short) (hasAlpha ? 4 : 3); + SGIImage image = new SGIImage(header); + image.data = data; + return image; + } + /** Determines from the magic number whether the given InputStream points to an SGI RGB image. The given InputStream must return true from markSupported() and support a minimum of two bytes @@ -258,6 +291,7 @@ public class SGIImage { tmpData = null; tmpRead = null; format = GL.GL_RGBA; + header.zsize = 4; } private void getRow(byte[] buf, int y, int z) { @@ -325,6 +359,250 @@ public class SGIImage { } } + private static byte imgref(byte[] i, + int x, + int y, + int z, + int xs, + int ys, + int zs) { + return i[(xs*ys*z)+(xs*y)+x]; + } + + + private void writeHeader(DataOutputStream stream, + int xsize, int ysize, int zsize, boolean rle) throws IOException { + // effects: outputs the 512-byte IRIS RGB header to STREAM, using xsize, + // ysize, and depth as the dimensions of the image. NOTE that + // the following defaults are used: + // STORAGE = 1 (storage format = RLE) + // BPC = 1 (# bytes/channel) + // DIMENSION = 3 + // PIXMIN = 0 + // PIXMAX = 255 + // IMAGENAME = <80 nulls> + // COLORMAP = 0 + // See ftp://ftp.sgi.com/pub/sgi/SGIIMAGESPEC for more details. + + // write out MAGIC, STORAGE, BPC + stream.writeShort(474); + stream.write((rle ? 1 : 0)); + stream.write(1); + + // write out DIMENSION + stream.writeShort(3); + + // write XSIZE, YSIZE, ZSIZE + stream.writeShort(xsize); + stream.writeShort(ysize); + stream.writeShort(zsize); + + // write PIXMIN, PIXMAX + stream.writeInt(0); + stream.writeInt(255); + + // write DUMMY + stream.writeInt(0); + + // write IMAGENAME + for (int i = 0; i < 80; i++) + stream.write(0); + + // write COLORMAP + stream.writeInt(0); + + // write DUMMY (404 bytes) + for (int i = 0; i < 404; i++) + stream.write(0); + } + + private void writeImage(File file, + byte[] data, + int xsize, + int ysize, + int zsize, + boolean yflip) throws IOException { + // Input data is in RGBRGBRGB or RGBARGBARGBA format; first unswizzle it + byte[] tmpData = new byte[xsize * ysize * zsize]; + int dest = 0; + for (int i = 0; i < zsize; i++) { + for (int j = i; j < (xsize * ysize * zsize); j += zsize) { + tmpData[dest++] = data[j]; + } + } + data = tmpData; + + // requires: DATA must be an array of size XSIZE * YSIZE * ZSIZE, + // indexed in the following manner: + // data[0] ...data[xsize-1] == first row of first channel + // data[xsize]...data[2*xsize-1] == second row of first channel + // ... data[(ysize - 1) * xsize]...data[(ysize * xsize) - 1] == + // last row of first channel + // Later channels follow the same format. + // *** NOTE that "first row" is defined by the BOTTOM ROW of + // the image. That is, the origin is in the lower left corner. + // effects: writes out an SGI image to FILE, RLE-compressed, INCLUDING + // header, of dimensions (xsize, ysize, zsize), and containing + // the data in DATA. If YFLIP is set, outputs the data in DATA + // in reverse order vertically (equivalent to a flip about the + // x axis). + + // Build the offset tables + int[] starttab = new int[ysize * zsize]; + int[] lengthtab = new int[ysize * zsize]; + + // Temporary buffer for holding RLE data. + // Note that this makes the assumption that RLE-compressed data will + // never exceed twice the size of the input data. + // There are surely formal proofs about how big the RLE buffer should + // be, as well as what the optimal look-ahead size is (i.e. don't switch + // copy/repeat modes for less than N repeats). However, I'm going from + // empirical evidence here; the break-even point seems to be a look- + // ahead of 3. (That is, if the three values following this one are all + // the same as the current value, switch to repeat mode.) + int lookahead = 3; + byte[] rlebuf = new byte[2 * xsize * ysize * zsize]; + + int cur_loc = 0; // current offset location. + int ptr = 0; + int total_size = 0; + int ystart = 0; + int yincr = 1; + int yend = ysize; + + if (yflip) { + ystart = ysize - 1; + yend = -1; + yincr = -1; + } + + boolean DEBUG = false; + + for (int z = 0; z < zsize; z++) { + for (int y = ystart; y != yend; y += yincr) { + // RLE-compress each row. + + int x = 0; + byte count = 0; + boolean repeat_mode = false; + boolean should_switch = false; + int start_ptr = ptr; + int num_ptr = ptr++; + byte repeat_val = 0; + + while (x < xsize) { + // see if we should switch modes + should_switch = false; + if (repeat_mode) { + if (imgref(data, x, y, z, xsize, ysize, zsize) != repeat_val) { + should_switch = true; + } + } else { + // look ahead to see if we should switch to repeat mode. + // stay within the scanline for the lookahead + if ((x + lookahead) < xsize) { + should_switch = true; + for (int i = 1; i <= lookahead; i++) { + if (DEBUG) + System.err.println("left side was " + ((int) imgref(data, x, y, z, xsize, ysize, zsize)) + + ", right side was " + (int)imgref(data, x+i, y, z, xsize, ysize, zsize)); + + if (imgref(data, x, y, z, xsize, ysize, zsize) != + imgref(data, x+i, y, z, xsize, ysize, zsize)) + should_switch = false; + } + } + } + + if (should_switch || (count == 127)) { + // update the number of elements we repeated/copied + if (x > 0) { + if (repeat_mode) + rlebuf[num_ptr] = count; + else + rlebuf[num_ptr] = (byte) (count | 0x80); + } + // perform mode switch if necessary; output repeat_val if + // switching FROM repeat mode, and set it if switching + // TO repeat mode. + if (repeat_mode) { + if (should_switch) + repeat_mode = false; + rlebuf[ptr++] = repeat_val; + } else { + if (should_switch) + repeat_mode = true; + repeat_val = imgref(data, x, y, z, xsize, ysize, zsize); + } + + if (x > 0) { + // reset the number pointer + num_ptr = ptr++; + // reset number of bytes copied + count = 0; + } + } + + // if not in repeat mode, copy element to ptr + if (!repeat_mode) { + rlebuf[ptr++] = imgref(data, x, y, z, xsize, ysize, zsize); + } + count++; + + if (x == xsize - 1) { + // Need to store the number of pixels we copied/repeated. + if (repeat_mode) { + rlebuf[num_ptr] = count; + // If we ended the row in repeat mode, store the + // repeated value + rlebuf[ptr++] = repeat_val; + } + else + rlebuf[num_ptr] = (byte) (count | 0x80); + + // output zero counter for the last value in the row + rlebuf[ptr++] = 0; + } + + x++; + } + // output this row's length into the length table + int rowlen = ptr - start_ptr; + if (yflip) + lengthtab[ysize*z+(ysize-y-1)] = rowlen; + else + lengthtab[ysize*z+y] = rowlen; + // add to the start table, and update the current offset + if (yflip) + starttab[ysize*z+(ysize-y-1)] = cur_loc; + else + starttab[ysize*z+y] = cur_loc; + cur_loc += rowlen; + } + } + + // Now we have the offset tables computed, as well as the RLE data. + // Output this information to the file. + total_size = ptr; + + if (DEBUG) + System.err.println("total_size was " + total_size); + + DataOutputStream stream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file))); + + writeHeader(stream, xsize, ysize, zsize, true); + + int SIZEOF_INT = 4; + for (int i = 0; i < (ysize * zsize); i++) + stream.writeInt(starttab[i] + 512 + (2 * ysize * zsize * SIZEOF_INT)); + for (int i = 0; i < (ysize * zsize); i++) + stream.writeInt(lengthtab[i]); + for (int i = 0; i < total_size; i++) + stream.write(rlebuf[i]); + + stream.close(); + } + private byte[] readAll(DataInputStream in) throws IOException { byte[] dest = new byte[16384]; int pos = 0; @@ -357,6 +635,8 @@ public class SGIImage { return dest; } + // Test case + /* public static void main(String[] args) { for (int i = 0; i < args.length; i++) { try { @@ -387,4 +667,5 @@ public class SGIImage { } } } + */ } diff --git a/src/classes/com/sun/opengl/utils/TGAImage.java b/src/classes/com/sun/opengl/utils/TGAImage.java index d5505afd5..c676d1a6a 100755 --- a/src/classes/com/sun/opengl/utils/TGAImage.java +++ b/src/classes/com/sun/opengl/utils/TGAImage.java @@ -40,11 +40,13 @@ package com.sun.opengl.utils; import java.io.*; +import java.nio.*; +import java.nio.channels.*; import javax.media.opengl.*; import com.sun.opengl.utils.*; /** - * Targa image reader adapted from sources of the Jimi image I/O class library. * *

@@ -70,7 +72,7 @@ import com.sun.opengl.utils.*; public class TGAImage { private Header header; private int format; - private byte[] data; + private ByteBuffer data; private TGAImage(Header header) { this.header = header; @@ -100,7 +102,7 @@ public class TGAImage { /** Field image descriptor bitfield values definitions */ public final static int ID_ATTRIBPERPIXEL = 0xF; - public final static int ID_LEFTTORIGHT = 0x10; + public final static int ID_RIGHTTOLEFT = 0x10; public final static int ID_TOPTOBOTTOM = 0x20; public final static int ID_INTERLEAVE = 0xC0; @@ -130,15 +132,14 @@ public class TGAImage { private byte pixelDepth; private byte imageDescriptor; - /** bitfields in imageDescriptor */ - private byte attribPerPixel; // how many attribute bits per pixel - private boolean leftToRight; // order of data on scan line - private boolean topToBottom; // order scan lines stored - private byte interleave; // how rows are stored in image data - private byte[] imageIDbuf; private String imageID; + // For construction from user data + Header() { + tgaType = TYPE_OLD; // dont try and get footer. + } + Header(LEDataInputStream in) throws IOException { int ret; @@ -146,7 +147,7 @@ public class TGAImage { // initial header fields idLength = in.readUnsignedByte(); - colorMapType = in.readUnsignedByte(); + colorMapType = in.readUnsignedByte(); imageType = in.readUnsignedByte(); // color map header fields @@ -162,11 +163,6 @@ public class TGAImage { pixelDepth = in.readByte(); imageDescriptor = in.readByte(); - attribPerPixel = (byte)(imageDescriptor & ID_ATTRIBPERPIXEL); - leftToRight = (imageDescriptor & ID_LEFTTORIGHT) != 0; // not used ? - topToBottom = (imageDescriptor & ID_TOPTOBOTTOM) != 0; - interleave = (byte)((imageDescriptor & ID_INTERLEAVE) >> 6); - if (idLength > 0) { imageIDbuf = new byte[idLength]; in.read(imageIDbuf, 0, idLength); @@ -195,10 +191,10 @@ public class TGAImage { public byte imageDescriptor() { return imageDescriptor; } /** bitfields in imageDescriptor */ - public byte attribPerPixel() { return attribPerPixel; } - public boolean leftToRight() { return leftToRight; } - public boolean topToBottom() { return topToBottom; } - public byte interleave() { return interleave; } + public byte attribPerPixel() { return (byte)(imageDescriptor & ID_ATTRIBPERPIXEL); } + public boolean rightToLeft() { return ((imageDescriptor & ID_RIGHTTOLEFT) != 0); } + public boolean topToBottom() { return ((imageDescriptor & ID_TOPTOBOTTOM) != 0); } + public byte interleave() { return (byte)((imageDescriptor & ID_INTERLEAVE) >> 6); } public byte[] imageIDbuf() { return imageIDbuf; } public String imageID() { return imageID; } @@ -219,6 +215,32 @@ public class TGAImage { " image descriptor: "+ imageDescriptor + (imageIDbuf == null ? "" : (" ID String: " + imageID)); } + + public int size() { return 18 + idLength; } + + // buf must be in little-endian byte order + private void write(ByteBuffer buf) { + buf.put((byte) idLength); + buf.put((byte) colorMapType); + buf.put((byte) imageType); + buf.putShort((short) firstEntryIndex); + buf.putShort((short) colorMapLength); + buf.put((byte) colorMapEntrySize); + buf.putShort((short) xOrigin); + buf.putShort((short) yOrigin); + buf.putShort((short) width); + buf.putShort((short) height); + buf.put((byte) pixelDepth); + buf.put((byte) imageDescriptor); + if (idLength > 0) { + try { + byte[] chars = imageID.getBytes("US-ASCII"); + buf.put(chars); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + } } @@ -269,7 +291,7 @@ public class TGAImage { int raw; // index through the raw input buffer int rawWidth = header.width() * (header.pixelDepth() / 8); byte[] rawBuf = new byte[rawWidth]; - data = new byte[rawWidth * header.height()]; + byte[] tmpData = new byte[rawWidth * header.height()]; if (header.pixelDepth() == 24) { format = GL.GL_BGR; @@ -281,13 +303,15 @@ public class TGAImage { for (i = 0; i < header.height(); ++i) { dIn.readFully(rawBuf, 0, rawWidth); - if (header.topToBottom) + if (header.topToBottom()) y = header.height - i - 1; // range 0 to (header.height - 1) else y = i; - System.arraycopy(rawBuf, 0, data, y * rawWidth, rawBuf.length); + System.arraycopy(rawBuf, 0, tmpData, y * rawWidth, rawBuf.length); } + + data = ByteBuffer.wrap(tmpData); } /** Returns the width of the image. */ @@ -301,7 +325,7 @@ public class TGAImage { /** Returns the raw data for this texture in the correct (bottom-to-top) order for calls to glTexImage2D. */ - public byte[] getData() { return data; } + public ByteBuffer getData() { return data; } /** Reads a Targa image from the specified file. */ public static TGAImage read(String filename) throws IOException { @@ -317,4 +341,46 @@ public class TGAImage { res.decodeImage(dIn); return res; } + + /** Writes the image in Targa format to the specified file name. */ + public void write(String filename) throws IOException { + write(new File(filename)); + } + + /** Writes the image in Targa format to the specified file. */ + public void write(File file) throws IOException { + FileOutputStream stream = new FileOutputStream(file); + FileChannel chan = stream.getChannel(); + ByteBuffer buf = ByteBuffer.allocate(header.size()); + buf.order(ByteOrder.LITTLE_ENDIAN); + header.write(buf); + buf.rewind(); + chan.write(buf); + chan.write(data); + data.rewind(); + chan.force(true); + chan.close(); + stream.close(); + } + + /** Creates a TGAImage from data supplied by the end user. Shares + data with the passed ByteBuffer. Assumes the data is already in + the correct byte order for writing to disk, i.e., BGR or + BGRA. */ + public static TGAImage createFromData(int width, + int height, + boolean hasAlpha, + boolean topToBottom, + ByteBuffer data) { + Header header = new Header(); + header.imageType = Header.UTRUECOLOR; + header.width = width; + header.height = height; + header.pixelDepth = (byte) (hasAlpha ? 32 : 24); + header.imageDescriptor = (byte) (topToBottom ? Header.ID_TOPTOBOTTOM : 0); + // Note ID not supported + TGAImage ret = new TGAImage(header); + ret.data = data; + return ret; + } } diff --git a/src/classes/com/sun/opengl/utils/Texture.java b/src/classes/com/sun/opengl/utils/Texture.java index efecdffea..924161d69 100755 --- a/src/classes/com/sun/opengl/utils/Texture.java +++ b/src/classes/com/sun/opengl/utils/Texture.java @@ -79,7 +79,7 @@ public class Texture { // For now make Texture constructor package-private to limit the // number of public APIs we commit to Texture(TextureData data) throws GLException { - GL gl = getCurrentGL(); + GL gl = GLU.getCurrentGL(); texID = createTextureID(gl); updateImage(data); @@ -88,7 +88,7 @@ public class Texture { // Constructor for use when creating e.g. cube maps, where there is // no initial texture data Texture(int target) throws GLException { - GL gl = getCurrentGL(); + GL gl = GLU.getCurrentGL(); texID = createTextureID(gl); this.target = target; } @@ -101,7 +101,7 @@ public class Texture { * OpenGL-related errors occurred */ public void enable() throws GLException { - getCurrentGL().glEnable(target); + GLU.getCurrentGL().glEnable(target); } /** @@ -112,7 +112,7 @@ public class Texture { * OpenGL-related errors occurred */ public void disable() throws GLException { - getCurrentGL().glDisable(target); + GLU.getCurrentGL().glDisable(target); } /** @@ -122,7 +122,7 @@ public class Texture { * OpenGL-related errors occurred */ public void bind() throws GLException { - getCurrentGL().glBindTexture(target, texID); + GLU.getCurrentGL().glBindTexture(target, texID); } /** @@ -132,7 +132,7 @@ public class Texture { * OpenGL-related errors occurred */ public void dispose() throws GLException { - getCurrentGL().glDeleteTextures(1, new int[] {texID}, 0); + GLU.getCurrentGL().glDeleteTextures(1, new int[] {texID}, 0); texID = 0; } @@ -241,6 +241,17 @@ public class Texture { updateImage(data, 0); } + /** + * Indicates whether this texture's texture coordinates must be + * flipped vertically in order to properly display the texture. This + * is handled automatically by {@link #getImageTexCoords} and {@link + * #getSubImageTexCoords}, but applications may generate or + * otherwise produce texture coordinates which must be corrected. + */ + public boolean getMustFlipVertically() { + return mustFlipVertically; + } + /** * Updates the content area of the specified target of this texture * using the data in the given image. In general this is intended @@ -250,7 +261,7 @@ public class Texture { * OpenGL-related errors occurred */ public void updateImage(TextureData data, int target) throws GLException { - GL gl = getCurrentGL(); + GL gl = GLU.getCurrentGL(); imgWidth = data.getWidth(); imgHeight = data.getHeight(); @@ -395,7 +406,7 @@ public class Texture { public void setTexParameteri(int parameterName, int value) { bind(); - GL gl = getCurrentGL(); + GL gl = GLU.getCurrentGL(); gl.glTexParameteri(target, parameterName, value); } @@ -412,18 +423,6 @@ public class Texture { // Internals only below this point // - /** - * Returns the current GL object. Throws GLException if no OpenGL - * context was current. - */ - private static GL getCurrentGL() throws GLException { - GLContext context = GLContext.getCurrent(); - if (context == null) { - throw new GLException("No OpenGL context current on current thread"); - } - return context.getGL(); - } - /** * Returns true if the given value is a power of two. * @@ -475,7 +474,7 @@ public class Texture { } private void updateSubImageImpl(TextureData data, int newTarget, int mipmapLevel, int x, int y) throws GLException { - GL gl = getCurrentGL(); + GL gl = GLU.getCurrentGL(); gl.glBindTexture(newTarget, texID); int width = data.getWidth(); int height = data.getHeight(); diff --git a/src/classes/com/sun/opengl/utils/TextureData.java b/src/classes/com/sun/opengl/utils/TextureData.java index a6fcd9073..6bb459432 100755 --- a/src/classes/com/sun/opengl/utils/TextureData.java +++ b/src/classes/com/sun/opengl/utils/TextureData.java @@ -316,16 +316,7 @@ public class TextureData { private void createNIOBufferFromImage(BufferedImage image, boolean flipVertically) { if (flipVertically) { - WritableRaster raster = image.getRaster(); - Object scanline1 = null; - Object scanline2 = null; - - for (int i = 0; i < image.getHeight() / 2; i++) { - scanline1 = raster.getDataElements(0, i, image.getWidth(), 1, scanline1); - scanline2 = raster.getDataElements(0, image.getHeight() - i - 1, image.getWidth(), 1, scanline2); - raster.setDataElements(0, i, image.getWidth(), 1, scanline2); - raster.setDataElements(0, image.getHeight() - i - 1, image.getWidth(), 1, scanline1); - } + TextureIO.flipImageVertically(image); } // diff --git a/src/classes/com/sun/opengl/utils/TextureIO.java b/src/classes/com/sun/opengl/utils/TextureIO.java index 07bb67ce4..d1f62cd5c 100755 --- a/src/classes/com/sun/opengl/utils/TextureIO.java +++ b/src/classes/com/sun/opengl/utils/TextureIO.java @@ -39,6 +39,7 @@ package com.sun.opengl.utils; +import java.awt.Graphics; import java.awt.image.*; import java.io.*; import java.net.*; @@ -47,25 +48,28 @@ import java.util.*; import javax.imageio.*; import javax.media.opengl.*; +import javax.media.opengl.glu.*; /**

Provides input and output facilities for both loading OpenGL textures from disk and streams as well as writing textures already in memory back to disk.

The TextureIO class supports an arbitrary number of plug-in - TextureProviders which know how to produce TextureData objects - from files, InputStreams and URLs. The TextureData class - represents the raw data of the texture before it has been - converted to an OpenGL texture object. The Texture class + readers and writers via TextureProviders and TextureWriters. + TextureProviders know how to produce TextureData objects from + files, InputStreams and URLs. TextureWriters know how to write + TextureData objects to disk in various file formats. The + TextureData class represents the raw data of the texture before it + has been converted to an OpenGL texture object. The Texture class represents the OpenGL texture object and provides easy facilities for using the texture.

-

There are several built-in TextureProviders supplied with the - TextureIO implementation. The most basic provider uses the - platform's Image I/O facilities to read in a BufferedImage and - convert it to a texture. This is the baseline provider and is - registered so that it is the last one consulted. All others are - asked first to open a given file.

+

There are several built-in TextureProviders and TextureWriters + supplied with the TextureIO implementation. The most basic + provider uses the platform's Image I/O facilities to read in a + BufferedImage and convert it to a texture. This is the baseline + provider and is registered so that it is the last one consulted. + All others are asked first to open a given file.

There are three other providers registered by default as of the time of this writing. One handles SGI RGB (".sgi", ".rgb") @@ -84,6 +88,18 @@ import javax.media.opengl.*; when probing for e.g. magic numbers at the head of the file to make sure not to disturb the state of the InputStream for downstream TextureProviders.

+ +

There are analogous TextureWriters provided for writing + textures back to disk if desired. As of this writing, there are + four TextureWriters registered by default: one for Targa files, + one for SGI RGB files, one for DirectDraw surface (.dds) files, + and one for ImageIO-supplied formats such as .jpg and .png. Some + of these writers have certain limitations such as only being able + to write out textures stored in GL_RGB or GL_RGBA format. The DDS + writer supports fetching and writing to disk of texture data in + DXTn compressed format. Whether this will occur is dependent on + whether the texture's internal format is one of the DXTn + compressed formats and whether the target file is .dds format. */ public class TextureIO { @@ -396,10 +412,11 @@ public class TextureIO { * @param data the texture data to turn into an OpenGL texture * @throws GLException if no OpenGL context is current or if an * OpenGL error occurred + * @throws IllegalArgumentException if the passed TextureData was null */ - public static Texture newTexture(TextureData data) throws GLException { + public static Texture newTexture(TextureData data) throws GLException, IllegalArgumentException { if (data == null) { - return null; + throw new IllegalArgumentException("Null TextureData"); } return new Texture(data); } @@ -513,6 +530,120 @@ public class TextureIO { return new Texture(target); } + /** + * Writes the given texture to a file. The type of the file is + * inferred from its suffix. An OpenGL context must be current in + * order to fetch the texture data back from the OpenGL pipeline. + * This method causes the specified Texture to be bound to the + * GL_TEXTURE_2D state. If no suitable writer for the requested file + * format was found, throws an IOException.

+ * + * Reasonable attempts are made to produce good results in the + * resulting images. The Targa, SGI and ImageIO writers produce + * results in the correct vertical orientation for those file + * formats. The DDS writer performs no vertical flip of the data, + * even in uncompressed mode. (It is impossible to perform such a + * vertical flip with compressed data.) Applications should keep + * this in mind when using this routine to save textures to disk for + * later re-loading.

+ * + * Any mipmaps for the specified texture are currently discarded + * when it is written to disk, regardless of whether the underlying + * file format supports multiple mipmaps in a given file. + * + * @throws IOException if an error occurred during writing or no + * suitable writer was found + * @throws GLException if no OpenGL context was current or an + * OpenGL-related error occurred + */ + public static void write(Texture texture, File file) throws IOException, GLException { + if (texture.getTarget() != GL.GL_TEXTURE_2D) { + throw new GLException("Only GL_TEXTURE_2D textures are supported"); + } + + // First fetch the texture data + GL gl = GLU.getCurrentGL(); + + texture.bind(); + int internalFormat = glGetTexLevelParameteri(GL.GL_TEXTURE_2D, 0, GL.GL_TEXTURE_INTERNAL_FORMAT); + int width = glGetTexLevelParameteri(GL.GL_TEXTURE_2D, 0, GL.GL_TEXTURE_WIDTH); + int height = glGetTexLevelParameteri(GL.GL_TEXTURE_2D, 0, GL.GL_TEXTURE_HEIGHT); + int border = glGetTexLevelParameteri(GL.GL_TEXTURE_2D, 0, GL.GL_TEXTURE_BORDER); + TextureData data = null; + if (internalFormat == GL.GL_COMPRESSED_RGB_S3TC_DXT1_EXT || + internalFormat == GL.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT || + internalFormat == GL.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT || + internalFormat == GL.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT) { + // Fetch using glGetCompressedTexImage + int size = glGetTexLevelParameteri(GL.GL_TEXTURE_2D, 0, GL.GL_TEXTURE_COMPRESSED_IMAGE_SIZE); + ByteBuffer res = ByteBuffer.allocate(size); + gl.glGetCompressedTexImage(GL.GL_TEXTURE_2D, 0, res); + data = new TextureData(internalFormat, width, height, border, internalFormat, GL.GL_UNSIGNED_BYTE, + false, true, true, res, null); + } else { + int bytesPerPixel = 0; + int fetchedFormat = 0; + switch (internalFormat) { + case GL.GL_RGB: + case GL.GL_BGR: + case GL.GL_RGB8: + bytesPerPixel = 3; + fetchedFormat = GL.GL_RGB; + break; + case GL.GL_RGBA: + case GL.GL_BGRA: + case GL.GL_ABGR_EXT: + case GL.GL_RGBA8: + bytesPerPixel = 4; + fetchedFormat = GL.GL_RGBA; + break; + default: + throw new IOException("Unsupported texture internal format 0x" + Integer.toHexString(internalFormat)); + } + + // Fetch using glGetTexImage + int packAlignment = glGetInteger(GL.GL_PACK_ALIGNMENT); + int packRowLength = glGetInteger(GL.GL_PACK_ROW_LENGTH); + int packSkipRows = glGetInteger(GL.GL_PACK_SKIP_ROWS); + int packSkipPixels = glGetInteger(GL.GL_PACK_SKIP_PIXELS); + int packSwapBytes = glGetInteger(GL.GL_PACK_SWAP_BYTES); + + gl.glPixelStorei(GL.GL_PACK_ALIGNMENT, 1); + gl.glPixelStorei(GL.GL_PACK_ROW_LENGTH, 0); + gl.glPixelStorei(GL.GL_PACK_SKIP_ROWS, 0); + gl.glPixelStorei(GL.GL_PACK_SKIP_PIXELS, 0); + gl.glPixelStorei(GL.GL_PACK_SWAP_BYTES, 0); + + ByteBuffer res = ByteBuffer.allocate((width + (2 * border)) * + (height + (2 * border)) * + bytesPerPixel); + System.err.println("Allocated buffer of size " + res.remaining() + " for fetched image (" + + ((fetchedFormat == GL.GL_RGB) ? "GL_RGB" : "GL_RGBA") + ")"); + gl.glGetTexImage(GL.GL_TEXTURE_2D, 0, fetchedFormat, GL.GL_UNSIGNED_BYTE, res); + + gl.glPixelStorei(GL.GL_PACK_ALIGNMENT, packAlignment); + gl.glPixelStorei(GL.GL_PACK_ROW_LENGTH, packRowLength); + gl.glPixelStorei(GL.GL_PACK_SKIP_ROWS, packSkipRows); + gl.glPixelStorei(GL.GL_PACK_SKIP_PIXELS, packSkipPixels); + gl.glPixelStorei(GL.GL_PACK_SWAP_BYTES, packSwapBytes); + + data = new TextureData(internalFormat, width, height, border, fetchedFormat, GL.GL_UNSIGNED_BYTE, + false, false, false, res, null); + + System.out.println("data.getPixelFormat() = " + + ((data.getPixelFormat() == GL.GL_RGB) ? "GL_RGB" : "GL_RGBA")); + } + + for (Iterator iter = textureWriters.iterator(); iter.hasNext(); ) { + TextureWriter writer = (TextureWriter) iter.next(); + if (writer.write(file, data)) { + return; + } + } + + throw new IOException("No suitable texture writer found"); + } + //---------------------------------------------------------------------- // Helper function for above TextureProviders /** @@ -540,8 +671,25 @@ public class TextureIO { return toLowerCase(filename.substring(lastDot + 1)); } - // FIXME: add texture writing capabilities - // public void writeTextureToFile(Texture texture, File file, boolean saveUncompressed) throws IOException, GLException; + //---------------------------------------------------------------------- + // Helper function which may be more generally useful + // + + /** Flips the supplied BufferedImage vertically. This is often a + necessary conversion step to display a Java2D image correctly + with OpenGL and vice versa. */ + public static void flipImageVertically(BufferedImage image) { + WritableRaster raster = image.getRaster(); + Object scanline1 = null; + Object scanline2 = null; + + for (int i = 0; i < image.getHeight() / 2; i++) { + scanline1 = raster.getDataElements(0, i, image.getWidth(), 1, scanline1); + scanline2 = raster.getDataElements(0, image.getHeight() - i - 1, image.getWidth(), 1, scanline2); + raster.setDataElements(0, i, image.getWidth(), 1, scanline2); + raster.setDataElements(0, image.getHeight() - i - 1, image.getWidth(), 1, scanline1); + } + } //---------------------------------------------------------------------- // SPI support @@ -556,11 +704,21 @@ public class TextureIO { textureProviders.add(0, provider); } + /** Adds a TextureWriter to support writing of a new file + format. */ + public static void addTextureWriter(TextureWriter writer) { + // Must always add at the front so the ImageIO writer is last, + // so we don't accidentally use it instead of a user's possibly + // more optimal writer + textureWriters.add(0, writer); + } + //---------------------------------------------------------------------- // Internals only below this point // private static List/**/ textureProviders = new ArrayList/**/(); + private static List/**/ textureWriters = new ArrayList/**/(); static { // ImageIO provider, the fall-back, must be the first one added @@ -570,6 +728,14 @@ public class TextureIO { addTextureProvider(new DDSTextureProvider()); addTextureProvider(new SGITextureProvider()); addTextureProvider(new TGATextureProvider()); + + // ImageIO writer, the fall-back, must be the first one added + textureWriters.add(new IIOTextureWriter()); + + // Other special-case writers + addTextureWriter(new DDSTextureWriter()); + addTextureWriter(new SGITextureWriter()); + addTextureWriter(new TGATextureWriter()); } // Implementation methods @@ -595,7 +761,8 @@ public class TextureIO { return data; } } - return null; + + throw new IOException("No suitable reader for given file"); } private static TextureData newTextureDataImpl(InputStream stream, @@ -626,7 +793,7 @@ public class TextureIO { } } - return null; + throw new IOException("No suitable reader for given stream"); } private static TextureData newTextureDataImpl(URL url, @@ -652,7 +819,7 @@ public class TextureIO { } } - return null; + throw new IOException("No suitable reader for given URL"); } private static TextureData newTextureDataImpl(BufferedImage image, @@ -713,12 +880,11 @@ public class TextureIO { String fileSuffix) throws IOException { if (DDS.equals(fileSuffix) || DDS.equals(getFileSuffix(file))) { - final DDSReader reader = new DDSReader(); - reader.loadFile(file); - DDSReader.ImageInfo info = reader.getMipMap(0); + final DDSImage image = DDSImage.read(file); + DDSImage.ImageInfo info = image.getMipMap(0); if (pixelFormat == 0) { - switch (reader.getPixelFormat()) { - case DDSReader.D3DFMT_R8G8B8: + switch (image.getPixelFormat()) { + case DDSImage.D3DFMT_R8G8B8: pixelFormat = GL.GL_RGB; break; default: @@ -728,23 +894,23 @@ public class TextureIO { } if (info.isCompressed()) { switch (info.getCompressionFormat()) { - case DDSReader.D3DFMT_DXT1: + case DDSImage.D3DFMT_DXT1: internalFormat = GL.GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; - case DDSReader.D3DFMT_DXT3: + case DDSImage.D3DFMT_DXT3: internalFormat = GL.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; - case DDSReader.D3DFMT_DXT5: + case DDSImage.D3DFMT_DXT5: internalFormat = GL.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; default: throw new RuntimeException("Unsupported DDS compression format \"" + - DDSReader.getCompressionFormatName(info.getCompressionFormat()) + "\""); + DDSImage.getCompressionFormatName(info.getCompressionFormat()) + "\""); } } if (internalFormat == 0) { - switch (reader.getPixelFormat()) { - case DDSReader.D3DFMT_R8G8B8: + switch (image.getPixelFormat()) { + case DDSImage.D3DFMT_R8G8B8: pixelFormat = GL.GL_RGB; break; default: @@ -754,14 +920,14 @@ public class TextureIO { } TextureData.Flusher flusher = new TextureData.Flusher() { public void flush() { - reader.close(); + image.close(); } }; TextureData data; - if (mipmap && reader.getNumMipMaps() > 0) { - Buffer[] mipmapData = new Buffer[reader.getNumMipMaps()]; - for (int i = 0; i < reader.getNumMipMaps(); i++) { - mipmapData[i] = reader.getMipMap(i).getData(); + if (mipmap && image.getNumMipMaps() > 0) { + Buffer[] mipmapData = new Buffer[image.getNumMipMaps()]; + for (int i = 0; i < image.getNumMipMaps(); i++) { + mipmapData[i] = image.getMipMap(i).getData(); } data = new TextureData(internalFormat, info.getWidth(), @@ -909,7 +1075,7 @@ public class TextureIO { mipmap, false, false, - ByteBuffer.wrap(image.getData()), + image.getData(), null); } @@ -917,10 +1083,230 @@ public class TextureIO { } } + //---------------------------------------------------------------------- + // ImageIO texture writer + // + static class IIOTextureWriter implements TextureWriter { + public boolean write(File file, + TextureData data) throws IOException { + int pixelFormat = data.getPixelFormat(); + int pixelType = data.getPixelType(); + if ((pixelFormat == GL.GL_RGB || + pixelFormat == GL.GL_RGBA) && + (pixelType == GL.GL_BYTE || + pixelType == GL.GL_UNSIGNED_BYTE)) { + // Convert TextureData to appropriate BufferedImage + // FIXME: almost certainly not obeying correct pixel order + BufferedImage image = new BufferedImage(data.getWidth(), data.getHeight(), + (pixelFormat == GL.GL_RGB) ? + BufferedImage.TYPE_3BYTE_BGR : + BufferedImage.TYPE_4BYTE_ABGR); + byte[] imageData = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); + ByteBuffer buf = (ByteBuffer) data.getBuffer(); + if (buf == null) { + buf = (ByteBuffer) data.getMipmapData()[0]; + } + buf.rewind(); + buf.get(imageData); + buf.rewind(); + + // Swizzle image components to be correct + if (pixelFormat == GL.GL_RGB) { + for (int i = 0; i < imageData.length; i += 3) { + byte red = imageData[i + 0]; + byte blue = imageData[i + 2]; + imageData[i + 0] = blue; + imageData[i + 2] = red; + } + } else { + for (int i = 0; i < imageData.length; i += 4) { + byte red = imageData[i + 0]; + byte green = imageData[i + 1]; + byte blue = imageData[i + 2]; + byte alpha = imageData[i + 3]; + imageData[i + 0] = alpha; + imageData[i + 1] = blue; + imageData[i + 2] = green; + imageData[i + 3] = red; + } + } + + // Flip image vertically for the user's convenience + flipImageVertically(image); + + // Happened to notice that writing RGBA images to JPEGS is broken + if (JPG.equals(getFileSuffix(file)) && + image.getType() == BufferedImage.TYPE_4BYTE_ABGR) { + BufferedImage tmpImage = new BufferedImage(image.getWidth(), image.getHeight(), + BufferedImage.TYPE_3BYTE_BGR); + Graphics g = tmpImage.getGraphics(); + g.drawImage(image, 0, 0, null); + g.dispose(); + image = tmpImage; + } + + return ImageIO.write(image, getFileSuffix(file), file); + } + + throw new IOException("ImageIO writer doesn't support this pixel format / type (only GL_RGB/A + bytes)"); + } + } + + //---------------------------------------------------------------------- + // DDS texture writer + // + static class DDSTextureWriter implements TextureWriter { + public boolean write(File file, + TextureData data) throws IOException { + if (DDS.equals(getFileSuffix(file))) { + // See whether the DDS writer can handle this TextureData + int pixelFormat = data.getPixelFormat(); + int pixelType = data.getPixelType(); + if (pixelType != GL.GL_BYTE && + pixelType != GL.GL_UNSIGNED_BYTE) { + throw new IOException("DDS writer only supports byte / unsigned byte textures"); + } + + int d3dFormat = 0; + // FIXME: some of these are probably not completely correct and would require swizzling + switch (pixelFormat) { + case GL.GL_RGB: d3dFormat = DDSImage.D3DFMT_R8G8B8; break; + case GL.GL_RGBA: d3dFormat = DDSImage.D3DFMT_A8R8G8B8; break; + case GL.GL_COMPRESSED_RGB_S3TC_DXT1_EXT: d3dFormat = DDSImage.D3DFMT_DXT1; break; + case GL.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: throw new IOException("RGBA DXT1 not yet supported"); + case GL.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: d3dFormat = DDSImage.D3DFMT_DXT3; break; + case GL.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: d3dFormat = DDSImage.D3DFMT_DXT5; break; + default: throw new IOException("Unsupported pixel format 0x" + Integer.toHexString(pixelFormat) + " by DDS writer"); + } + + ByteBuffer[] mipmaps = null; + if (data.getMipmapData() != null) { + mipmaps = new ByteBuffer[data.getMipmapData().length]; + for (int i = 0; i < mipmaps.length; i++) { + mipmaps[i] = (ByteBuffer) data.getMipmapData()[i]; + } + } else { + mipmaps = new ByteBuffer[] { (ByteBuffer) data.getBuffer() }; + } + + DDSImage image = DDSImage.createFromData(d3dFormat, + data.getWidth(), + data.getHeight(), + mipmaps); + image.write(file); + return true; + } + + return false; + } + } + + //---------------------------------------------------------------------- + // SGI (rgb) texture writer + // + static class SGITextureWriter implements TextureWriter { + public boolean write(File file, + TextureData data) throws IOException { + String fileSuffix = getFileSuffix(file); + if (SGI.equals(fileSuffix) || + SGI_RGB.equals(fileSuffix)) { + // See whether the SGI writer can handle this TextureData + int pixelFormat = data.getPixelFormat(); + int pixelType = data.getPixelType(); + if ((pixelFormat == GL.GL_RGB || + pixelFormat == GL.GL_RGBA) && + (pixelType == GL.GL_BYTE || + pixelType == GL.GL_UNSIGNED_BYTE)) { + ByteBuffer buf = ((data.getBuffer() != null) ? + (ByteBuffer) data.getBuffer() : + (ByteBuffer) data.getMipmapData()[0]); + byte[] bytes; + if (buf.hasArray()) { + bytes = buf.array(); + } else { + buf.rewind(); + bytes = new byte[buf.remaining()]; + buf.get(bytes); + buf.rewind(); + } + + SGIImage image = SGIImage.createFromData(data.getWidth(), + data.getHeight(), + (pixelFormat == GL.GL_RGBA), + bytes); + image.write(file, false); + return true; + } + + throw new IOException("SGI writer doesn't support this pixel format / type (only GL_RGB/A + bytes)"); + } + + return false; + } + } + + //---------------------------------------------------------------------- + // TGA (Targa) texture writer + + static class TGATextureWriter implements TextureWriter { + public boolean write(File file, + TextureData data) throws IOException { + if (TGA.equals(getFileSuffix(file))) { + // See whether the TGA writer can handle this TextureData + int pixelFormat = data.getPixelFormat(); + int pixelType = data.getPixelType(); + if ((pixelFormat == GL.GL_RGB || + pixelFormat == GL.GL_RGBA) && + (pixelType == GL.GL_BYTE || + pixelType == GL.GL_UNSIGNED_BYTE)) { + ByteBuffer buf = ((data.getBuffer() != null) ? + (ByteBuffer) data.getBuffer() : + (ByteBuffer) data.getMipmapData()[0]); + // Must reverse order of red and blue channels to get correct results + int skip = ((pixelFormat == GL.GL_RGB) ? 3 : 4); + for (int i = 0; i < buf.remaining(); i += skip) { + byte red = buf.get(i + 0); + byte blue = buf.get(i + 2); + buf.put(i + 0, blue); + buf.put(i + 2, red); + } + + TGAImage image = TGAImage.createFromData(data.getWidth(), + data.getHeight(), + (pixelFormat == GL.GL_RGBA), + false, + ((data.getBuffer() != null) ? + (ByteBuffer) data.getBuffer() : + (ByteBuffer) data.getMipmapData()[0])); + image.write(file); + return true; + } + + throw new IOException("TGA writer doesn't support this pixel format / type (only GL_RGB/A + bytes)"); + } + + return false; + } + } + //---------------------------------------------------------------------- // Helper routines // + private static int glGetInteger(int pname) { + int[] tmp = new int[1]; + GL gl = GLU.getCurrentGL(); + gl.glGetIntegerv(pname, tmp, 0); + return tmp[0]; + } + + private static int glGetTexLevelParameteri(int target, int level, int pname) { + int[] tmp = new int[1]; + GL gl = GLU.getCurrentGL(); + gl.glGetTexLevelParameteriv(target, 0, pname, tmp, 0); + return tmp[0]; + } + private static String toLowerCase(String arg) { if (arg == null) { return null; diff --git a/src/classes/com/sun/opengl/utils/TextureWriter.java b/src/classes/com/sun/opengl/utils/TextureWriter.java new file mode 100755 index 000000000..38b976b9d --- /dev/null +++ b/src/classes/com/sun/opengl/utils/TextureWriter.java @@ -0,0 +1,55 @@ +/* + * 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. + * + * Sun gratefully acknowledges that this software was originally authored + * and developed by Kenneth Bradley Russell and Christopher John Kline. + */ + +package com.sun.opengl.utils; + +import java.io.*; + +/** Plug-in interface to TextureIO to support writing OpenGL textures + to new file formats. */ + +public interface TextureWriter { + /** Writes the given TextureData to the passed file. Returns true if + this TextureWriter successfully handled the writing of the file, + otherwise false. May throw IOException if either this writer did + not support certain parameters of the TextureData or if an I/O + error occurred. */ + public boolean write(File file, + TextureData data) throws IOException; +} -- cgit v1.2.3