Paranoia2/utils/common/ddstex.cpp

895 lines
22 KiB
C++

/*
image_dds.cpp - image dds encoder. Based on original code from Doom3: BFG Edition
Copyright (C) 2016 Uncle Mike
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#define STB_DXT_IMPLEMENTATION
#include "conprint.h"
#include <windows.h>
#include <math.h>
#include <assert.h>
#include <io.h>
#include "cmdlib.h"
#include "stringlib.h"
#include "imagelib.h"
#include "stringlib.h"
#include "filesystem.h"
#include "ddstex.h"
#include "squish.h"
#include "mathlib.h"
#define BLOCK_SIZE ( 4 * 4 ) // DXT block size quad 4x4 pixels
#define RGB_TO_YCOCG_Y( r, g, b ) ((( r + (g<<1) + b ) + 2 ) >> 2 )
#define RGB_TO_YCOCG_CO( r, g, b ) ((( (r<<1)-(b<<1) ) + 2 ) >> 2 )
#define RGB_TO_YCOCG_CG( r, g, b ) ((( -r + (g<<1) - b ) + 2 ) >> 2 )
typedef enum
{
PF_DXT1 = 0, // without alpha
PF_DXT3, // DXT3
PF_DXT5, // DXT5 as default
PF_DXT5_ALPHA, // DXT5 with alpha
PF_DXT5_SDF_ALPHA, // DXT5 with SDF alpha
PF_DXT5_YCoCg,
PF_DXT5_NORM_BASE, // same as original
PF_ATI2_NORM_PARABOLOID,
} saveformat_t;
/*
========================
ExtractBlock
params: inPtr - input image, 4 bytes per pixel
paramO: colorBlock - 4*4 output tile, 4 bytes per pixel
========================
*/
inline static void ExtractBlock( const byte *inPtr, int width, byte *colorBlock )
{
for( int i = 0; i < 4; i++ )
{
memcpy( &colorBlock[i*BLOCK_SIZE], inPtr, BLOCK_SIZE );
inPtr += width * 4;
}
}
static size_t Image_DXTGetBlockSize( int format )
{
if( format == PF_DXT1 )
return 8;
return 16;
}
/*
========================
ScaleYCoCg
params: colorBlock - 16 pixel block for which find color indexes
========================
*/
inline static void ScaleYCoCg( byte *colorBlock )
{
for( int i = 0; i < 16; i++ )
{
int r = colorBlock[i*4+0];
int g = colorBlock[i*4+1];
int b = colorBlock[i*4+2];
int a = colorBlock[i*4+3];
const int Co = r - b;
const int t = b + Co / 2;
const int Cg = g - t;
const int Y = t + Cg / 2;
// Just saturate the chroma here (we loose out of one bit in each channel)
// this just means that we won't have as high dynamic range. Perhaps a better option
// is to loose the least significant bit instead?
colorBlock[i*4+0] = bound( 0, Co + 128, 255 );
colorBlock[i*4+1] = bound( 0, Cg + 128, 255 );
colorBlock[i*4+2] = 0; // trying to use alpha ?
colorBlock[i*4+3] = Y;
}
}
/*
========================
NormalizeBlock
params: colorBlock - 16 pixel block for which find color indexes
========================
*/
inline static void NormalizeBlock( byte *colorBlock, int format )
{
float pX, pY, a, t;
float discriminant;
vec3_t normal;
for( int i = 0; i < 16; i++ )
{
int x = colorBlock[i*4+0];
int y = colorBlock[i*4+1];
int z = colorBlock[i*4+2];
normal[0] = (x * (1.0f/127.0f) - 1.0f);
normal[1] = (y * (1.0f/127.0f) - 1.0f);
normal[2] = (z * (1.0f/127.0f) - 1.0f);
if( VectorNormalize( normal ) == 0.0f )
VectorSet( normal, 0.5f, 0.5f, 1.0f );
switch( format )
{
case PF_ATI2_NORM_PARABOLOID:
pX = normal[0];
pY = normal[1];
a = (pX * pX) + (pY * pY);
discriminant = normal[2] * normal[2] - 4.0f * a * -1.0f;
t = ( -normal[2] + sqrtf( discriminant )) / ( 2.0f * a );
pX *= t;
pY *= t;
// store normal as two channels (RG)
colorBlock[i*4+0] = 0;
colorBlock[i*4+1] = (byte)(128 + 127 * pY);
colorBlock[i*4+2] = 0;
colorBlock[i*4+3] = (byte)(128 + 127 * pX);
break;
default:
// store normal as normal image (XYZ)
colorBlock[i*4+0] = (byte)(128 + 127 * normal[0]);
colorBlock[i*4+1] = (byte)(128 + 127 * normal[1]);
colorBlock[i*4+2] = (byte)(128 + 127 * normal[2]);
colorBlock[i*4+3] = 0xFF;
break;
}
}
}
/*
========================
CompressYCoCgDXT5HQ
params: inBuf - image to compress
paramO: outBuf - result of compression
params: width - width of image
params: height - height of image
========================
*/
inline static void CompressRGBABufferToDXT( const byte *inBuf, vfile_t *f, int width, int height, int format, bool estimate )
{
ALIGN16 byte block[64];
ALIGN16 byte outBlock[16];
float metricsRGB[3] = { 0.2126f, 0.7152f, 0.0722f };
const char *typeString = NULL;
float *metrics = NULL;
double start, end;
int flags = 0;
char str[64];
start = I_FloatTime();
for( int j = 0; j < height; j += 4, inBuf += width * BLOCK_SIZE )
{
for( int i = 0; i < width; i += 4 )
{
ExtractBlock( inBuf + i * 4, width, block );
if( format == PF_DXT5_YCoCg )
{
SetBits( flags, squish::kDxt5 | squish::kColourIterativeClusterFit );
typeString = "DXT5 YCoCg";
ScaleYCoCg( block );
}
else if( format >= PF_DXT5_NORM_BASE && format <= PF_ATI2_NORM_PARABOLOID )
{
switch( format )
{
case PF_DXT5_NORM_BASE:
typeString = "DXT5 NormXYZ Base";
SetBits( flags, squish::kDxt5 | squish::kColourIterativeClusterFit );
break;
case PF_ATI2_NORM_PARABOLOID:
typeString = "ATI2 NormAG Paraboloid";
SetBits( flags, squish::kAti2 );
break;
}
NormalizeBlock( block, format );
}
else if( format == PF_DXT5 )
{
SetBits( flags, squish::kDxt5 | squish::kColourIterativeClusterFit );
typeString = "DXT5 RGB";
}
else if( format == PF_DXT5_ALPHA || format == PF_DXT5_SDF_ALPHA )
{
SetBits( flags, squish::kDxt5 | squish::kColourIterativeClusterFit | squish::kWeightColourByAlpha );
typeString = "DXT5 RGBA";
}
else if( format == PF_DXT1 )
{
SetBits( flags, squish::kDxt1 | squish::kColourIterativeClusterFit );
typeString = "DXT1 RGB";
}
squish::Compress( block, outBlock, flags, metrics );
VFS_Write( f, outBlock, Image_DXTGetBlockSize( format ));
if( estimate )
{
Sys_IgnoreLog( true );
Msg( "\rcompress %s: %2d%%", typeString, ( j * width + i ) * 100 / ( width * height ) );
Sys_IgnoreLog( false );
}
}
}
end = I_FloatTime();
Q_timestring((int)(end - start), str );
if( estimate )
{
Msg( "\r" );
Msg( "compress %s: 100%%. %s elapsed\n", typeString, str );
}
}
/*
========================================================================
.DDS image format
========================================================================
*/
#define DDSHEADER ((' '<<24)+('S'<<16)+('D'<<8)+'D') // little-endian "DDS "
// various four-cc types
#define TYPE_DXT1 (('1'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian "DXT1"
#define TYPE_DXT3 (('3'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian "DXT3"
#define TYPE_DXT5 (('5'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian "DXT5"
#define TYPE_ATI2 (('2'<<24)+('I'<<16)+('T'<<8)+'A') // little-endian "ATI2"
// dwFlags1
#define DDS_CAPS 0x00000001L
#define DDS_HEIGHT 0x00000002L
#define DDS_WIDTH 0x00000004L
#define DDS_PITCH 0x00000008L
#define DDS_PIXELFORMAT 0x00001000L
#define DDS_MIPMAPCOUNT 0x00020000L
#define DDS_LINEARSIZE 0x00080000L
#define DDS_DEPTH 0x00800000L
// dwFlags2
#define DDS_ALPHAPIXELS 0x00000001L
#define DDS_ALPHA 0x00000002L
#define DDS_FOURCC 0x00000004L
#define DDS_RGB 0x00000040L
#define DDS_RGBA 0x00000041L // (DDS_RGB|DDS_ALPHAPIXELS)
#define DDS_LUMINANCE 0x00020000L
#define DDS_DUDV 0x00080000L
// dwCaps1
#define DDS_COMPLEX 0x00000008L
#define DDS_TEXTURE 0x00001000L
#define DDS_MIPMAP 0x00400000L
// dwCaps2
#define DDS_CUBEMAP 0x00000200L
#define DDS_CUBEMAP_POSITIVEX 0x00000400L
#define DDS_CUBEMAP_NEGATIVEX 0x00000800L
#define DDS_CUBEMAP_POSITIVEY 0x00001000L
#define DDS_CUBEMAP_NEGATIVEY 0x00002000L
#define DDS_CUBEMAP_POSITIVEZ 0x00004000L
#define DDS_CUBEMAP_NEGATIVEZ 0x00008000L
#define DDS_CUBEMAP_ALL_SIDES 0x0000FC00L
#define DDS_VOLUME 0x00200000L
typedef struct dds_pf_s
{
uint dwSize;
uint dwFlags;
uint dwFourCC;
uint dwRGBBitCount;
uint dwRBitMask;
uint dwGBitMask;
uint dwBBitMask;
uint dwABitMask;
} dds_pixf_t;
// DDCAPS2
typedef struct dds_caps_s
{
uint dwCaps1;
uint dwCaps2;
uint dwCaps3; // currently unused
uint dwCaps4; // currently unused
} dds_caps_t;
typedef struct dds_s
{
uint dwIdent; // must matched with DDSHEADER
uint dwSize;
uint dwFlags; // determines what fields are valid
uint dwHeight;
uint dwWidth;
uint dwLinearSize; // Formless late-allocated optimized surface size
uint dwDepth; // depth if a volume texture
uint dwMipMapCount; // number of mip-map levels requested
uint dwAlphaBitDepth; // depth of alpha buffer requested
uint dwReserved1[10]; // reserved for future expansions
dds_pixf_t dsPixelFormat;
dds_caps_t dsCaps;
uint dwTextureStage;
} dds_t;
static bool Image_DXTGetPixelFormat( dds_t *hdr, uint &flags, uint &sqFlags )
{
uint bits = hdr->dsPixelFormat.dwRGBBitCount;
if( FBitSet( hdr->dsCaps.dwCaps2, DDS_VOLUME ))
return false; // 3D textures doesn't support
if( FBitSet( hdr->dsPixelFormat.dwFlags, DDS_FOURCC ))
{
switch( hdr->dsPixelFormat.dwFourCC )
{
case TYPE_DXT1:
SetBits( flags, IMAGE_DXT_FORMAT );
SetBits( sqFlags, squish::kDxt1 );
break;
case TYPE_DXT3:
SetBits( flags, IMAGE_DXT_FORMAT );
SetBits( sqFlags, squish::kDxt3 );
break;
case TYPE_DXT5:
SetBits( flags, IMAGE_DXT_FORMAT );
SetBits( sqFlags, squish::kDxt5 );
break;
default: return false;
}
}
else
{
// this dds texture isn't compressed so write out ARGB or luminance format
if( FBitSet( hdr->dsPixelFormat.dwFlags, DDS_DUDV ))
{
return false;
}
else if( FBitSet( hdr->dsPixelFormat.dwFlags, DDS_LUMINANCE ))
{
return false;
}
else
{
if( bits == 32 )
SetBits( flags, IMAGE_HAS_8BIT_ALPHA );
else if( bits != 24 )
return false;
}
// no deals with other DXT types anyway
}
SetBits( flags, IMAGE_HAS_COLOR ); // predict state
if( FBitSet( hdr->dsPixelFormat.dwFlags, DDS_ALPHA ))
SetBits( flags, IMAGE_HAS_8BIT_ALPHA );
if( FBitSet( hdr->dsPixelFormat.dwFlags, DDS_ALPHAPIXELS ))
SetBits( flags, IMAGE_HAS_8BIT_ALPHA );
if( !FBitSet( hdr->dwFlags, DDS_MIPMAPCOUNT ))
hdr->dwMipMapCount = 1;
// setup additional flags
if( FBitSet( hdr->dsCaps.dwCaps1, DDS_COMPLEX ) && FBitSet( hdr->dsCaps.dwCaps2, DDS_CUBEMAP ))
{
if( hdr->dwMipMapCount <= 1 )
SetBits( flags, IMAGE_SKYBOX );
else SetBits( flags, IMAGE_CUBEMAP );
}
return true;
}
static size_t Image_DXTGetLinearSize( int block, int width, int height )
{
return ((width + 3) / 4) * ((height + 3) / 4) * block;
}
static size_t Image_DXTGetBlockSize( dds_t *hdr )
{
if( FBitSet( hdr->dsPixelFormat.dwFlags, DDS_FOURCC ))
{
switch( hdr->dsPixelFormat.dwFourCC )
{
case TYPE_DXT1: return 8;
case TYPE_DXT3: return 16;
case TYPE_DXT5: return 16;
case TYPE_ATI2: return 16;
default: return 0;
}
}
// no deals with other DXT types anyway
return 0;
}
static size_t Image_DXTCalcMipmapSize( dds_t *hdr )
{
size_t buffsize = 0;
int width, height;
// now correct buffer size
for( int i = 0; i < hdr->dwMipMapCount; i++ )
{
width = Q_max( 1, ( hdr->dwWidth >> i ));
height = Q_max( 1, ( hdr->dwHeight >> i ));
buffsize += Image_DXTGetLinearSize( Image_DXTGetBlockSize( hdr ), width, height );
}
return buffsize;
}
static size_t Image_DXTCalcSize( const char *name, dds_t *hdr, size_t filesize )
{
size_t buffsize = Image_DXTCalcMipmapSize( hdr );
if( FBitSet( hdr->dsCaps.dwCaps2, DDS_CUBEMAP ))
buffsize *= 6;
if( filesize != buffsize ) // main check
{
MsgDev( D_WARN, "LoadDDS: (%s) probably corrupted (%i should be %i)\n", name, buffsize, filesize );
if( filesize < buffsize )
return false;
}
return buffsize;
}
static int Image_DXTCalcMipmapCount( rgbdata_t *pix )
{
int width, height;
if( FBitSet( pix->flags, IMAGE_SKYBOX|IMAGE_NOMIPS ))
return 1;
// mip-maps can't exceeds 16
for( int mipcount = 0; mipcount < 16; mipcount++ )
{
width = Q_max( 1, ( pix->width >> mipcount ));
height = Q_max( 1, ( pix->height >> mipcount ));
if( width == 1 && height == 1 )
break;
}
MsgDev( D_REPORT, "image[ %i x %i ] has %i mips\n", pix->width, pix->height, mipcount + 1 );
return mipcount + 1;
}
static bool Image_CheckDXT3Alpha( dds_t *hdr, byte *fin )
{
word sAlpha;
byte *alpha;
int x, y, i, j;
for( y = 0; y < hdr->dwHeight; y += 4 )
{
for( x = 0; x < hdr->dwWidth; x += 4 )
{
alpha = fin + 8;
fin += 16;
for( j = 0; j < 4; j++ )
{
sAlpha = alpha[2*j+0] + 256 * alpha[2*j+1];
for( i = 0; i < 4; i++ )
{
if((( x + i ) < hdr->dwWidth ) && (( y + j ) < hdr->dwHeight ))
{
if( sAlpha == 0 )
return true;
}
sAlpha >>= 4;
}
}
}
}
return false;
}
static bool Image_CheckDXT5Alpha( dds_t *hdr, byte *fin )
{
uint bits, bitmask;
byte *alphamask;
int x, y, i, j;
for( y = 0; y < hdr->dwHeight; y += 4 )
{
for( x = 0; x < hdr->dwWidth; x += 4 )
{
if( y >= hdr->dwHeight || x >= hdr->dwWidth )
break;
alphamask = fin + 2;
fin += 8;
bitmask = ((uint *)fin)[1];
fin += 8;
// last three bytes
bits = (alphamask[3]) | (alphamask[4] << 8) | (alphamask[5] << 16);
for( j = 2; j < 4; j++ )
{
for( i = 0; i < 4; i++ )
{
// only put pixels out < width or height
if((( x + i ) < hdr->dwWidth ) && (( y + j ) < hdr->dwHeight ))
{
if( bits & 0x07 )
return true;
}
bits >>= 3;
}
}
}
}
return false;
}
/*
=============
DDSToBuffer
=============
*/
rgbdata_t *DDSToBuffer( const char *name, const byte *buffer, size_t filesize )
{
uint flags = 0;
uint dummy = 0;
dds_t header;
rgbdata_t *pic;
if( filesize < sizeof( dds_t ))
{
MsgDev( D_ERROR, "Image_LoadDDS: file (%s) have invalid size\n", name );
return NULL;
}
memcpy( &header, buffer, sizeof( dds_t ));
if( header.dwIdent != DDSHEADER )
return NULL; // it's not a dds file, just skip it
if( header.dwSize != sizeof( dds_t ) - sizeof( uint )) // size of the structure (minus MagicNum)
{
MsgDev( D_ERROR, "LoadDDS: (%s) have corrupted header\n", name );
return NULL;
}
if( header.dsPixelFormat.dwSize != sizeof( dds_pixf_t )) // size of the structure
{
MsgDev( D_ERROR, "LoadDDS: (%s) have corrupt pixelformat header\n", name );
return NULL;
}
if( !Image_ValidSize( name, header.dwWidth, header.dwHeight ))
return NULL;
if( !Image_DXTGetPixelFormat( &header, flags, dummy ))
{
MsgDev( D_ERROR, "LoadDDS: (%s) unsupported DXT format (only DXT1, DXT3 and DXT5 is supported)\n", name );
return NULL;
}
if( !Image_DXTCalcSize( name, &header, filesize - sizeof( dds_t )))
return NULL;
// now all the checks are passed, store image as rgbdata
pic = (rgbdata_t *)Mem_Alloc( sizeof( rgbdata_t ) + filesize );
pic->buffer = ((byte *)pic) + sizeof( rgbdata_t );
memcpy( pic->buffer, buffer, filesize );
pic->width = header.dwWidth;
pic->height = header.dwHeight;
pic->size = filesize;
pic->flags = flags;
return pic;
}
static bool Image_DXTWriteHeader( vfile_t *f, rgbdata_t *pix, int format, int numMipMaps )
{
uint dwFourCC = 0, dwFlags1 = 0, dwFlags2 = 0, dwCaps1 = 0, dwCaps2 = 0;
uint dwLinearSize, dwSize = 124, dwSize2 = sizeof( dds_pixf_t );
uint dwWidth, dwHeight, dwMipCount, dwCustomEncode = DXT_ENCODE_DEFAULT;
dword dwReflectivity = 0;
uint dwIdent = DDSHEADER;
if( !pix || !pix->buffer )
return false;
// setup flags
SetBits( dwFlags1, DDS_LINEARSIZE|DDS_WIDTH|DDS_HEIGHT|DDS_CAPS|DDS_PIXELFORMAT|DDS_PITCH );
SetBits( dwFlags2, DDS_FOURCC );
dwMipCount = numMipMaps;
if( dwMipCount > 1 ) SetBits( dwFlags1, DDS_MIPMAPCOUNT );
switch( format )
{
case PF_DXT1:
dwFourCC = TYPE_DXT1;
break;
case PF_DXT3:
dwFourCC = TYPE_DXT3;
break;
case PF_DXT5_YCoCg:
dwCustomEncode = DXT_ENCODE_COLOR_YCoCg;
dwFourCC = TYPE_DXT5;
break;
case PF_ATI2_NORM_PARABOLOID:
dwCustomEncode = DXT_ENCODE_NORMAL_AG_PARABOLOID;
dwFourCC = TYPE_ATI2;
break;
case PF_DXT5:
case PF_DXT5_ALPHA:
case PF_DXT5_NORM_BASE:
dwFourCC = TYPE_DXT5;
break;
case PF_DXT5_SDF_ALPHA:
dwCustomEncode = DXT_ENCODE_ALPHA_SDF;
dwFourCC = TYPE_DXT5;
break;
default:
MsgDev( D_ERROR, "Image_DXTWriteHeader: unknown format\n" );
return false;
}
Image_PackRGB( pix->reflectivity, dwReflectivity );
dwWidth = pix->width;
dwHeight = pix->height;
VFS_Write( f, &dwIdent, sizeof( uint ));
VFS_Write( f, &dwSize, sizeof( uint ));
VFS_Write( f, &dwFlags1, sizeof( uint ));
VFS_Write( f, &dwHeight, sizeof( uint ));
VFS_Write( f, &dwWidth, sizeof( uint ));
dwLinearSize = Image_DXTGetLinearSize( Image_DXTGetBlockSize( format ), pix->width, pix->height );
VFS_Write( f, &dwLinearSize, sizeof( uint ));
VFS_Write( f, 0, sizeof( uint ));
VFS_Write( f, &dwMipCount, sizeof( uint ));
VFS_Write( f, NULL, sizeof( uint ));
VFS_Write( f, &dwCustomEncode, sizeof( uint )); // was dwReserved[0]
VFS_Write( f, &dwReflectivity, sizeof( dword )); // was dwReserved[1]
VFS_Write( f, NULL, sizeof( uint ) * 8 ); // reserved fields
VFS_Write( f, &dwSize2, sizeof( uint ));
VFS_Write( f, &dwFlags2, sizeof( uint ));
VFS_Write( f, &dwFourCC, sizeof( uint ));
VFS_Write( f, NULL, sizeof( uint ));
VFS_Write( f, NULL, sizeof( uint ));
VFS_Write( f, NULL, sizeof( uint ));
VFS_Write( f, NULL, sizeof( uint ));
VFS_Write( f, NULL, sizeof( uint ));
dwCaps1 |= DDS_TEXTURE;
if( dwMipCount > 1 )
SetBits( dwCaps1, DDS_MIPMAP|DDS_COMPLEX );
if( pix->flags & ( IMAGE_CUBEMAP|IMAGE_SKYBOX ))
{
SetBits( dwCaps1, DDS_COMPLEX );
SetBits( dwCaps2, DDS_CUBEMAP|DDS_CUBEMAP_ALL_SIDES );
}
VFS_Write( f, &dwCaps1, sizeof( uint ));
VFS_Write( f, &dwCaps2, sizeof( uint ));
VFS_Write( f, NULL, sizeof( uint ) * 3 ); // other caps and TextureStage
return true;
}
static size_t CompressDXT( vfile_t *f, const byte *buffer, int width, int height, int format, bool estimate )
{
size_t dst_size;
size_t cur_size;
size_t curpos;
if( !buffer ) return 0;
dst_size = Image_DXTGetLinearSize( Image_DXTGetBlockSize( format ), width, height );
curpos = VFS_Tell( f );
CompressRGBABufferToDXT( buffer, f, width, height, format, estimate );
cur_size = VFS_Tell( f ) - curpos;
if( cur_size != dst_size )
{
MsgDev( D_ERROR, "CompressDXT: corrupt mem (buffer is %s bytes, written %s)\n", Q_memprint( dst_size ), Q_memprint( cur_size ));
return false;
}
return true;
}
rgbdata_t *BufferToDDS( rgbdata_t *pix, int saveformat )
{
vfile_t *file; // virtual file
rgbdata_t *out = NULL;
rgbdata_t *mip = NULL;
bool normalMap = (saveformat >= PF_DXT5_NORM_BASE && saveformat <= PF_ATI2_NORM_PARABOLOID) ? true : false;
int width, height;
int nummips = 1;
int numSides = 1;
// check for all the possible problems
if( !pix ) return NULL;
if( FBitSet( pix->flags, IMAGE_DXT_FORMAT ))
return NULL; // already compressed
if(( pix->width & 15 ) || ( pix->height & 15 ))
return NULL; // not aligned by 16
file = VFS_Create( NULL, 0 );
if( !file ) return NULL;
nummips = Image_DXTCalcMipmapCount( pix );
if( !Image_DXTWriteHeader( file, pix, saveformat, nummips ))
{
MsgDev( D_ERROR, "BufferToDDS: unsupported format\n" );
VFS_Close( file );
return NULL;
}
mip = Image_Copy( pix );
if( FBitSet( pix->flags, IMAGE_CUBEMAP|IMAGE_SKYBOX ))
numSides = 6;
for( int i = 0; i < numSides; i++ )
{
byte *buffer = mip->buffer + ((pix->width * pix->height * 4) * i);
width = pix->width;
height = pix->height;
for( int j = 0; j < nummips; j++ )
{
if( j ) Image_BuildMipMap( buffer, width, height, normalMap );
width = Q_max( 1, ( pix->width >> j ));
height = Q_max( 1, ( pix->height >> j ));
if( !CompressDXT( file, buffer, width, height, saveformat, ( j == 0 ) ))
{
MsgDev( D_ERROR, "BufferToDDS: can't compress image\n" );
VFS_Close( file );
Mem_Free( mip );
return NULL;
}
}
}
Mem_Free( mip );
// create a new pic
out = (rgbdata_t *)Mem_Alloc( sizeof( rgbdata_t ) + VFS_Tell( file ));
out->buffer = ((byte *)out) + sizeof( rgbdata_t );
memcpy( out->buffer, VFS_GetBuffer( file ), VFS_Tell( file ));
out->width = pix->width;
out->height = pix->height;
out->size = VFS_Tell( file );
SetBits( out->flags, IMAGE_HAS_COLOR );
SetBits( out->flags, IMAGE_DXT_FORMAT );
if( FBitSet( pix->flags, IMAGE_CUBEMAP ))
SetBits( out->flags, IMAGE_CUBEMAP );
if( FBitSet( pix->flags, IMAGE_SKYBOX ))
SetBits( out->flags, IMAGE_SKYBOX );
// release virtual file
VFS_Close( file );
return out;
}
rgbdata_t *DDSToRGBA( const char *name, const byte *buffer, size_t filesize )
{
uint flags = 0;
uint sqFlags = 0;
dds_t header;
rgbdata_t *pic;
byte *fin;
if( filesize < sizeof( dds_t ))
{
MsgDev( D_ERROR, "Image_LoadDDS: file (%s) have invalid size\n", name );
return NULL;
}
memcpy( &header, buffer, sizeof( dds_t ));
if( header.dwIdent != DDSHEADER )
return NULL; // it's not a dds file, just skip it
if( header.dwSize != sizeof( dds_t ) - sizeof( uint )) // size of the structure (minus MagicNum)
{
MsgDev( D_ERROR, "LoadDDS: (%s) have corrupted header\n", name );
return NULL;
}
if( header.dsPixelFormat.dwSize != sizeof( dds_pixf_t )) // size of the structure
{
MsgDev( D_ERROR, "LoadDDS: (%s) have corrupt pixelformat header\n", name );
return NULL;
}
if( !Image_ValidSize( name, header.dwWidth, header.dwHeight ))
return NULL;
if( !Image_DXTGetPixelFormat( &header, flags, sqFlags ))
{
MsgDev( D_ERROR, "LoadDDS: (%s) unsupported DXT format (only DXT1, DXT3 and DXT5 is supported)\n", name );
return NULL;
}
if( FBitSet( flags, IMAGE_DXT_FORMAT ))
{
if( !Image_DXTCalcSize( name, &header, filesize - sizeof( dds_t )))
return NULL;
}
fin = (byte *)(buffer + sizeof( dds_t )); // pixels are immediately follows after header
if( FBitSet( sqFlags, squish::kDxt3 ) && Image_CheckDXT3Alpha( &header, fin ))
SetBits( flags, IMAGE_HAS_8BIT_ALPHA );
else if( FBitSet( sqFlags, squish::kDxt5 ) && Image_CheckDXT5Alpha( &header, fin ))
SetBits( flags, IMAGE_HAS_8BIT_ALPHA );
pic = Image_Alloc( header.dwWidth, header.dwHeight );
// now all the checks are passed, store image as rgbdata
if( FBitSet( flags, IMAGE_DXT_FORMAT ))
squish::DecompressImage( pic->buffer, header.dwWidth, header.dwHeight, fin, sqFlags );
else memcpy( pic->buffer, fin, header.dwWidth * header.dwHeight * FBitSet( flags, IMAGE_HAS_ALPHA ) ? 4 : 3 );
pic->flags = flags;
return pic;
}
int DDS_GetSaveFormatForHint( char hint, rgbdata_t *pix )
{
if( hint == IMG_NORMALMAP )
{
return PF_ATI2_NORM_PARABOLOID;
}
else
{
if( FBitSet( pix->flags, IMAGE_HAS_ALPHA ))
return PF_DXT5_ALPHA;
return PF_DXT1;
}
}