/* * 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 Vogster; import java.io.*; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; /** * 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 public static final int DDSCAPS_COMPLEX = 0x00000008; // Selected bits in DDS extended capabilities flags public static final int DDSCAPS2_CUBEMAP = 0x00000200; public static final int DDSCAPS2_CUBEMAP_POSITIVEX = 0x00000400; public static final int DDSCAPS2_CUBEMAP_NEGATIVEX = 0x00000800; public static final int DDSCAPS2_CUBEMAP_POSITIVEY = 0x00001000; public static final int DDSCAPS2_CUBEMAP_NEGATIVEY = 0x00002000; public static final int DDSCAPS2_CUBEMAP_POSITIVEZ = 0x00004000; public static final int DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x00008000; // 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. * @param filename File name * @return DDS image object * @throws java.io.IOException I/O exception */ 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. * @param file File object * @return DDS image object * @throws java.io.IOException I/O exception */ public static DDSImage read( File file ) throws IOException { DDSImage image = new DDSImage(); image.readFromFile( file ); return image; } /** * Reads a DirectDraw surface from the specified ByteBuffer, returning * the resulting DDSImage. * @param buf Input data * @return DDS image object * @throws java.io.IOException I/O exception */ public static DDSImage read( ByteBuffer buf ) throws IOException { DDSImage image = new DDSImage(); image.readFromBuffer( buf ); 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 * @return DDS image object */ 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; } /** * Determines from the magic number whether the given InputStream * points to a DDS image. The given InputStream must return true * from markSupported() and support a minimum of four bytes of * read-ahead. * @param in Stream to check * @return true if input stream is DDS image or false otherwise * @throws java.io.IOException I/O exception */ public static boolean isDDSImage( InputStream in ) throws IOException { if( !( in instanceof BufferedInputStream ) ) { in = new BufferedInputStream( in ); } if( !in.markSupported() ) { throw new IOException( "Can not test non-destructively whether given InputStream is a DDS image" ); } in.mark( 4 ); int magic = 0; for( int i = 0; i < 4; i++ ) { int tmp = in.read(); if( tmp < 0 ) { in.reset(); return false; } magic = ( ( magic >>> 8 ) | ( tmp << 24 ) ); } in.reset(); return ( magic == MAGIC ); } /** * Writes this DDSImage to the specified file name. * @param filename File name to write in * @throws java.io.IOException I/O exception */ public void write( String filename ) throws IOException { write( new File( filename ) ); } /** * Writes this DDSImage to the specified file name. * @param file File object to write in * @throws java.io.IOException I/O exception */ 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_*) * @param flag DDSD_* flags set to test * @return true if flag present or false otherwise */ 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 cubemap * @return true if cubemap or false otherwise */ public boolean isCubemap() { return ( ( header.ddsCaps1 & DDSCAPS_COMPLEX ) != 0 ) && ( ( header.ddsCaps2 & DDSCAPS2_CUBEMAP ) != 0 ); } /** * Indicates whethe this cubemap side present * @param side Side to test * @return true if side present or false otherwise */ public boolean isCubemapSidePresent( int side) { return isCubemap() && ( header.ddsCaps2 & side ) != 0; } /** * 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) * @param map Mipmap index * @return Image object */ public ImageInfo getMipMap( int map ) { return getMipMap( 0, map ); } /** * Gets the ith mipmap data (0..getNumMipMaps() - 1) * @param side Cubemap side or 0 for 2D texture * @param map Mipmap index * @return Image object */ public ImageInfo getMipMap( int side, int map ) { if ( !isCubemap() && ( side != 0 ) ) { throw new RuntimeException( "Illegal side for 2D texture: " + side ); } if ( isCubemap() && !isCubemapSidePresent( side ) ) { throw new RuntimeException( "Illegal side, side not present: " + side ); } 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(); if ( isCubemap() ) { seek += sideShiftInBytes( side ); } 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. * @return Mipmap image objects set */ public ImageInfo[] getAllMipMaps() { return getAllMipMaps( 0 ); } /** * Returns an array of ImageInfos corresponding to all mipmap * levels of this DDS file. * @param side Cubemap side or 0 for 2D texture * @return Mipmap image objects set */ public ImageInfo[] getAllMipMaps( int side ) { int numLevels = getNumMipMaps(); if( numLevels == 0 ) { numLevels = 1; } ImageInfo[] result = new ImageInfo[numLevels]; for( int i = 0; i < numLevels; i++ ) { result[i] = getMipMap( side, i ); } return result; } /** * Converts e.g. DXT1 compression format constant (see {@link * #getCompressionFormat}) into "DXT1". * @param compressionFormat Compression format constant * @return String format code */ 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; 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 = 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 int size() { return 124; } private static int pfSize() { return 32; } private static int writtenSize() { return 128; } } private DDSImage() { } private void readFromFile( File file ) throws IOException { fis = new FileInputStream( file ); chan = fis.getChannel(); ByteBuffer buf = chan.map( FileChannel.MapMode.READ_ONLY, 0, ( int ) file.length() ); readFromBuffer( buf ); } private void readFromBuffer( ByteBuffer buf ) throws IOException { this.buf = buf; 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( ByteBuffer mipLevel : mipmapData ) { buf.put( mipLevel ); } 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; } header.pitchOrLinearSize = computeCompressedBlockSize( getWidth(), getHeight(), depth, getCompressionFormat() ); 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; } if( width <= 0 ) { return 1; } return width; } private int mipMapHeight( int map ) { int height = getHeight(); for( int i = 0; i < map; i++ ) { height >>= 1; } if( height <= 0 ) { return 1; } return height; } private int mipMapSizeInBytes( int map ) { int width = mipMapWidth( map ); int height = mipMapHeight( map ); if( isCompressed() ) { int blockSize = ( getCompressionFormat() == D3DFMT_DXT1 ? 8 : 16 ); return ( ( width + 3 ) / 4 ) * ( ( height + 3 ) / 4 ) * blockSize; } else { return width * height * ( getDepth() / 8 ); } } private int sideSizeInBytes() { int numLevels = getNumMipMaps(); if( numLevels == 0 ) { numLevels = 1; } int size = 0; for( int i = 0; i < numLevels; i++ ) { size += mipMapSizeInBytes( i ); } return size; } private int sideShiftInBytes( int side ) { int[] sides = { DDSCAPS2_CUBEMAP_POSITIVEX, DDSCAPS2_CUBEMAP_NEGATIVEX, DDSCAPS2_CUBEMAP_POSITIVEY, DDSCAPS2_CUBEMAP_NEGATIVEY, DDSCAPS2_CUBEMAP_POSITIVEZ, DDSCAPS2_CUBEMAP_NEGATIVEZ }; int shift = 0; int sideSize = sideSizeInBytes(); for ( int temp : sides ) { if ( ( temp & side ) != 0 ) { return shift; } shift += sideSize; } throw new RuntimeException( "Illegal side: " + side ); } private boolean printIfRecognized( PrintStream tty, int flags, int flag, String what ) { if( ( flags & flag ) != 0 ) { tty.println( what ); return true; } return false; } }