1200 lines
28 KiB
C++
1200 lines
28 KiB
C++
// Filename:- R_Image.cpp
|
|
//
|
|
// a pile of block-paste code from other codebases for basic image file stuff
|
|
|
|
#include "stdafx.h"
|
|
#include "includes.h"
|
|
#include "R_Common.h"
|
|
#include "jpeg_interface.h"
|
|
#include "png/png.h"
|
|
//
|
|
#include "R_Image.h"
|
|
|
|
|
|
static image_t* hashTable[FILE_HASH_SIZE];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
========================================================================
|
|
|
|
PCX files are used for 8 bit images
|
|
|
|
========================================================================
|
|
*/
|
|
|
|
typedef struct {
|
|
char manufacturer;
|
|
char version;
|
|
char encoding;
|
|
char bits_per_pixel;
|
|
unsigned short xmin,ymin,xmax,ymax;
|
|
unsigned short hres,vres;
|
|
unsigned char palette[48];
|
|
char reserved;
|
|
char color_planes;
|
|
unsigned short bytes_per_line;
|
|
unsigned short palette_type;
|
|
char filler[58];
|
|
unsigned char data; // unbounded
|
|
} pcx_t;
|
|
|
|
|
|
/*
|
|
========================================================================
|
|
|
|
TGA files are used for 24/32 bit images
|
|
|
|
========================================================================
|
|
*/
|
|
|
|
typedef struct _TargaHeader {
|
|
unsigned char id_length, colormap_type, image_type;
|
|
unsigned short colormap_index, colormap_length;
|
|
unsigned char colormap_size;
|
|
unsigned short x_origin, y_origin, width, height;
|
|
unsigned char pixel_size, attributes;
|
|
} TargaHeader;
|
|
|
|
|
|
|
|
|
|
/*
|
|
=========================================================
|
|
|
|
BMP LOADING
|
|
|
|
=========================================================
|
|
*/
|
|
typedef struct
|
|
{
|
|
char id[2];
|
|
unsigned long fileSize;
|
|
unsigned long reserved0;
|
|
unsigned long bitmapDataOffset;
|
|
unsigned long bitmapHeaderSize;
|
|
unsigned long width;
|
|
unsigned long height;
|
|
unsigned short planes;
|
|
unsigned short bitsPerPixel;
|
|
unsigned long compression;
|
|
unsigned long bitmapDataSize;
|
|
unsigned long hRes;
|
|
unsigned long vRes;
|
|
unsigned long colors;
|
|
unsigned long importantColors;
|
|
unsigned char palette[256][4];
|
|
} BMPHeader_t;
|
|
|
|
static void LoadBMP( const char *name, byte **pic, int *width, int *height )
|
|
{
|
|
int columns, rows, numPixels;
|
|
byte *pixbuf;
|
|
int row, column;
|
|
byte *buf_p;
|
|
byte *buffer;
|
|
int length;
|
|
BMPHeader_t bmpHeader;
|
|
byte *bmpRGBA;
|
|
|
|
*pic = NULL;
|
|
|
|
//
|
|
// load the file
|
|
//
|
|
length = ri.FS_ReadFile( ( char * ) name, (void **)&buffer);
|
|
if (!buffer) {
|
|
return;
|
|
}
|
|
|
|
buf_p = buffer;
|
|
|
|
bmpHeader.id[0] = *buf_p++;
|
|
bmpHeader.id[1] = *buf_p++;
|
|
bmpHeader.fileSize = LittleLong( * ( long * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.reserved0 = LittleLong( * ( long * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.bitmapDataOffset = LittleLong( * ( long * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.bitmapHeaderSize = LittleLong( * ( long * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.width = LittleLong( * ( long * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.height = LittleLong( * ( long * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.planes = LittleShort( * ( short * ) buf_p );
|
|
buf_p += 2;
|
|
bmpHeader.bitsPerPixel = LittleShort( * ( short * ) buf_p );
|
|
buf_p += 2;
|
|
bmpHeader.compression = LittleLong( * ( long * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.bitmapDataSize = LittleLong( * ( long * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.hRes = LittleLong( * ( long * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.vRes = LittleLong( * ( long * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.colors = LittleLong( * ( long * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.importantColors = LittleLong( * ( long * ) buf_p );
|
|
buf_p += 4;
|
|
|
|
memcpy( bmpHeader.palette, buf_p, sizeof( bmpHeader.palette ) );
|
|
|
|
if ( bmpHeader.bitsPerPixel == 8 )
|
|
buf_p += 1024;
|
|
|
|
if ( bmpHeader.id[0] != 'B' && bmpHeader.id[1] != 'M' )
|
|
{
|
|
ri.Error( ERR_DROP, "LoadBMP: only Windows-style BMP files supported (%s)\n", name );
|
|
}
|
|
if ( bmpHeader.fileSize != length )
|
|
{
|
|
ri.Error( ERR_DROP, "LoadBMP: header size does not match file size (%d vs. %d) (%s)\n", bmpHeader.fileSize, length, name );
|
|
}
|
|
if ( bmpHeader.compression != 0 )
|
|
{
|
|
ri.Error( ERR_DROP, "LoadBMP: only uncompressed BMP files supported (%s)\n", name );
|
|
}
|
|
if ( bmpHeader.bitsPerPixel < 8 )
|
|
{
|
|
ri.Error( ERR_DROP, "LoadBMP: monochrome and 4-bit BMP files not supported (%s)\n", name );
|
|
}
|
|
|
|
columns = bmpHeader.width;
|
|
rows = bmpHeader.height;
|
|
if ( rows < 0 )
|
|
rows = -rows;
|
|
numPixels = columns * rows;
|
|
|
|
if ( width )
|
|
*width = columns;
|
|
if ( height )
|
|
*height = rows;
|
|
|
|
bmpRGBA = (unsigned char *)ri.Malloc( numPixels * 4 );
|
|
*pic = bmpRGBA;
|
|
|
|
|
|
for ( row = rows-1; row >= 0; row-- )
|
|
{
|
|
pixbuf = bmpRGBA + row*columns*4;
|
|
|
|
for ( column = 0; column < columns; column++ )
|
|
{
|
|
unsigned char red, green, blue, alpha;
|
|
int palIndex;
|
|
unsigned short shortPixel;
|
|
|
|
switch ( bmpHeader.bitsPerPixel )
|
|
{
|
|
case 8:
|
|
palIndex = *buf_p++;
|
|
*pixbuf++ = bmpHeader.palette[palIndex][2];
|
|
*pixbuf++ = bmpHeader.palette[palIndex][1];
|
|
*pixbuf++ = bmpHeader.palette[palIndex][0];
|
|
*pixbuf++ = 0xff;
|
|
break;
|
|
case 16:
|
|
shortPixel = * ( unsigned short * ) pixbuf;
|
|
pixbuf += 2;
|
|
*pixbuf++ = ( shortPixel & ( 31 << 10 ) ) >> 7;
|
|
*pixbuf++ = ( shortPixel & ( 31 << 5 ) ) >> 2;
|
|
*pixbuf++ = ( shortPixel & ( 31 ) ) << 3;
|
|
*pixbuf++ = 0xff;
|
|
break;
|
|
|
|
case 24:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = 255;
|
|
break;
|
|
case 32:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
alpha = *buf_p++;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = alpha;
|
|
break;
|
|
default:
|
|
ri.Error( ERR_DROP, "LoadBMP: illegal pixel_size '%d' in file '%s'\n", bmpHeader.bitsPerPixel, name );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ri.FS_FreeFile( buffer );
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
typedef struct _TargaHeader {
|
|
unsigned char id_length, colormap_type, image_type;
|
|
unsigned short colormap_index, colormap_length;
|
|
unsigned char colormap_size;
|
|
unsigned short x_origin, y_origin, width, height;
|
|
unsigned char pixel_size, attributes;
|
|
} TargaHeader;
|
|
|
|
correct
|
|
|
|
000000 00 00 02 00 00 00 00 18 00 00 00 00 76 02 43 01 ............v.C.
|
|
000010 18 20 ED D9 C9 EC D9 C8 EC D9 C7 EA D7 C7 EA D7 . íÙÉìÙÈìÙÇê×Çê×
|
|
|
|
bad
|
|
000000 00 00 02 00 00 00 00 00 00 00 00 00 76 02 43 01 ............v.C.
|
|
000010 20 00 ED D9 C9 FF EC D9 C8 FF EC D9 C7 FF EA D7 .íÙÉÿìÙÈÿìÙÇÿê×
|
|
|
|
|
|
#pragma pack(push,1)
|
|
typedef struct
|
|
{
|
|
byte byIDFieldLength; // must be 0
|
|
byte byColourmapType; // 0 = truecolour, 1 = paletted, else bad
|
|
byte byImageType; // 1 = colour mapped (palette), uncompressed, 2 = truecolour, uncompressed, else bad
|
|
word w1stColourMapEntry; // must be 0
|
|
word wColourMapLength; // 256 for 8-bit palettes, else 0 for true-colour
|
|
byte byColourMapEntrySize; // 24 for 8-bit palettes, else 0 for true-colour
|
|
word wImageXOrigin; // ignored
|
|
word wImageYOrigin; // ignored
|
|
word wImageWidth; // in pixels
|
|
word wImageHeight; // in pixels
|
|
byte byImagePlanes; // bits per pixel (8 for paletted, else 24 for true-colour)
|
|
byte byScanLineOrder; // Image descriptor bytes
|
|
// bits 0-3 = # attr bits (alpha chan)
|
|
// bits 4-5 = pixel order/dir
|
|
// bits 6-7 scan line interleave (00b=none,01b=2way interleave,10b=4way)
|
|
} TGAHEADER, *LPTGAHEADER;
|
|
#pragma pack(pop)
|
|
|
|
*/
|
|
bool TGA_Write(LPCSTR psFullPathedFilename, byte *pPixels, int iWidth, int iHeight, int iPlanes)
|
|
{
|
|
byte *pbBuffer = NULL;
|
|
int iPixelBytes = (iPlanes == 24) ? 3:4;
|
|
int iTotalPixelBytes = iWidth * iHeight * iPixelBytes;
|
|
int iTotalBufferBytes = iTotalPixelBytes + 18;
|
|
|
|
assert(iPlanes == 24 || iPlanes == 32);
|
|
|
|
pbBuffer = (byte *) ri.Hunk_AllocateTempMemory(iTotalBufferBytes);
|
|
memset (pbBuffer, 0, 18);
|
|
pbBuffer[2] = 2; // uncompressed type
|
|
pbBuffer[12] = iWidth & 255;
|
|
pbBuffer[13] = iWidth >> 8;
|
|
pbBuffer[14] = iHeight & 255;
|
|
pbBuffer[15] = iHeight >> 8;
|
|
pbBuffer[16] = iPlanes; // pixel size
|
|
pbBuffer[17] = 0x00; // scan line order, tells TGA-legal packages that it's upside down. ID code ignore it.
|
|
|
|
memcpy(pbBuffer+18, pPixels, iTotalPixelBytes );
|
|
|
|
// turn it upside down to match TGA default, because ID loader ignores scanline attr flags... (sigh)
|
|
//
|
|
int iScanLineBytes = iWidth * iPixelBytes;
|
|
byte *pbScanLine = (byte *) ri.Hunk_AllocateTempMemory(iScanLineBytes);
|
|
byte *pbSrc = &pbBuffer[18];
|
|
byte *pbDst = &pbBuffer[(18+iTotalPixelBytes)-iScanLineBytes];
|
|
for (int iYFlip=0; iYFlip < iHeight/2; iYFlip++)
|
|
{
|
|
memcpy(pbScanLine,pbSrc,iScanLineBytes);
|
|
memcpy(pbSrc,pbDst,iScanLineBytes);
|
|
memcpy(pbDst,pbScanLine,iScanLineBytes);
|
|
|
|
pbSrc+=iScanLineBytes;
|
|
pbDst-=iScanLineBytes;
|
|
}
|
|
ri.Hunk_FreeTempMemory( pbScanLine );
|
|
|
|
// swap rgb to bgr...
|
|
//
|
|
for (int i=18 ; i<iTotalBufferBytes ; i += iPixelBytes)
|
|
{
|
|
byte r = pbBuffer[i];
|
|
pbBuffer[i] = pbBuffer[i+2];
|
|
pbBuffer[i+2] = r;
|
|
}
|
|
|
|
// ri.FS_WriteFile( psFullPathedFilename, pbBuffer, iTotalBufferBytes );
|
|
int iLength = SaveFile (psFullPathedFilename, pbBuffer, iTotalBufferBytes);
|
|
|
|
ri.Hunk_FreeTempMemory( pbBuffer );
|
|
|
|
return !!(iLength != -1);
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
LoadTGA
|
|
=============
|
|
*/
|
|
/*
|
|
// ID's TGA loader...
|
|
//
|
|
void LoadTGA ( const char *name, byte **pic, int *width, int *height)
|
|
{
|
|
int columns, rows, numPixels;
|
|
byte *pixbuf;
|
|
int row, column;
|
|
byte *buf_p;
|
|
byte *buffer;
|
|
TargaHeader targa_header;
|
|
byte *pRGBA;
|
|
|
|
*pic = NULL;
|
|
|
|
//
|
|
// load the file
|
|
//
|
|
ri.FS_ReadFile ( ( char * ) name, (void **)&buffer);
|
|
if (!buffer) {
|
|
return;
|
|
}
|
|
|
|
buf_p = buffer;
|
|
|
|
targa_header.id_length = *buf_p++;
|
|
targa_header.colormap_type = *buf_p++;
|
|
targa_header.image_type = *buf_p++;
|
|
|
|
targa_header.colormap_index = LittleShort ( *(short *)buf_p );
|
|
buf_p += 2;
|
|
targa_header.colormap_length = LittleShort ( *(short *)buf_p );
|
|
buf_p += 2;
|
|
targa_header.colormap_size = *buf_p++;
|
|
targa_header.x_origin = LittleShort ( *(short *)buf_p );
|
|
buf_p += 2;
|
|
targa_header.y_origin = LittleShort ( *(short *)buf_p );
|
|
buf_p += 2;
|
|
targa_header.width = LittleShort ( *(short *)buf_p );
|
|
buf_p += 2;
|
|
targa_header.height = LittleShort ( *(short *)buf_p );
|
|
buf_p += 2;
|
|
targa_header.pixel_size = *buf_p++;
|
|
targa_header.attributes = *buf_p++;
|
|
|
|
if (targa_header.image_type!=2
|
|
&& targa_header.image_type!=10
|
|
&& targa_header.image_type != 3 )
|
|
{
|
|
ri.Error (ERR_DROP, "LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported\n");
|
|
}
|
|
|
|
if ( targa_header.colormap_type != 0 )
|
|
{
|
|
ri.Error( ERR_DROP, "LoadTGA: colormaps not supported\n" );
|
|
}
|
|
|
|
if ( ( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 ) && targa_header.image_type != 3 )
|
|
{
|
|
ri.Error (ERR_DROP, "LoadTGA: Only 32 or 24 bit images supported (no colormaps)\n");
|
|
}
|
|
|
|
columns = targa_header.width;
|
|
rows = targa_header.height;
|
|
numPixels = columns * rows;
|
|
|
|
if (width)
|
|
*width = columns;
|
|
if (height)
|
|
*height = rows;
|
|
|
|
pRGBA = (unsigned char *)ri.Malloc (numPixels*4);
|
|
*pic = pRGBA;
|
|
|
|
if (targa_header.id_length != 0)
|
|
buf_p += targa_header.id_length; // skip TARGA image comment
|
|
|
|
if ( targa_header.image_type==2 || targa_header.image_type == 3 )
|
|
{
|
|
// Uncompressed RGB or gray scale image
|
|
for(row=rows-1; row>=0; row--)
|
|
{
|
|
pixbuf = pRGBA + row*columns*4;
|
|
for(column=0; column<columns; column++)
|
|
{
|
|
unsigned char red,green,blue,alphabyte;
|
|
switch (targa_header.pixel_size)
|
|
{
|
|
|
|
case 8:
|
|
blue = *buf_p++;
|
|
green = blue;
|
|
red = blue;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = 255;
|
|
break;
|
|
|
|
case 24:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = 255;
|
|
break;
|
|
case 32:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
alphabyte = *buf_p++;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = alphabyte;
|
|
break;
|
|
default:
|
|
ri.Error( ERR_DROP, "LoadTGA: illegal pixel_size '%d' in file '%s'\n", targa_header.pixel_size, name );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (targa_header.image_type==10) { // Runlength encoded RGB images
|
|
unsigned char red,green,blue,alphabyte,packetHeader,packetSize,j;
|
|
|
|
red = 0;
|
|
green = 0;
|
|
blue = 0;
|
|
alphabyte = 0xff;
|
|
|
|
for(row=rows-1; row>=0; row--) {
|
|
pixbuf = pRGBA + row*columns*4;
|
|
for(column=0; column<columns; ) {
|
|
packetHeader= *buf_p++;
|
|
packetSize = 1 + (packetHeader & 0x7f);
|
|
if (packetHeader & 0x80) { // run-length packet
|
|
switch (targa_header.pixel_size) {
|
|
case 24:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
alphabyte = 255;
|
|
break;
|
|
case 32:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
alphabyte = *buf_p++;
|
|
break;
|
|
default:
|
|
ri.Error( ERR_DROP, "LoadTGA: illegal pixel_size '%d' in file '%s'\n", targa_header.pixel_size, name );
|
|
break;
|
|
}
|
|
|
|
for(j=0;j<packetSize;j++) {
|
|
*pixbuf++=red;
|
|
*pixbuf++=green;
|
|
*pixbuf++=blue;
|
|
*pixbuf++=alphabyte;
|
|
column++;
|
|
if (column==columns) { // run spans across rows
|
|
column=0;
|
|
if (row>0)
|
|
row--;
|
|
else
|
|
goto breakOut;
|
|
pixbuf = pRGBA + row*columns*4;
|
|
}
|
|
}
|
|
}
|
|
else { // non run-length packet
|
|
for(j=0;j<packetSize;j++) {
|
|
switch (targa_header.pixel_size) {
|
|
case 24:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = 255;
|
|
break;
|
|
case 32:
|
|
blue = *buf_p++;
|
|
green = *buf_p++;
|
|
red = *buf_p++;
|
|
alphabyte = *buf_p++;
|
|
*pixbuf++ = red;
|
|
*pixbuf++ = green;
|
|
*pixbuf++ = blue;
|
|
*pixbuf++ = alphabyte;
|
|
break;
|
|
default:
|
|
ri.Error( ERR_DROP, "LoadTGA: illegal pixel_size '%d' in file '%s'\n", targa_header.pixel_size, name );
|
|
break;
|
|
}
|
|
column++;
|
|
if (column==columns) { // pixel packet run spans across rows
|
|
column=0;
|
|
if (row>0)
|
|
row--;
|
|
else
|
|
goto breakOut;
|
|
pixbuf = pRGBA + row*columns*4;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
breakOut:;
|
|
}
|
|
}
|
|
|
|
ri.FS_FreeFile (buffer);
|
|
}
|
|
*/
|
|
|
|
// My TGA loader...
|
|
//
|
|
//---------------------------------------------------
|
|
#pragma pack(push,1)
|
|
typedef struct
|
|
{
|
|
byte byIDFieldLength; // must be 0
|
|
byte byColourmapType; // 0 = truecolour, 1 = paletted, else bad
|
|
byte byImageType; // 1 = colour mapped (palette), uncompressed, 2 = truecolour, uncompressed, else bad
|
|
word w1stColourMapEntry; // must be 0
|
|
word wColourMapLength; // 256 for 8-bit palettes, else 0 for true-colour
|
|
byte byColourMapEntrySize; // 24 for 8-bit palettes, else 0 for true-colour
|
|
word wImageXOrigin; // ignored
|
|
word wImageYOrigin; // ignored
|
|
word wImageWidth; // in pixels
|
|
word wImageHeight; // in pixels
|
|
byte byImagePlanes; // bits per pixel (8 for paletted, else 24 for true-colour)
|
|
byte byScanLineOrder; // Image descriptor bytes
|
|
// bits 0-3 = # attr bits (alpha chan)
|
|
// bits 4-5 = pixel order/dir
|
|
// bits 6-7 scan line interleave (00b=none,01b=2way interleave,10b=4way)
|
|
} TGAHeader_t;
|
|
#pragma pack(pop)
|
|
|
|
|
|
// *pic == pic, else NULL for failed.
|
|
//
|
|
// returns false if found but had a format error, else true for either OK or not-found (there's a reason for this)
|
|
//
|
|
|
|
void LoadTGA ( const char *name, byte **pic, int *width, int *height)
|
|
{
|
|
char sErrorString[1024];
|
|
bool bFormatErrors = false;
|
|
|
|
// these don't need to be declared or initialised until later, but the compiler whines that 'goto' skips them.
|
|
//
|
|
byte *pRGBA = NULL;
|
|
byte *pOut = NULL;
|
|
byte *pIn = NULL;
|
|
|
|
|
|
*pic = NULL;
|
|
|
|
#define TGA_FORMAT_ERROR(blah) {sprintf(sErrorString,blah); bFormatErrors = true; goto TGADone;}
|
|
//#define TGA_FORMAT_ERROR(blah) ri.Error( ERR_DROP, blah );
|
|
|
|
//
|
|
// load the file
|
|
//
|
|
byte *pTempLoadedBuffer = 0;
|
|
ri.FS_ReadFile ( ( char * ) name, (void **)&pTempLoadedBuffer);
|
|
if (!pTempLoadedBuffer) {
|
|
return;
|
|
}
|
|
|
|
TGAHeader_t *pHeader = (TGAHeader_t *) pTempLoadedBuffer;
|
|
|
|
if (pHeader->byColourmapType!=0)
|
|
{
|
|
TGA_FORMAT_ERROR("LoadTGA: colourmaps not supported\n" );
|
|
}
|
|
|
|
if (pHeader->byImageType != 2 && pHeader->byImageType != 3 && pHeader->byImageType != 10)
|
|
{
|
|
TGA_FORMAT_ERROR("LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RLE-RGB) images supported\n");
|
|
}
|
|
|
|
if (pHeader->w1stColourMapEntry != 0)
|
|
{
|
|
TGA_FORMAT_ERROR("LoadTGA: colourmaps not supported\n" );
|
|
}
|
|
|
|
if (pHeader->wColourMapLength !=0 && pHeader->wColourMapLength != 256)
|
|
{
|
|
TGA_FORMAT_ERROR("LoadTGA: ColourMapLength must be either 0 or 256\n" );
|
|
}
|
|
|
|
if (pHeader->byColourMapEntrySize != 0 && pHeader->byColourMapEntrySize != 24)
|
|
{
|
|
TGA_FORMAT_ERROR("LoadTGA: ColourMapEntrySize must be either 0 or 24\n" );
|
|
}
|
|
|
|
if ( ( pHeader->byImagePlanes != 24 && pHeader->byImagePlanes != 32) && (pHeader->byImagePlanes != 8 && pHeader->byImageType != 3))
|
|
{
|
|
TGA_FORMAT_ERROR("LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported\n");
|
|
}
|
|
|
|
if ((pHeader->byScanLineOrder&0x30)!=0x00 &&
|
|
(pHeader->byScanLineOrder&0x30)!=0x10 &&
|
|
(pHeader->byScanLineOrder&0x30)!=0x20 &&
|
|
(pHeader->byScanLineOrder&0x30)!=0x30
|
|
)
|
|
{
|
|
TGA_FORMAT_ERROR("LoadTGA: ScanLineOrder must be either 0x00,0x10,0x20, or 0x30\n");
|
|
}
|
|
|
|
|
|
|
|
// these last checks are so i can use ID's RLE-code. I don't dare fiddle with it or it'll probably break...
|
|
//
|
|
if ( pHeader->byImageType == 10)
|
|
{
|
|
if ((pHeader->byScanLineOrder & 0x30) != 0x00)
|
|
{
|
|
TGA_FORMAT_ERROR("LoadTGA: RLE-RGB Images (type 10) must be in bottom-to-top format\n");
|
|
}
|
|
if (pHeader->byImagePlanes != 24 && pHeader->byImagePlanes != 32) // probably won't happen, but avoids compressed greyscales?
|
|
{
|
|
TGA_FORMAT_ERROR("LoadTGA: RLE-RGB Images (type 10) must be 24 or 32 bit\n");
|
|
}
|
|
}
|
|
|
|
// now read the actual bitmap in...
|
|
//
|
|
// Image descriptor bytes
|
|
// bits 0-3 = # attr bits (alpha chan)
|
|
// bits 4-5 = pixel order/dir
|
|
// bits 6-7 scan line interleave (00b=none,01b=2way interleave,10b=4way)
|
|
//
|
|
int iYStart,iXStart,iYStep,iXStep;
|
|
|
|
switch(pHeader->byScanLineOrder & 0x30)
|
|
{
|
|
case 0x00: // left to right, bottom to top
|
|
|
|
iXStart = 0;
|
|
iXStep = 1;
|
|
|
|
iYStart = pHeader->wImageHeight-1;
|
|
iYStep = -1;
|
|
|
|
break;
|
|
|
|
case 0x10: // right to left, bottom to top
|
|
|
|
iXStart = pHeader->wImageWidth-1;
|
|
iXStep = -1;
|
|
|
|
iYStart = pHeader->wImageHeight-1;
|
|
iYStep = -1;
|
|
|
|
break;
|
|
|
|
case 0x20: // left to right, top to bottom
|
|
|
|
iXStart = 0;
|
|
iXStep = 1;
|
|
|
|
iYStart = 0;
|
|
iYStep = 1;
|
|
|
|
break;
|
|
|
|
case 0x30: // right to left, top to bottom
|
|
|
|
iXStart = pHeader->wImageWidth-1;
|
|
iXStep = -1;
|
|
|
|
iYStart = 0;
|
|
iYStep = 1;
|
|
|
|
break;
|
|
}
|
|
|
|
// feed back the results...
|
|
//
|
|
if (width)
|
|
*width = pHeader->wImageWidth;
|
|
if (height)
|
|
*height = pHeader->wImageHeight;
|
|
|
|
pRGBA = (byte *) ri.Malloc (pHeader->wImageWidth * pHeader->wImageHeight * 4);
|
|
*pic = pRGBA;
|
|
pOut = pRGBA;
|
|
pIn = pTempLoadedBuffer + sizeof(*pHeader);
|
|
|
|
// I don't know if this ID-thing here is right, since comments that I've seen are at the end of the file,
|
|
// with a zero in this field. However, may as well...
|
|
//
|
|
if (pHeader->byIDFieldLength != 0)
|
|
pIn += pHeader->byIDFieldLength; // skip TARGA image comment
|
|
|
|
byte red,green,blue,alpha;
|
|
|
|
if ( pHeader->byImageType == 2 || pHeader->byImageType == 3 ) // RGB or greyscale
|
|
{
|
|
for (int y=iYStart, iYCount=0; iYCount<pHeader->wImageHeight; y+=iYStep, iYCount++)
|
|
{
|
|
pOut = pRGBA + y * pHeader->wImageWidth *4;
|
|
for (int x=iXStart, iXCount=0; iXCount<pHeader->wImageWidth; x+=iXStep, iXCount++)
|
|
{
|
|
switch (pHeader->byImagePlanes)
|
|
{
|
|
case 8:
|
|
blue = *pIn++;
|
|
green = blue;
|
|
red = blue;
|
|
*pOut++ = red;
|
|
*pOut++ = green;
|
|
*pOut++ = blue;
|
|
*pOut++ = 255;
|
|
break;
|
|
|
|
case 24:
|
|
blue = *pIn++;
|
|
green = *pIn++;
|
|
red = *pIn++;
|
|
*pOut++ = red;
|
|
*pOut++ = green;
|
|
*pOut++ = blue;
|
|
*pOut++ = 255;
|
|
break;
|
|
|
|
case 32:
|
|
blue = *pIn++;
|
|
green = *pIn++;
|
|
red = *pIn++;
|
|
alpha = *pIn++;
|
|
*pOut++ = red;
|
|
*pOut++ = green;
|
|
*pOut++ = blue;
|
|
*pOut++ = alpha;
|
|
break;
|
|
|
|
default:
|
|
assert(0); // if we ever hit this, someone deleted a header check higher up
|
|
TGA_FORMAT_ERROR("LoadTGA: Image can only have 8, 24 or 32 planes for RGB/greyscale\n");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
if (pHeader->byImageType == 10) // RLE-RGB
|
|
{
|
|
// I've no idea if this stuff works, I normally reject RLE targas, but this is from ID's code
|
|
// so maybe I should try and support it...
|
|
//
|
|
byte packetHeader, packetSize, j;
|
|
|
|
for (int y = pHeader->wImageHeight-1; y >= 0; y--)
|
|
{
|
|
pOut = pRGBA + y * pHeader->wImageWidth *4;
|
|
for (int x=0; x<pHeader->wImageWidth;)
|
|
{
|
|
packetHeader = *pIn++;
|
|
packetSize = 1 + (packetHeader & 0x7f);
|
|
if (packetHeader & 0x80) // run-length packet
|
|
{
|
|
switch (pHeader->byImagePlanes)
|
|
{
|
|
case 24:
|
|
|
|
blue = *pIn++;
|
|
green = *pIn++;
|
|
red = *pIn++;
|
|
alpha = 255;
|
|
break;
|
|
|
|
case 32:
|
|
|
|
blue = *pIn++;
|
|
green = *pIn++;
|
|
red = *pIn++;
|
|
alpha = *pIn++;
|
|
break;
|
|
|
|
default:
|
|
assert(0); // if we ever hit this, someone deleted a header check higher up
|
|
TGA_FORMAT_ERROR("LoadTGA: RLE-RGB can only have 24 or 32 planes\n");
|
|
break;
|
|
}
|
|
|
|
for (j=0; j<packetSize; j++)
|
|
{
|
|
*pOut++ = red;
|
|
*pOut++ = green;
|
|
*pOut++ = blue;
|
|
*pOut++ = alpha;
|
|
x++;
|
|
if (x == pHeader->wImageWidth) // run spans across rows
|
|
{
|
|
x = 0;
|
|
if (y > 0)
|
|
y--;
|
|
else
|
|
goto breakOut;
|
|
pOut = pRGBA + y * pHeader->wImageWidth * 4;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{ // non run-length packet
|
|
|
|
for (j=0; j<packetSize; j++)
|
|
{
|
|
switch (pHeader->byImagePlanes)
|
|
{
|
|
case 24:
|
|
|
|
blue = *pIn++;
|
|
green = *pIn++;
|
|
red = *pIn++;
|
|
*pOut++ = red;
|
|
*pOut++ = green;
|
|
*pOut++ = blue;
|
|
*pOut++ = 255;
|
|
break;
|
|
|
|
case 32:
|
|
blue = *pIn++;
|
|
green = *pIn++;
|
|
red = *pIn++;
|
|
alpha = *pIn++;
|
|
*pOut++ = red;
|
|
*pOut++ = green;
|
|
*pOut++ = blue;
|
|
*pOut++ = alpha;
|
|
break;
|
|
|
|
default:
|
|
assert(0); // if we ever hit this, someone deleted a header check higher up
|
|
TGA_FORMAT_ERROR("LoadTGA: RLE-RGB can only have 24 or 32 planes\n");
|
|
break;
|
|
}
|
|
x++;
|
|
if (x == pHeader->wImageWidth) // pixel packet run spans across rows
|
|
{
|
|
x = 0;
|
|
if (y > 0)
|
|
y--;
|
|
else
|
|
goto breakOut;
|
|
pOut = pRGBA + y * pHeader->wImageWidth * 4;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
breakOut:;
|
|
}
|
|
}
|
|
|
|
TGADone:
|
|
|
|
ri.FS_FreeFile (pTempLoadedBuffer);
|
|
|
|
if (bFormatErrors)
|
|
{
|
|
ErrorBox(va("Error parsing file \"%s\"!\n\n",name, sErrorString));
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
LoadPCX
|
|
==============
|
|
*/
|
|
static void LoadPCX ( const char *filename, byte **pic, byte **palette, int *width, int *height)
|
|
{
|
|
byte *raw;
|
|
pcx_t *pcx;
|
|
int x, y;
|
|
int len;
|
|
int dataByte, runLength;
|
|
byte *out, *pix;
|
|
int xmax, ymax;
|
|
|
|
*pic = NULL;
|
|
*palette = NULL;
|
|
|
|
//
|
|
// load the file
|
|
//
|
|
len = ri.FS_ReadFile( ( char * ) filename, (void **)&raw);
|
|
if (!raw) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// parse the PCX file
|
|
//
|
|
pcx = (pcx_t *)raw;
|
|
raw = &pcx->data;
|
|
|
|
xmax = LittleShort(pcx->xmax);
|
|
ymax = LittleShort(pcx->ymax);
|
|
|
|
if (pcx->manufacturer != 0x0a
|
|
|| pcx->version != 5
|
|
|| pcx->encoding != 1
|
|
|| pcx->bits_per_pixel != 8
|
|
|| xmax >= 1024
|
|
|| ymax >= 1024)
|
|
{
|
|
ri.Printf (PRINT_ALL, "Bad pcx file %s (%i x %i) (%i x %i)\n", filename, xmax+1, ymax+1, pcx->xmax, pcx->ymax);
|
|
return;
|
|
}
|
|
|
|
out = (unsigned char *)ri.Malloc ( (ymax+1) * (xmax+1) );
|
|
|
|
*pic = out;
|
|
|
|
pix = out;
|
|
|
|
if (palette)
|
|
{
|
|
*palette = (unsigned char *)ri.Malloc(768);
|
|
memcpy (*palette, (byte *)pcx + len - 768, 768);
|
|
}
|
|
|
|
if (width)
|
|
*width = xmax+1;
|
|
if (height)
|
|
*height = ymax+1;
|
|
// FIXME: use bytes_per_line here?
|
|
|
|
for (y=0 ; y<=ymax ; y++, pix += xmax+1)
|
|
{
|
|
for (x=0 ; x<=xmax ; )
|
|
{
|
|
dataByte = *raw++;
|
|
|
|
if((dataByte & 0xC0) == 0xC0)
|
|
{
|
|
runLength = dataByte & 0x3F;
|
|
dataByte = *raw++;
|
|
}
|
|
else
|
|
runLength = 1;
|
|
|
|
while(runLength-- > 0)
|
|
pix[x++] = dataByte;
|
|
}
|
|
|
|
}
|
|
|
|
if ( raw - (byte *)pcx > len)
|
|
{
|
|
ri.Printf (PRINT_DEVELOPER, "PCX file %s was malformed", filename);
|
|
ri.Free (*pic);
|
|
*pic = NULL;
|
|
}
|
|
|
|
ri.FS_FreeFile (pcx);
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
LoadPCX32
|
|
==============
|
|
*/
|
|
static void LoadPCX32 ( const char *filename, byte **pic, int *width, int *height) {
|
|
byte *palette;
|
|
byte *pic8;
|
|
int i, c, p;
|
|
byte *pic32;
|
|
|
|
LoadPCX (filename, &pic8, &palette, width, height);
|
|
if (!pic8) {
|
|
*pic = NULL;
|
|
return;
|
|
}
|
|
|
|
c = (*width) * (*height);
|
|
pic32 = *pic = (unsigned char *)ri.Malloc(4 * c );
|
|
for (i = 0 ; i < c ; i++) {
|
|
p = pic8[i];
|
|
pic32[0] = palette[p*3];
|
|
pic32[1] = palette[p*3 + 1];
|
|
pic32[2] = palette[p*3 + 2];
|
|
pic32[3] = 255;
|
|
pic32 += 4;
|
|
}
|
|
|
|
ri.Free (pic8);
|
|
ri.Free (palette);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
=================
|
|
R_LoadImage
|
|
|
|
Loads any of the supported image types into a cannonical
|
|
32 bit format.
|
|
=================
|
|
*/
|
|
char g_sImageExtension[MAX_QPATH];
|
|
void R_LoadImage2( const char *name, byte **pic, int *width, int *height ) {
|
|
int len;
|
|
|
|
*pic = NULL;
|
|
*width = 0;
|
|
*height = 0;
|
|
|
|
len = strlen(name);
|
|
if (len<5) {
|
|
return;
|
|
}
|
|
|
|
char *psExt = strstr(name,".");
|
|
strcpy(g_sImageExtension,psExt?psExt:"");
|
|
|
|
if ( !Q_stricmp( name+len-4, ".tga" ) ) {
|
|
LoadTGA( name, pic, width, height ); // try tga first
|
|
if (!*pic) { //
|
|
char altname[MAX_QPATH]; // try jpg in place of tga
|
|
strcpy( altname, name );
|
|
len = strlen( altname );
|
|
altname[len-3] = 'j';
|
|
altname[len-2] = 'p';
|
|
altname[len-1] = 'g';
|
|
LoadJPG( altname, pic, width, height );
|
|
strcpy(g_sImageExtension,".jpg");
|
|
}
|
|
} else if ( !Q_stricmp(name+len-4, ".pcx") ) {
|
|
LoadPCX32( name, pic, width, height );
|
|
} else if ( !Q_stricmp( name+len-4, ".bmp" ) ) {
|
|
LoadBMP( name, pic, width, height );
|
|
} else if ( !Q_stricmp( name+len-4, ".jpg" ) ) {
|
|
LoadJPG( name, pic, width, height );
|
|
} else if ( !Q_stricmp( name+len-4, ".png" ) ) {
|
|
LoadPNG32(name, pic , width, height, NULL);
|
|
}
|
|
}
|
|
|
|
// feeder version of above because of John's not having extensions on anymore (but it might do... sigh)
|
|
//
|
|
bool g_bReportImageLoadErrors = false;
|
|
void R_LoadImage1( const char *name, byte **pic, int *width, int *height )
|
|
{
|
|
int len;
|
|
char work[MAX_QPATH];
|
|
|
|
*pic = NULL;
|
|
*width = 0;
|
|
*height = 0;
|
|
|
|
len = strlen(name);
|
|
if(len >= MAX_QPATH)
|
|
{
|
|
return;
|
|
}
|
|
if (len < 5)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (name[1] == ':')
|
|
{
|
|
if (g_bReportImageLoadErrors)
|
|
{
|
|
g_bReportImageLoadErrors = GetYesNo(va("Filename \"%s\" has a full-path in it, paths should be local only!\n\nContinue reporting image-load errors?",name));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// if the file has an extension then try it first...
|
|
//
|
|
if (name[strlen(name)-4] == '.')
|
|
{
|
|
R_LoadImage2( name, pic, width, height );
|
|
if (*pic)
|
|
return;
|
|
}
|
|
|
|
// else if not found, or no extension specified, then force some on in SOF2 order...
|
|
//
|
|
|
|
COM_StripExtension(name, work);
|
|
COM_DefaultExtension( work, sizeof( work ), ".png" );
|
|
LoadPNG32( work, pic, width, height, NULL ); // try png first
|
|
if(*pic)
|
|
{
|
|
strcpy(g_sImageExtension,".png");
|
|
return;
|
|
}
|
|
COM_StripExtension(name, work);
|
|
COM_DefaultExtension( work, sizeof( work ), ".tga" );
|
|
LoadTGA( work, pic, width, height); // try tga next
|
|
if(*pic)
|
|
{
|
|
strcpy(g_sImageExtension,".tga");
|
|
return;
|
|
}
|
|
COM_StripExtension(name, work);
|
|
COM_DefaultExtension( work, sizeof( work ), ".jpg" );
|
|
LoadJPG( work, pic, width, height ); // try jpg last
|
|
if(*pic)
|
|
{
|
|
strcpy(g_sImageExtension,".jpg");
|
|
return;
|
|
}
|
|
|
|
// Image loading failed...
|
|
//
|
|
if (g_bReportImageLoadErrors)
|
|
{
|
|
g_bReportImageLoadErrors = GetYesNo(va("R_LoadImage(): Couldn't read image:\n\n\"%s\"\n\n... continue receiving this message for this model?", name));
|
|
}
|
|
}
|
|
|
|
bool gbInImageLoader = false;
|
|
void R_LoadImage( const char *name, byte **pic, int *width, int *height )
|
|
{
|
|
// only attempt load if not the special case...
|
|
//
|
|
if (stricmp(name,"[NoMaterial]"))
|
|
{
|
|
// thanks, Jake... <sigh>
|
|
//
|
|
char sTempName[1024]={0};
|
|
|
|
if (bXMenPathHack)
|
|
strcpy(sTempName,"textures/");
|
|
strcat(sTempName,name);
|
|
|
|
gbInImageLoader = true;
|
|
{
|
|
R_LoadImage1( sTempName, pic, width, height );
|
|
}
|
|
gbInImageLoader = false;
|
|
}
|
|
else
|
|
{
|
|
// special case, so just zap whatever the last extension found was, else it gets strcat'd on return...
|
|
//
|
|
g_sImageExtension[0] = '\0';
|
|
}
|
|
}
|
|
|
|
///////////////////////// eof //////////////////////////
|
|
|