/*
* 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;
}
}