This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/launch/imagelib/img_main.c

514 lines
15 KiB
C

//=======================================================================
// Copyright XashXT Group 2007 ©
// img_main.c - load & save various image formats
//=======================================================================
#include "imagelib.h"
#include "mathlib.h"
// global image variables
imglib_t image;
typedef struct suffix_s
{
const char *suf;
uint flags;
image_hint_t hint;
} suffix_t;
static const suffix_t skybox_qv1[6] =
{
{ "ft", IMAGE_FLIP_X, CB_HINT_POSX },
{ "bk", IMAGE_FLIP_Y, CB_HINT_NEGX },
{ "up", IMAGE_ROT_90, CB_HINT_POSZ },
{ "dn", IMAGE_ROT_90, CB_HINT_NEGZ },
{ "rt", IMAGE_ROT_90, CB_HINT_POSY },
{ "lf", IMAGE_ROT270, CB_HINT_NEGY },
};
static const suffix_t skybox_qv2[6] =
{
{ "_ft", IMAGE_FLIP_X, CB_HINT_POSX },
{ "_bk", IMAGE_FLIP_Y, CB_HINT_NEGX },
{ "_up", IMAGE_ROT_90, CB_HINT_POSZ },
{ "_dn", IMAGE_ROT_90, CB_HINT_NEGZ },
{ "_rt", IMAGE_ROT_90, CB_HINT_POSY },
{ "_lf", IMAGE_ROT270, CB_HINT_NEGY },
};
static const suffix_t cubemap_v1[6] =
{
{ "posx", 0, CB_HINT_POSX },
{ "negx", 0, CB_HINT_NEGX },
{ "posy", 0, CB_HINT_POSY },
{ "negy", 0, CB_HINT_NEGY },
{ "posz", 0, CB_HINT_POSZ },
{ "negz", 0, CB_HINT_NEGZ },
};
static const suffix_t cubemap_v2[6] =
{
{ "px", 0, CB_HINT_POSX },
{ "nx", 0, CB_HINT_NEGX },
{ "py", 0, CB_HINT_POSY },
{ "ny", 0, CB_HINT_NEGY },
{ "pz", 0, CB_HINT_POSZ },
{ "nz", 0, CB_HINT_NEGZ },
};
typedef struct cubepack_s
{
const char *name; // just for debug
const suffix_t *type;
} cubepack_t;
static const cubepack_t load_cubemap[] =
{
{ "3Ds Sky1", skybox_qv1 },
{ "3Ds Sky2", skybox_qv2 },
{ "3Ds Cube", cubemap_v2 },
{ "Tenebrae", cubemap_v1 }, // FIXME: remove this ?
{ NULL, NULL },
};
// soul of ImageLib - table of image format constants
const bpc_desc_t PFDesc[] =
{
{PF_UNKNOWN, "raw", 0x1908, 0x1401, 0, 0, 0 },
{PF_INDEXED_24, "pal 24", 0x1908, 0x1401, 1, 1, 0 }, // expand data to RGBA buffer
{PF_INDEXED_32, "pal 32", 0x1908, 0x1401, 1, 1, 0 },
{PF_RGBA_32, "RGBA 32",0x1908, 0x1401, 4, 1, -4 },
{PF_BGRA_32, "BGRA 32",0x80E1, 0x1401, 4, 1, -4 },
{PF_ARGB_32, "ARGB 32",0x1908, 0x8366, 4, 1, -4 },
{PF_ABGR_64, "ABGR 64",0x80E1, 0x1401, 4, 2, -8 },
{PF_RGB_24, "RGB 24", 0x1908, 0x1401, 3, 1, -3 },
{PF_BGR_24, "BGR 24", 0x80E0, 0x1401, 3, 1, -3 },
{PF_RGB_16, "RGB 16", 0x8054, 0x8364, 3, 2, 16 }, // FIXME: do revision
{PF_DXT1, "DXT1", 0x1908, 0x1401, 4, 1, 8 },
{PF_DXT2, "DXT2", 0x1908, 0x1401, 4, 1, 16 },
{PF_DXT3, "DXT3", 0x1908, 0x1401, 4, 1, 16 },
{PF_DXT4, "DXT4", 0x1908, 0x1401, 4, 1, 16 },
{PF_DXT5, "DXT5", 0x1908, 0x1401, 4, 1, 16 },
{PF_RXGB, "RXGB", 0x1908, 0x1401, 3, 1, 16 },
{PF_ATI1N, "ATI1N", 0x1908, 0x1401, 1, 1, 8 },
{PF_ATI2N, "3DC", 0x1908, 0x1401, 4, 1, 16 },
{PF_LUMINANCE, "LUM 8", 0x1909, 0x1401, 1, 1, -1 },
{PF_LUMINANCE_16, "LUM 16", 0x1909, 0x1401, 2, 2, -2 },
{PF_LUMINANCE_ALPHA,"LUM A", 0x190A, 0x1401, 2, 1, -2 },
{PF_UV_16, "UV 16", 0x190A, 0x1401, 2, 1, -2 },
{PF_UV_16, "UV 16", 0x190A, 0x1401, 2, 1, -4 },
{PF_R_16F, "R 16f", 0x8884, 0x1406, 1, 4, -2 }, // FIXME: these NV extension, reinstall for ATI
{PF_R_32F, "R 32f", 0x8885, 0x1406, 1, 4, -4 },
{PF_GR_32F, "GR 32f", 0x8886, 0x1406, 2, 4, -4 },
{PF_GR_64F, "GR 64f", 0x8887, 0x1406, 2, 4, -8 },
{PF_ABGR_64F, "ABGR64f",0x888A, 0x1406, 4, 2, -8 },
{PF_ABGR_128F, "ABGR128",0x888B, 0x1406, 4, 4, -16},
{PF_RGBA_GN, "system", 0x1908, 0x1401, 4, 1, -4 },
};
bpc_desc_t *Image_GetPixelFormat( pixformat_t type )
{
type = bound( PF_UNKNOWN, type, PF_TOTALCOUNT - 1 );
return (bpc_desc_t *)&PFDesc[type];
}
void Image_Reset( void )
{
// reset global variables
image.width = image.height = 0;
image.source_width = image.source_height = 0;
image.bits_count = image.flags = 0;
image.num_sides = 0;
image.depth = 1;
image.source_type = 0;
image.num_mips = 0;
image.filter = CB_HINT_NO;
image.type = PF_UNKNOWN;
// pointers will be saved with prevoius picture struct
// don't care about it
image.palette = NULL;
image.cubemap = NULL;
image.rgba = NULL;
image.ptr = 0;
image.size = 0;
}
rgbdata_t *ImagePack( void )
{
rgbdata_t *pack = Mem_Alloc( Sys.imagepool, sizeof( rgbdata_t ));
if( image.cubemap && image.num_sides != 6 )
{
// this neved be happens, just in case
MsgDev( D_NOTE, "ImagePack: inconsistent cubemap pack %d\n", image.num_sides );
FS_FreeImage( pack );
return NULL;
}
if( image.cubemap )
{
image.flags |= IMAGE_CUBEMAP;
pack->buffer = image.cubemap;
pack->width = image.source_width;
pack->height = image.source_height;
pack->type = image.source_type;
pack->size = image.size * image.num_sides;
}
else
{
pack->buffer = image.rgba;
pack->width = image.width;
pack->height = image.height;
pack->type = image.type;
pack->size = image.size;
}
pack->depth = image.depth;
pack->numMips = image.num_mips;
pack->bitsCount = image.bits_count;
pack->flags = image.flags;
pack->palette = image.palette;
return pack;
}
/*
================
FS_AddSideToPack
FIXME: rewrite with VirtualFS using ?
================
*/
bool FS_AddSideToPack( const char *name, int adjust_flags )
{
byte *out, *flipped;
bool resampled = false;
// first side set average size for all cubemap sides!
if( !image.cubemap )
{
image.source_width = image.width;
image.source_height = image.height;
image.source_type = image.type;
}
image.size = image.source_width * image.source_height * 4; // keep constant size, render.dll expecting it
// mixing dds format with any existing ?
if( image.type != image.source_type )
return false;
// flip image if needed
flipped = Image_FlipInternal( image.rgba, &image.width, &image.height, image.source_type, adjust_flags );
if( !flipped ) return false; // try to reasmple dxt?
if( flipped != image.rgba ) image.rgba = Image_Copy( image.size );
// resampling image if needed
out = Image_ResampleInternal((uint *)image.rgba, image.width, image.height, image.source_width, image.source_height, image.source_type, &resampled );
if( !out ) return false; // try to reasmple dxt?
if( resampled ) image.rgba = Image_Copy( image.size );
image.cubemap = Mem_Realloc( Sys.imagepool, image.cubemap, image.ptr + image.size );
Mem_Copy( image.cubemap + image.ptr, image.rgba, image.size ); // add new side
Mem_Free( image.rgba ); // release source buffer
image.ptr += image.size; // move to next
image.num_sides++; // bump sides count
return true;
}
bool FS_AddMipmapToPack( const byte *in, int width, int height )
{
int mipsize = width * height;
int outsize = width * height;
// check for inconsistency
if( !image.source_type ) image.source_type = image.type;
// trying to add 8 bit mimpap into 32-bit mippack or somewhat...
if( image.source_type != image.type ) return false;
if(!( image.cmd_flags & IL_KEEP_8BIT )) outsize *= 4;
else Image_CopyPalette32bit();
// reallocate image buffer
image.rgba = Mem_Realloc( Sys.imagepool, image.rgba, image.size + outsize );
if( image.cmd_flags & IL_KEEP_8BIT ) Mem_Copy( image.rgba + image.ptr, in, outsize );
else if( !Image_Copy8bitRGBA( in, image.rgba + image.ptr, mipsize ))
return false; // probably pallette not installed
image.size += outsize;
image.ptr += outsize;
image.num_mips++;
return true;
}
/*
================
FS_LoadImage
loading and unpack to rgba any known image
================
*/
rgbdata_t *FS_LoadImage( const char *filename, const byte *buffer, size_t size )
{
const char *ext = FS_FileExtension( filename );
string path, loadname, sidename;
bool dds_installed = false; // current loadformats list supported dds
bool anyformat = true;
int i, filesize = 0;
const loadpixformat_t *format;
const cubepack_t *cmap;
byte *f;
#if 0 // don't try to be very clever
if( !buffer || !buffsize ) buffer = (char *)florr1_2_jpg, buffsize = sizeof(florr1_2_jpg);
#endif
Image_Reset(); // clear old image
com.strncpy( loadname, filename, sizeof( loadname ));
if( com.stricmp( ext, "" ))
{
// we needs to compare file extension with list of supported formats
// and be sure what is real extension, not a filename with dot
for( format = image.loadformats; format && format->formatstring; format++ )
{
if( !com.stricmp( format->ext, ext ))
{
FS_StripExtension( loadname );
anyformat = false;
break;
}
}
}
// HACKHACK: skip any checks, load file from buffer
if( filename[0] == '#' && buffer && size ) goto load_internal;
// engine notify
if( !anyformat ) MsgDev( D_NOTE, "Note: %s will be loading only with ext .%s\n", loadname, ext );
// now try all the formats in the selected list
for( format = image.loadformats; format && format->formatstring; format++)
{
if( !com_stricmp( format->ext, "dds" )) dds_installed = true;
if( anyformat || !com_stricmp( ext, format->ext ))
{
com_sprintf( path, format->formatstring, loadname, "", format->ext );
image.hint = format->hint;
f = FS_LoadFile( path, &filesize );
if( f && filesize > 0 )
{
if( format->loadfunc( path, f, filesize ))
{
Mem_Free(f); // release buffer
return ImagePack(); // loaded
}
else Mem_Free(f); // release buffer
}
}
}
// special case for extract cubemap side from dds image
if( dds_installed && (anyformat || !com_stricmp( ext, "dds" )))
{
// first, check for cubemap suffixes
for( cmap = load_cubemap; cmap && cmap->type; cmap++ )
{
for( i = 0; i < 6; i++ )
{
int suflen = com.strlen( cmap->type[i].suf );
char *suffix = &loadname[com.strlen(loadname)-suflen];
// suffixes may have difference length, need fine check
if( !com.strnicmp( suffix, cmap->type[i].suf, suflen ))
{
com.strncpy( path, loadname, com.strlen( loadname ) - suflen + 1 );
FS_DefaultExtension( path, ".dds" );
image.filter = cmap->type[i].hint; // install side hint
f = FS_LoadFile( path, &filesize );
if( f && filesize > 0 )
{
// this name will be used only for tell user about problems
if( Image_LoadDDS( path, f, filesize ))
{
Mem_Free(f); // release buffer
return ImagePack(); // loaded
}
else
{
Mem_Free(f); // release buffer
goto load_internal; // cubemap test failed
}
}
}
}
}
}
// check all cubemap sides with package suffix
for( cmap = load_cubemap; cmap && cmap->type; cmap++ )
{
for( i = 0; i < 6; i++ )
{
// for support mixed cubemaps e.g. sky_ft.jpg, sky_rt.tga, sky_bk.png
// NOTE: all loaders must keep sides in one format for all
for( format = image.loadformats; format && format->formatstring; format++ )
{
if( anyformat || !com.stricmp( ext, format->ext ))
{
com_sprintf( path, format->formatstring, loadname, cmap->type[i].suf, format->ext );
image.hint = cmap->type[i].hint; // side hint
f = FS_LoadFile( path, &filesize );
if( f && filesize > 0 )
{
// this name will be used only for tell user about problems
if( format->loadfunc( path, f, filesize ))
{
com.snprintf( sidename, MAX_STRING, "%s%s.%s", loadname, cmap->type[i].suf, format->ext );
if( FS_AddSideToPack( sidename, cmap->type[i].flags )) // process flags to flip some sides
{
Mem_Free( f );
break; // loaded
}
}
Mem_Free( f );
}
}
}
if( image.num_sides != i + 1 ) // check side
{
// first side not found, probably it's not cubemap
// it contain info about image_type and dimensions, don't generate black cubemaps
if( !image.cubemap ) break;
MsgDev( D_ERROR, "FS_LoadImage: couldn't load (%s%s.%s), create black image\n", loadname, cmap->type[i].suf );
// Mem_Alloc already filled memblock with 0x00, no need to do it again
image.cubemap = Mem_Realloc( Sys.imagepool, image.cubemap, image.ptr + image.size );
image.ptr += image.size; // move to next
image.num_sides++; // merge counter
}
}
// make sure what all sides is loaded
if( image.num_sides != 6 )
{
// unexpected errors ?
if( image.cubemap )
Mem_Free( image.cubemap );
Image_Reset();
}
else break;
}
if( image.cubemap )
return ImagePack(); // all done
load_internal:
for( format = image.baseformats; format && format->formatstring; format++ )
{
if( anyformat || !com_stricmp( ext, format->ext ))
{
image.hint = format->hint;
if( buffer && size > 0 )
{
if( format->loadfunc( loadname, buffer, size ))
return ImagePack(); // loaded
}
}
}
if( !image.loadformats || image.loadformats->ext == NULL )
MsgDev( D_NOTE, "FS_LoadImage: imagelib offline\n" );
else if( filename[0] != '#' )
MsgDev( D_WARN, "FS_LoadImage: couldn't load \"%s\"\n", loadname );
return NULL;
}
/*
================
Image_Save
writes image as any known format
================
*/
bool FS_SaveImage( const char *filename, rgbdata_t *pix )
{
const char *ext = FS_FileExtension( filename );
bool anyformat = !com_stricmp( ext, "" ) ? true : false;
string path, savename;
const savepixformat_t *format;
if( !pix || !pix->buffer || anyformat ) return false;
com.strncpy( savename, filename, sizeof( savename ));
FS_StripExtension( savename ); // remove extension if needed
if( pix->flags & (IMAGE_CUBEMAP|IMAGE_SKYBOX) && com.stricmp( ext, "dds" ))
{
size_t realSize = pix->size; // keep real pic size
byte *picBuffer; // to avoid corrupt memory on free data
const suffix_t *box;
int i;
if( pix->flags & IMAGE_SKYBOX )
box = skybox_qv1;
else if( pix->flags & IMAGE_CUBEMAP )
box = cubemap_v2;
else return false; // do not happens
pix->size /= 6; // now set as side size
picBuffer = pix->buffer;
// save all sides seperately
for( format = image.saveformats; format && format->formatstring; format++ )
{
if( !com.stricmp( ext, format->ext ))
{
for( i = 0; i < 6; i++ )
{
com.sprintf( path, format->formatstring, savename, box[i].suf, format->ext );
if( !format->savefunc( path, pix )) break; // there were errors
pix->buffer += pix->size; // move pointer
}
// restore pointers
pix->size = realSize;
pix->buffer = picBuffer;
return ( i == 6 );
}
}
}
else
{
for( format = image.saveformats; format && format->formatstring; format++ )
{
if( !com.stricmp( ext, format->ext ))
{
com.sprintf( path, format->formatstring, savename, "", format->ext );
if( format->savefunc( path, pix )) return true; // saved
}
}
}
return false;
}
/*
================
Image_FreeImage
free RGBA buffer
================
*/
void FS_FreeImage( rgbdata_t *pack )
{
if( pack )
{
if( pack->buffer ) Mem_Free( pack->buffer );
if( pack->palette ) Mem_Free( pack->palette );
Mem_Free( pack );
}
else MsgDev( D_WARN, "FS_FreeImage: trying to free NULL image\n" );
}