2008-01-28 22:00:00 +01:00
|
|
|
|
//=======================================================================
|
|
|
|
|
// Copyright XashXT Group 2007 <20>
|
|
|
|
|
// img_tga.c - tga format load & save
|
|
|
|
|
//=======================================================================
|
|
|
|
|
|
2008-08-06 22:00:00 +02:00
|
|
|
|
#include "launch.h"
|
2008-07-23 22:00:00 +02:00
|
|
|
|
#include "byteorder.h"
|
2008-08-06 22:00:00 +02:00
|
|
|
|
#include "filesystem.h"
|
2008-01-28 22:00:00 +01:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
=============
|
|
|
|
|
Image_LoadTGA
|
|
|
|
|
=============
|
|
|
|
|
*/
|
2008-07-23 22:00:00 +02:00
|
|
|
|
bool Image_LoadTGA( const char *name, const byte *buffer, size_t filesize )
|
2008-01-28 22:00:00 +01:00
|
|
|
|
{
|
|
|
|
|
int x, y, pix_inc, row_inc;
|
|
|
|
|
int red, green, blue, alpha;
|
|
|
|
|
int runlen, alphabits;
|
2008-08-06 22:00:00 +02:00
|
|
|
|
byte *p, *pixbuf;
|
2008-01-28 22:00:00 +01:00
|
|
|
|
const byte *fin, *enddata;
|
2008-08-06 22:00:00 +02:00
|
|
|
|
byte palette[256*4];
|
2008-01-28 22:00:00 +01:00
|
|
|
|
tga_t targa_header;
|
|
|
|
|
|
|
|
|
|
if(filesize < sizeof(tga_t))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
fin = buffer;
|
|
|
|
|
enddata = fin + filesize;
|
|
|
|
|
|
|
|
|
|
targa_header.id_length = *fin++;
|
|
|
|
|
targa_header.colormap_type = *fin++;
|
|
|
|
|
targa_header.image_type = *fin++;
|
|
|
|
|
|
|
|
|
|
targa_header.colormap_index = BuffLittleShort( fin ); fin += 2;
|
|
|
|
|
targa_header.colormap_length = BuffLittleShort( fin ); fin += 2;
|
|
|
|
|
targa_header.colormap_size = *fin++;
|
|
|
|
|
targa_header.x_origin = BuffLittleShort( fin ); fin += 2;
|
|
|
|
|
targa_header.y_origin = BuffLittleShort( fin ); fin += 2;
|
|
|
|
|
targa_header.width = image_width = BuffLittleShort( fin ); fin += 2;
|
|
|
|
|
targa_header.height = image_height = BuffLittleShort( fin );fin += 2;
|
|
|
|
|
|
|
|
|
|
if(!Image_ValidSize( name )) return false;
|
|
|
|
|
|
|
|
|
|
image_num_layers = 1;
|
|
|
|
|
image_num_mips = 1;
|
|
|
|
|
image_type = PF_RGBA_32; // always exctracted to 32-bit buffer
|
|
|
|
|
|
|
|
|
|
targa_header.pixel_size = *fin++;
|
|
|
|
|
targa_header.attributes = *fin++;
|
|
|
|
|
// end of header
|
|
|
|
|
|
|
|
|
|
// skip TARGA image comment ( usually 0 bytes )
|
|
|
|
|
fin += targa_header.id_length;
|
|
|
|
|
|
|
|
|
|
// read/skip the colormap if present (note: according to the TARGA spec it
|
|
|
|
|
// can be present even on truecolor or greyscale images, just not used by
|
|
|
|
|
// the image data)
|
|
|
|
|
if( targa_header.colormap_type )
|
|
|
|
|
{
|
2008-08-06 22:00:00 +02:00
|
|
|
|
if( targa_header.colormap_length > 256 )
|
|
|
|
|
{
|
|
|
|
|
MsgDev( D_ERROR, "Image_LoadTGA: only up to 256 colormap_length supported\n" );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if( targa_header.colormap_index )
|
|
|
|
|
{
|
|
|
|
|
MsgDev( D_ERROR, "Image_LoadTGA: colormap_index not supported\n" );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if( targa_header.colormap_size == 24 )
|
|
|
|
|
{
|
|
|
|
|
for( x = 0; x < targa_header.colormap_length; x++ )
|
|
|
|
|
{
|
|
|
|
|
palette[x*4+2] = *fin++;
|
|
|
|
|
palette[x*4+1] = *fin++;
|
|
|
|
|
palette[x*4+0] = *fin++;
|
|
|
|
|
palette[x*4+3] = 255;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if( targa_header.colormap_size == 32 )
|
|
|
|
|
{
|
|
|
|
|
for( x = 0; x < targa_header.colormap_length; x++ )
|
|
|
|
|
{
|
|
|
|
|
palette[x*4+2] = *fin++;
|
|
|
|
|
palette[x*4+1] = *fin++;
|
|
|
|
|
palette[x*4+0] = *fin++;
|
|
|
|
|
palette[x*4+3] = *fin++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Msg("Image_LoadTGA: Only 32 and 24 bit colormap_size supported\n");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2008-01-28 22:00:00 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// check our pixel_size restrictions according to image_type
|
|
|
|
|
switch (targa_header.image_type & ~8)
|
|
|
|
|
{
|
|
|
|
|
case 2:
|
|
|
|
|
if( targa_header.pixel_size != 24 && targa_header.pixel_size != 32 )
|
|
|
|
|
{
|
|
|
|
|
MsgDev(D_ERROR, "LoadTGA: (%s) have unsupported pixel size '%d', for type '%d'\n", name, targa_header.pixel_size, targa_header.image_type );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2008-08-06 22:00:00 +02:00
|
|
|
|
case 3:
|
|
|
|
|
// set up a palette to make the loader easier
|
|
|
|
|
for( x = 0; x < 256; x++ )
|
|
|
|
|
{
|
|
|
|
|
palette[x*4+2] = x;
|
|
|
|
|
palette[x*4+1] = x;
|
|
|
|
|
palette[x*4+0] = x;
|
|
|
|
|
palette[x*4+3] = 255;
|
|
|
|
|
}
|
|
|
|
|
// fall through to colormap case
|
|
|
|
|
case 1:
|
|
|
|
|
if( targa_header.pixel_size != 8 )
|
|
|
|
|
{
|
|
|
|
|
MsgDev( D_ERROR, "Image_LoadTGA: only 8bit pixel size for type 1, 3, 9, and 11 images supported\n" );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2008-01-28 22:00:00 +01:00
|
|
|
|
default:
|
2008-08-06 22:00:00 +02:00
|
|
|
|
MsgDev(D_ERROR, "Image_LoadTGA: (%s) is unsupported image type '%i'\n", name, targa_header.image_type );
|
2008-01-28 22:00:00 +01:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( targa_header.attributes & 0x10 )
|
|
|
|
|
{
|
2008-08-06 22:00:00 +02:00
|
|
|
|
MsgDev( D_WARN, "Image_LoadTGA: (%s): top right and bottom right origin are not supported\n", name );
|
2008-01-28 22:00:00 +01:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// number of attribute bits per pixel, we only support 0 or 8
|
|
|
|
|
alphabits = targa_header.attributes & 0x0F;
|
|
|
|
|
if( alphabits != 8 && alphabits != 0 )
|
|
|
|
|
{
|
2008-06-29 22:00:00 +02:00
|
|
|
|
MsgDev( D_WARN, "LoadTGA: (%s) have invalid attributes '%i'\n", name, alphabits );
|
2008-01-28 22:00:00 +01:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
image_flags |= alphabits ? IMAGE_HAS_ALPHA : 0;
|
|
|
|
|
image_size = image_width * image_height * 4;
|
2008-08-06 22:00:00 +02:00
|
|
|
|
image_rgba = Mem_Alloc( Sys.imagepool, image_size );
|
2008-01-28 22:00:00 +01:00
|
|
|
|
|
|
|
|
|
// If bit 5 of attributes isn't set, the image has been stored from bottom to top
|
|
|
|
|
if(!(targa_header.attributes & 0x20))
|
|
|
|
|
{
|
|
|
|
|
pixbuf = image_rgba + (image_height - 1) * image_width * 4;
|
|
|
|
|
row_inc = -image_width * 4 * 2;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
pixbuf = image_rgba;
|
|
|
|
|
row_inc = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
x = y = 0;
|
|
|
|
|
red = green = blue = alpha = 255;
|
|
|
|
|
pix_inc = 1;
|
|
|
|
|
if((targa_header.image_type & ~8) == 2) pix_inc = targa_header.pixel_size / 8;
|
|
|
|
|
|
|
|
|
|
switch( targa_header.image_type )
|
|
|
|
|
{
|
2008-08-06 22:00:00 +02:00
|
|
|
|
case 1: // colormapped, uncompressed
|
|
|
|
|
case 3: // greyscale, uncompressed
|
|
|
|
|
if( fin + image_width * image_height * pix_inc > enddata )
|
|
|
|
|
break;
|
|
|
|
|
for( y = 0; y < image_height; y++, pixbuf += row_inc )
|
|
|
|
|
{
|
|
|
|
|
for( x = 0; x < image_width; x++ )
|
|
|
|
|
{
|
|
|
|
|
p = palette + *fin++ * 4;
|
|
|
|
|
*pixbuf++ = p[0];
|
|
|
|
|
*pixbuf++ = p[1];
|
|
|
|
|
*pixbuf++ = p[2];
|
|
|
|
|
*pixbuf++ = p[3];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
2008-01-28 22:00:00 +01:00
|
|
|
|
case 2:
|
|
|
|
|
// BGR or BGRA, uncompressed
|
|
|
|
|
if(fin + image_width * image_height * pix_inc > enddata)
|
|
|
|
|
break;
|
|
|
|
|
if(targa_header.pixel_size == 32 && alphabits)
|
|
|
|
|
{
|
|
|
|
|
for (y = 0;y < image_height;y++, pixbuf += row_inc)
|
|
|
|
|
{
|
|
|
|
|
for (x = 0;x < image_width;x++, fin += pix_inc)
|
|
|
|
|
{
|
|
|
|
|
*pixbuf++ = fin[2];
|
|
|
|
|
*pixbuf++ = fin[1];
|
|
|
|
|
*pixbuf++ = fin[0];
|
|
|
|
|
*pixbuf++ = fin[3];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else // 24 bits
|
|
|
|
|
{
|
|
|
|
|
for (y = 0;y < image_height; y++, pixbuf += row_inc)
|
|
|
|
|
{
|
|
|
|
|
for (x = 0;x < image_width; x++, fin += pix_inc)
|
|
|
|
|
{
|
|
|
|
|
*pixbuf++ = fin[2];
|
|
|
|
|
*pixbuf++ = fin[1];
|
|
|
|
|
*pixbuf++ = fin[0];
|
|
|
|
|
*pixbuf++ = 255;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
2008-08-06 22:00:00 +02:00
|
|
|
|
case 9: // colormapped, RLE
|
|
|
|
|
case 11: // greyscale, RLE
|
|
|
|
|
for( y = 0; y < image_height; y++, pixbuf += row_inc )
|
|
|
|
|
{
|
|
|
|
|
for( x = 0; x < image_width; )
|
|
|
|
|
{
|
|
|
|
|
if( fin >= enddata ) break; // error - truncated file
|
|
|
|
|
runlen = *fin++;
|
|
|
|
|
if( runlen & 0x80 )
|
|
|
|
|
{
|
|
|
|
|
// RLE - all pixels the same color
|
|
|
|
|
runlen += 1 - 0x80;
|
|
|
|
|
if( fin + pix_inc > enddata )
|
|
|
|
|
break; // error - truncated file
|
|
|
|
|
if( x + runlen > image_width )
|
|
|
|
|
break; // error - line exceeds width
|
|
|
|
|
p = palette + *fin++ * 4;
|
|
|
|
|
red = p[0];
|
|
|
|
|
green = p[1];
|
|
|
|
|
blue = p[2];
|
|
|
|
|
alpha = p[3];
|
|
|
|
|
for( ;runlen--; x++ )
|
|
|
|
|
{
|
|
|
|
|
*pixbuf++ = red;
|
|
|
|
|
*pixbuf++ = green;
|
|
|
|
|
*pixbuf++ = blue;
|
|
|
|
|
*pixbuf++ = alpha;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// uncompressed - all pixels different color
|
|
|
|
|
runlen++;
|
|
|
|
|
if( fin + pix_inc * runlen > enddata )
|
|
|
|
|
break; // error - truncated file
|
|
|
|
|
if( x + runlen > image_width )
|
|
|
|
|
break; // error - line exceeds width
|
|
|
|
|
for( ;runlen--; x++ )
|
|
|
|
|
{
|
|
|
|
|
p = palette + *fin++ * 4;
|
|
|
|
|
*pixbuf++ = p[0];
|
|
|
|
|
*pixbuf++ = p[1];
|
|
|
|
|
*pixbuf++ = p[2];
|
|
|
|
|
*pixbuf++ = p[3];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
2008-01-28 22:00:00 +01:00
|
|
|
|
case 10:
|
|
|
|
|
// BGR or BGRA, RLE
|
|
|
|
|
if( targa_header.pixel_size == 32 && alphabits )
|
|
|
|
|
{
|
|
|
|
|
for( y = 0; y < image_height; y++, pixbuf += row_inc )
|
|
|
|
|
{
|
|
|
|
|
for (x = 0; x < image_width; )
|
|
|
|
|
{
|
|
|
|
|
if( fin >= enddata ) break; // error - truncated file
|
|
|
|
|
runlen = *fin++;
|
|
|
|
|
if( runlen & 0x80 )
|
|
|
|
|
{
|
|
|
|
|
// RLE - all pixels the same color
|
|
|
|
|
runlen += 1 - 0x80;
|
|
|
|
|
if( fin + pix_inc > enddata ) break; // error - truncated file
|
|
|
|
|
if( x + runlen > image_width) break; // error - line exceeds width
|
|
|
|
|
red = fin[2];
|
|
|
|
|
green = fin[1];
|
|
|
|
|
blue = fin[0];
|
|
|
|
|
alpha = fin[3];
|
|
|
|
|
fin += pix_inc;
|
|
|
|
|
for( ; runlen--; x++)
|
|
|
|
|
{
|
|
|
|
|
*pixbuf++ = red;
|
|
|
|
|
*pixbuf++ = green;
|
|
|
|
|
*pixbuf++ = blue;
|
|
|
|
|
*pixbuf++ = alpha;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// uncompressed - all pixels different color
|
|
|
|
|
runlen++;
|
|
|
|
|
if( fin + pix_inc * runlen > enddata ) break; // error - truncated file
|
|
|
|
|
if( x + runlen > image_width ) break; // error - line exceeds width
|
|
|
|
|
for( ;runlen--; x++, fin += pix_inc )
|
|
|
|
|
{
|
|
|
|
|
*pixbuf++ = fin[2];
|
|
|
|
|
*pixbuf++ = fin[1];
|
|
|
|
|
*pixbuf++ = fin[0];
|
|
|
|
|
*pixbuf++ = fin[3];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
for( y = 0; y < image_height; y++, pixbuf += row_inc )
|
|
|
|
|
{
|
|
|
|
|
for (x = 0; x < image_width; )
|
|
|
|
|
{
|
|
|
|
|
if( fin >= enddata ) break; // error - truncated file
|
|
|
|
|
runlen = *fin++;
|
|
|
|
|
if( runlen & 0x80 )
|
|
|
|
|
{
|
|
|
|
|
// RLE - all pixels the same color
|
|
|
|
|
runlen += 1 - 0x80;
|
|
|
|
|
if( fin + pix_inc > enddata ) break; // error - truncated file
|
|
|
|
|
if( x + runlen > image_width )break; // error - line exceeds width
|
|
|
|
|
red = fin[2];
|
|
|
|
|
green = fin[1];
|
|
|
|
|
blue = fin[0];
|
|
|
|
|
alpha = 255;
|
|
|
|
|
fin += pix_inc;
|
|
|
|
|
for( ;runlen--; x++ )
|
|
|
|
|
{
|
|
|
|
|
*pixbuf++ = red;
|
|
|
|
|
*pixbuf++ = green;
|
|
|
|
|
*pixbuf++ = blue;
|
|
|
|
|
*pixbuf++ = alpha;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// uncompressed - all pixels different color
|
|
|
|
|
runlen++;
|
|
|
|
|
if( fin + pix_inc * runlen > enddata ) break; // error - truncated file
|
|
|
|
|
if( x + runlen > image_width ) break; // error - line exceeds width
|
|
|
|
|
for ( ; runlen--; x++, fin += pix_inc)
|
|
|
|
|
{
|
|
|
|
|
*pixbuf++ = fin[2];
|
|
|
|
|
*pixbuf++ = fin[1];
|
|
|
|
|
*pixbuf++ = fin[0];
|
|
|
|
|
*pixbuf++ = 255;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
// unknown image_type
|
|
|
|
|
default: return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
=============
|
2008-08-06 22:00:00 +02:00
|
|
|
|
Image_SaveTGA
|
2008-01-28 22:00:00 +01:00
|
|
|
|
=============
|
|
|
|
|
*/
|
|
|
|
|
bool Image_SaveTGA( const char *name, rgbdata_t *pix, int saveformat )
|
|
|
|
|
{
|
|
|
|
|
int y, outsize, pixel_size;
|
|
|
|
|
const byte *bufend, *in;
|
|
|
|
|
byte *buffer, *out;
|
|
|
|
|
const char *comment = "Generated by Xash ImageLib\0";
|
|
|
|
|
|
|
|
|
|
if(FS_FileExists(name)) return false; // already existed
|
2008-08-06 22:00:00 +02:00
|
|
|
|
if( pix->flags & IMAGE_HAS_ALPHA ) outsize = pix->width * pix->height * 4 + 18 + com_strlen( comment );
|
|
|
|
|
else outsize = pix->width * pix->height * 3 + 18 + com_strlen( comment );
|
2008-01-28 22:00:00 +01:00
|
|
|
|
|
2008-08-06 22:00:00 +02:00
|
|
|
|
buffer = (byte *)Mem_Alloc( Sys.imagepool, outsize );
|
2008-01-28 22:00:00 +01:00
|
|
|
|
memset( buffer, 0, 18 );
|
|
|
|
|
|
|
|
|
|
// prepare header
|
2008-08-06 22:00:00 +02:00
|
|
|
|
buffer[0] = com_strlen( comment ); // tga comment length
|
2008-01-28 22:00:00 +01:00
|
|
|
|
buffer[2] = 2; // uncompressed type
|
|
|
|
|
buffer[12] = (pix->width >> 0) & 0xFF;
|
|
|
|
|
buffer[13] = (pix->width >> 8) & 0xFF;
|
|
|
|
|
buffer[14] = (pix->height >> 0) & 0xFF;
|
|
|
|
|
buffer[15] = (pix->height >> 8) & 0xFF;
|
|
|
|
|
buffer[16] = ( pix->flags & IMAGE_HAS_ALPHA ) ? 32 : 24;
|
|
|
|
|
buffer[17] = ( pix->flags & IMAGE_HAS_ALPHA ) ? 8 : 0; // 8 bits of alpha
|
2008-08-06 22:00:00 +02:00
|
|
|
|
com_strncpy( buffer + 18, comment, com_strlen( comment ));
|
|
|
|
|
out = buffer + 18 + com_strlen( comment );
|
2008-01-28 22:00:00 +01:00
|
|
|
|
|
|
|
|
|
// get image description
|
|
|
|
|
switch( pix->type )
|
|
|
|
|
{
|
|
|
|
|
case PF_RGB_24_FLIP:
|
|
|
|
|
case PF_RGB_24: pixel_size = 3; break;
|
|
|
|
|
case PF_RGBA_32: pixel_size = 4; break;
|
|
|
|
|
default:
|
2008-06-29 22:00:00 +02:00
|
|
|
|
MsgDev( D_ERROR, "SaveTGA: unsupported image type %s\n", PFDesc[pix->type].name );
|
2008-01-28 22:00:00 +01:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// flip buffer
|
|
|
|
|
switch( pix->type )
|
|
|
|
|
{
|
|
|
|
|
case PF_RGB_24_FLIP:
|
|
|
|
|
// glReadPixels rotating image at 180 degrees, flip it
|
|
|
|
|
for (in = pix->buffer; in < pix->buffer + pix->width * pix->height * pixel_size; in += pixel_size)
|
|
|
|
|
{
|
|
|
|
|
*out++ = in[2];
|
|
|
|
|
*out++ = in[1];
|
|
|
|
|
*out++ = in[0];
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case PF_RGB_24:
|
|
|
|
|
case PF_RGBA_32:
|
|
|
|
|
// swap rgba to bgra and flip upside down
|
|
|
|
|
for (y = pix->height - 1; y >= 0; y--)
|
|
|
|
|
{
|
|
|
|
|
in = pix->buffer + y * pix->width * pixel_size;
|
|
|
|
|
bufend = in + pix->width * pixel_size;
|
|
|
|
|
for ( ;in < bufend; in += pixel_size)
|
|
|
|
|
{
|
|
|
|
|
*out++ = in[2];
|
|
|
|
|
*out++ = in[1];
|
|
|
|
|
*out++ = in[0];
|
|
|
|
|
if( pix->flags & IMAGE_HAS_ALPHA )
|
|
|
|
|
*out++ = in[3];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MsgDev(D_NOTE, "Writing %s[%d]\n", name, (pix->flags & IMAGE_HAS_ALPHA) ? 32 : 24 );
|
|
|
|
|
FS_WriteFile( name, buffer, outsize );
|
|
|
|
|
|
|
|
|
|
Mem_Free( buffer );
|
|
|
|
|
return true;
|
|
|
|
|
}
|