mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-11-26 14:21:18 +00:00
b0e0883f6e
libjpeg is a pain in the ass, especially due to Ubuntu shipping libjpeg-turbo in jpeg8 mode as their default libjpeg, while every other distro I checked (including debian!) ships libjpeg-turbo in jpeg6.2 mode Thankfully stb_image.h exists - just a single header and it even has a (much!) friendlier API. It's not like Doom3 (or any of the mods I checked) actually use JPEGs, but I'm sure if I'd drop support completely, someone would complain (perhaps rightfully so).
1019 lines
24 KiB
C++
1019 lines
24 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 GPL Source Code
|
|
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
|
|
|
|
Doom 3 Source Code 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.
|
|
|
|
Doom 3 Source Code 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.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
// DG: replace libjpeg with stb_image.h because it causes fewer headaches
|
|
// include this first, otherwise build breaks because of use_idStr_* #defines in Str.h
|
|
#define STB_IMAGE_IMPLEMENTATION
|
|
#define STBI_NO_HDR
|
|
#define STBI_NO_LINEAR
|
|
#define STBI_ONLY_JPEG // at least for now, only use it for JPEG
|
|
#define STBI_NO_STDIO // images are passed as buffers
|
|
#include "stb_image.h"
|
|
|
|
#include "sys/platform.h"
|
|
|
|
#include "renderer/tr_local.h"
|
|
|
|
#include "renderer/Image.h"
|
|
|
|
/*
|
|
|
|
This file only has a single entry point:
|
|
|
|
void R_LoadImage( const char *name, byte **pic, int *width, int *height, bool makePowerOf2 );
|
|
|
|
*/
|
|
|
|
/*
|
|
================
|
|
R_WriteTGA
|
|
================
|
|
*/
|
|
void R_WriteTGA( const char *filename, const byte *data, int width, int height, bool flipVertical ) {
|
|
byte *buffer;
|
|
int i;
|
|
int bufferSize = width*height*4 + 18;
|
|
int imgStart = 18;
|
|
|
|
buffer = (byte *)Mem_Alloc( bufferSize );
|
|
memset( buffer, 0, 18 );
|
|
buffer[2] = 2; // uncompressed type
|
|
buffer[12] = width&255;
|
|
buffer[13] = width>>8;
|
|
buffer[14] = height&255;
|
|
buffer[15] = height>>8;
|
|
buffer[16] = 32; // pixel size
|
|
if ( !flipVertical ) {
|
|
buffer[17] = (1<<5); // flip bit, for normal top to bottom raster order
|
|
}
|
|
|
|
// swap rgb to bgr
|
|
for ( i=imgStart ; i<bufferSize ; i+=4 ) {
|
|
buffer[i] = data[i-imgStart+2]; // blue
|
|
buffer[i+1] = data[i-imgStart+1]; // green
|
|
buffer[i+2] = data[i-imgStart+0]; // red
|
|
buffer[i+3] = data[i-imgStart+3]; // alpha
|
|
}
|
|
|
|
fileSystem->WriteFile( filename, buffer, bufferSize );
|
|
|
|
Mem_Free (buffer);
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
R_WritePalTGA
|
|
================
|
|
*/
|
|
void R_WritePalTGA( const char *filename, const byte *data, const byte *palette, int width, int height, bool flipVertical ) {
|
|
byte *buffer;
|
|
int i;
|
|
int bufferSize = (width * height) + (256 * 3) + 18;
|
|
int palStart = 18;
|
|
int imgStart = 18 + (256 * 3);
|
|
|
|
buffer = (byte *)Mem_Alloc( bufferSize );
|
|
memset( buffer, 0, 18 );
|
|
buffer[1] = 1; // color map type
|
|
buffer[2] = 1; // uncompressed color mapped image
|
|
buffer[5] = 0; // number of palette entries (lo)
|
|
buffer[6] = 1; // number of palette entries (hi)
|
|
buffer[7] = 24; // color map bpp
|
|
buffer[12] = width&255;
|
|
buffer[13] = width>>8;
|
|
buffer[14] = height&255;
|
|
buffer[15] = height>>8;
|
|
buffer[16] = 8; // pixel size
|
|
if ( !flipVertical ) {
|
|
buffer[17] = (1<<5); // flip bit, for normal top to bottom raster order
|
|
}
|
|
|
|
// store palette, swapping rgb to bgr
|
|
for ( i=palStart ; i<imgStart ; i+=3 ) {
|
|
buffer[i] = palette[i-palStart+2]; // blue
|
|
buffer[i+1] = palette[i-palStart+1]; // green
|
|
buffer[i+2] = palette[i-palStart+0]; // red
|
|
}
|
|
|
|
// store the image data
|
|
for ( i=imgStart ; i<bufferSize ; i++ ) {
|
|
buffer[i] = data[i-imgStart];
|
|
}
|
|
|
|
fileSystem->WriteFile( filename, buffer, bufferSize );
|
|
|
|
Mem_Free (buffer);
|
|
}
|
|
|
|
|
|
static void LoadBMP( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp );
|
|
static void LoadTGA( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp );
|
|
static void LoadJPG( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp );
|
|
|
|
|
|
/*
|
|
========================================================================
|
|
|
|
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 int fileSize;
|
|
unsigned int reserved0;
|
|
unsigned int bitmapDataOffset;
|
|
unsigned int bitmapHeaderSize;
|
|
unsigned int width;
|
|
unsigned int height;
|
|
unsigned short planes;
|
|
unsigned short bitsPerPixel;
|
|
unsigned int compression;
|
|
unsigned int bitmapDataSize;
|
|
unsigned int hRes;
|
|
unsigned int vRes;
|
|
unsigned int colors;
|
|
unsigned int importantColors;
|
|
unsigned char palette[256][4];
|
|
} BMPHeader_t;
|
|
|
|
/*
|
|
==============
|
|
LoadBMP
|
|
==============
|
|
*/
|
|
static void LoadBMP( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp )
|
|
{
|
|
int columns, rows, numPixels;
|
|
byte *pixbuf;
|
|
int row, column;
|
|
byte *buf_p;
|
|
byte *buffer;
|
|
int length;
|
|
BMPHeader_t bmpHeader;
|
|
byte *bmpRGBA;
|
|
|
|
if ( !pic ) {
|
|
fileSystem->ReadFile ( name, NULL, timestamp );
|
|
return; // just getting timestamp
|
|
}
|
|
|
|
*pic = NULL;
|
|
|
|
//
|
|
// load the file
|
|
//
|
|
length = fileSystem->ReadFile( name, (void **)&buffer, timestamp );
|
|
if ( !buffer ) {
|
|
return;
|
|
}
|
|
|
|
buf_p = buffer;
|
|
|
|
bmpHeader.id[0] = *buf_p++;
|
|
bmpHeader.id[1] = *buf_p++;
|
|
bmpHeader.fileSize = LittleInt( * ( int * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.reserved0 = LittleInt( * ( int * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.bitmapDataOffset = LittleInt( * ( int * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.bitmapHeaderSize = LittleInt( * ( int * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.width = LittleInt( * ( int * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.height = LittleInt( * ( int * ) 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 = LittleInt( * ( int * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.bitmapDataSize = LittleInt( * ( int * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.hRes = LittleInt( * ( int * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.vRes = LittleInt( * ( int * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.colors = LittleInt( * ( int * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.importantColors = LittleInt( * ( int * ) 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' )
|
|
{
|
|
common->Error( "LoadBMP: only Windows-style BMP files supported (%s)\n", name );
|
|
}
|
|
if ( bmpHeader.fileSize != length )
|
|
{
|
|
common->Error( "LoadBMP: header size does not match file size (%u vs. %d) (%s)\n", bmpHeader.fileSize, length, name );
|
|
}
|
|
if ( bmpHeader.compression != 0 )
|
|
{
|
|
common->Error( "LoadBMP: only uncompressed BMP files supported (%s)\n", name );
|
|
}
|
|
if ( bmpHeader.bitsPerPixel < 8 )
|
|
{
|
|
common->Error( "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 = (byte *)R_StaticAlloc( 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:
|
|
common->Error( "LoadBMP: illegal pixel_size '%d' in file '%s'\n", bmpHeader.bitsPerPixel, name );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
fileSystem->FreeFile( buffer );
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
=================================================================
|
|
|
|
PCX LOADING
|
|
|
|
=================================================================
|
|
*/
|
|
|
|
|
|
/*
|
|
==============
|
|
LoadPCX
|
|
==============
|
|
*/
|
|
static void LoadPCX ( const char *filename, byte **pic, byte **palette, int *width, int *height,
|
|
ID_TIME_T *timestamp ) {
|
|
byte *raw;
|
|
pcx_t *pcx;
|
|
int x, y;
|
|
int len;
|
|
int dataByte, runLength;
|
|
byte *out, *pix;
|
|
int xmax, ymax;
|
|
|
|
if ( !pic ) {
|
|
fileSystem->ReadFile( filename, NULL, timestamp );
|
|
return; // just getting timestamp
|
|
}
|
|
|
|
*pic = NULL;
|
|
*palette = NULL;
|
|
|
|
//
|
|
// load the file
|
|
//
|
|
len = fileSystem->ReadFile( filename, (void **)&raw, timestamp );
|
|
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)
|
|
{
|
|
common->Printf( "Bad pcx file %s (%i x %i) (%i x %i)\n", filename, xmax+1, ymax+1, pcx->xmax, pcx->ymax);
|
|
return;
|
|
}
|
|
|
|
out = (byte *)R_StaticAlloc( (ymax+1) * (xmax+1) );
|
|
|
|
*pic = out;
|
|
|
|
pix = out;
|
|
|
|
if (palette)
|
|
{
|
|
*palette = (byte *)R_StaticAlloc(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)
|
|
{
|
|
common->Printf( "PCX file %s was malformed", filename );
|
|
R_StaticFree (*pic);
|
|
*pic = NULL;
|
|
}
|
|
|
|
fileSystem->FreeFile( pcx );
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
LoadPCX32
|
|
==============
|
|
*/
|
|
static void LoadPCX32 ( const char *filename, byte **pic, int *width, int *height, ID_TIME_T *timestamp) {
|
|
byte *palette;
|
|
byte *pic8;
|
|
int i, c, p;
|
|
byte *pic32;
|
|
|
|
if ( !pic ) {
|
|
fileSystem->ReadFile( filename, NULL, timestamp );
|
|
return; // just getting timestamp
|
|
}
|
|
LoadPCX (filename, &pic8, &palette, width, height, timestamp);
|
|
if (!pic8) {
|
|
*pic = NULL;
|
|
return;
|
|
}
|
|
|
|
c = (*width) * (*height);
|
|
pic32 = *pic = (byte *)R_StaticAlloc(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;
|
|
}
|
|
|
|
R_StaticFree( pic8 );
|
|
R_StaticFree( palette );
|
|
}
|
|
|
|
/*
|
|
=========================================================
|
|
|
|
TARGA LOADING
|
|
|
|
=========================================================
|
|
*/
|
|
|
|
/*
|
|
=============
|
|
LoadTGA
|
|
=============
|
|
*/
|
|
static void LoadTGA( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp ) {
|
|
int columns, rows, numPixels, fileSize, numBytes;
|
|
byte *pixbuf;
|
|
int row, column;
|
|
byte *buf_p;
|
|
byte *buffer;
|
|
TargaHeader targa_header;
|
|
byte *targa_rgba;
|
|
|
|
if ( !pic ) {
|
|
fileSystem->ReadFile( name, NULL, timestamp );
|
|
return; // just getting timestamp
|
|
}
|
|
|
|
*pic = NULL;
|
|
|
|
//
|
|
// load the file
|
|
//
|
|
fileSize = fileSystem->ReadFile( name, (void **)&buffer, timestamp );
|
|
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 ) {
|
|
common->Error( "LoadTGA( %s ): Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported\n", name );
|
|
}
|
|
|
|
if ( targa_header.colormap_type != 0 ) {
|
|
common->Error( "LoadTGA( %s ): colormaps not supported\n", name );
|
|
}
|
|
|
|
if ( ( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 ) && targa_header.image_type != 3 ) {
|
|
common->Error( "LoadTGA( %s ): Only 32 or 24 bit images supported (no colormaps)\n", name );
|
|
}
|
|
|
|
if ( targa_header.image_type == 2 || targa_header.image_type == 3 ) {
|
|
numBytes = targa_header.width * targa_header.height * ( targa_header.pixel_size >> 3 );
|
|
if ( numBytes > fileSize - 18 - targa_header.id_length ) {
|
|
common->Error( "LoadTGA( %s ): incomplete file\n", name );
|
|
}
|
|
}
|
|
|
|
columns = targa_header.width;
|
|
rows = targa_header.height;
|
|
numPixels = columns * rows;
|
|
|
|
if ( width ) {
|
|
*width = columns;
|
|
}
|
|
if ( height ) {
|
|
*height = rows;
|
|
}
|
|
|
|
targa_rgba = (byte *)R_StaticAlloc(numPixels*4);
|
|
*pic = targa_rgba;
|
|
|
|
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 = targa_rgba + 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:
|
|
common->Error( "LoadTGA( %s ): illegal pixel_size '%d'\n", name, targa_header.pixel_size );
|
|
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 = targa_rgba + 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:
|
|
common->Error( "LoadTGA( %s ): illegal pixel_size '%d'\n", name, targa_header.pixel_size );
|
|
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 = targa_rgba + 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:
|
|
common->Error( "LoadTGA( %s ): illegal pixel_size '%d'\n", name, targa_header.pixel_size );
|
|
break;
|
|
}
|
|
column++;
|
|
if ( column == columns ) { // pixel packet run spans across rows
|
|
column = 0;
|
|
if ( row > 0 ) {
|
|
row--;
|
|
}
|
|
else {
|
|
goto breakOut;
|
|
}
|
|
pixbuf = targa_rgba + row*columns*4;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
breakOut: ;
|
|
}
|
|
}
|
|
|
|
if ( (targa_header.attributes & (1<<5)) ) { // image flp bit
|
|
R_VerticalFlip( *pic, *width, *height );
|
|
}
|
|
|
|
fileSystem->FreeFile( buffer );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
LoadJPG
|
|
=============
|
|
*/
|
|
static void LoadJPG( const char *filename, unsigned char **pic, int *width, int *height, ID_TIME_T *timestamp ) {
|
|
|
|
if ( pic ) {
|
|
*pic = NULL; // until proven otherwise
|
|
}
|
|
|
|
idFile *f = fileSystem->OpenFileRead( filename );
|
|
if ( !f ) {
|
|
return;
|
|
}
|
|
int len = f->Length();
|
|
if ( timestamp ) {
|
|
*timestamp = f->Timestamp();
|
|
}
|
|
if ( !pic ) {
|
|
fileSystem->CloseFile( f );
|
|
return; // just getting timestamp
|
|
}
|
|
byte *fbuffer = (byte *)Mem_ClearedAlloc( len );
|
|
f->Read( fbuffer, len );
|
|
fileSystem->CloseFile( f );
|
|
|
|
int w=0, h=0, comp=0;
|
|
byte* decodedImageData = stbi_load_from_memory( fbuffer, len, &w, &h, &comp, 4 );
|
|
|
|
Mem_Free( fbuffer );
|
|
|
|
if ( decodedImageData == NULL ) {
|
|
common->Warning( "stb_image was unable to load JPG %s : %s\n",
|
|
filename, stbi_failure_reason());
|
|
return;
|
|
}
|
|
|
|
// *pic must be allocated with R_StaticAlloc(), but stb_image allocates with malloc()
|
|
// (and as there is no R_StaticRealloc(), #define STBI_MALLOC etc won't help)
|
|
// so the decoded data must be copied once
|
|
int size = w*h*4;
|
|
*pic = (byte *)R_StaticAlloc( size );
|
|
memcpy( *pic, decodedImageData, size );
|
|
*width = w;
|
|
*height = h;
|
|
// now that decodedImageData has been copied into *pic, it's not needed anymore
|
|
stbi_image_free( decodedImageData );
|
|
}
|
|
|
|
//===================================================================
|
|
|
|
/*
|
|
=================
|
|
R_LoadImage
|
|
|
|
Loads any of the supported image types into a cannonical
|
|
32 bit format.
|
|
|
|
Automatically attempts to load .jpg files if .tga files fail to load.
|
|
|
|
*pic will be NULL if the load failed.
|
|
|
|
Anything that is going to make this into a texture would use
|
|
makePowerOf2 = true, but something loading an image as a lookup
|
|
table of some sort would leave it in identity form.
|
|
|
|
It is important to do this at image load time instead of texture load
|
|
time for bump maps.
|
|
|
|
Timestamp may be NULL if the value is going to be ignored
|
|
|
|
If pic is NULL, the image won't actually be loaded, it will just find the
|
|
timestamp.
|
|
=================
|
|
*/
|
|
void R_LoadImage( const char *cname, byte **pic, int *width, int *height, ID_TIME_T *timestamp, bool makePowerOf2 ) {
|
|
idStr name = cname;
|
|
|
|
if ( pic ) {
|
|
*pic = NULL;
|
|
}
|
|
if ( timestamp ) {
|
|
*timestamp = FILE_NOT_FOUND_TIMESTAMP;
|
|
}
|
|
if ( width ) {
|
|
*width = 0;
|
|
}
|
|
if ( height ) {
|
|
*height = 0;
|
|
}
|
|
|
|
name.DefaultFileExtension( ".tga" );
|
|
|
|
if (name.Length()<5) {
|
|
return;
|
|
}
|
|
|
|
name.ToLower();
|
|
idStr ext;
|
|
name.ExtractFileExtension( ext );
|
|
|
|
if ( ext == "tga" ) {
|
|
LoadTGA( name.c_str(), pic, width, height, timestamp ); // try tga first
|
|
if ( ( pic && *pic == 0 ) || ( timestamp && *timestamp == FILE_NOT_FOUND_TIMESTAMP ) ) {
|
|
name.StripFileExtension();
|
|
name.DefaultFileExtension( ".jpg" );
|
|
LoadJPG( name.c_str(), pic, width, height, timestamp );
|
|
}
|
|
} else if ( ext == "pcx" ) {
|
|
LoadPCX32( name.c_str(), pic, width, height, timestamp );
|
|
} else if ( ext == "bmp" ) {
|
|
LoadBMP( name.c_str(), pic, width, height, timestamp );
|
|
} else if ( ext == "jpg" ) {
|
|
LoadJPG( name.c_str(), pic, width, height, timestamp );
|
|
}
|
|
|
|
if ( ( width && *width < 1 ) || ( height && *height < 1 ) ) {
|
|
if ( pic && *pic ) {
|
|
R_StaticFree( *pic );
|
|
*pic = 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// convert to exact power of 2 sizes
|
|
//
|
|
if ( pic && *pic && makePowerOf2 ) {
|
|
int w, h;
|
|
int scaled_width, scaled_height;
|
|
byte *resampledBuffer;
|
|
|
|
w = *width;
|
|
h = *height;
|
|
|
|
for (scaled_width = 1 ; scaled_width < w ; scaled_width<<=1)
|
|
;
|
|
for (scaled_height = 1 ; scaled_height < h ; scaled_height<<=1)
|
|
;
|
|
|
|
if ( scaled_width != w || scaled_height != h ) {
|
|
if ( globalImages->image_roundDown.GetBool() && scaled_width > w ) {
|
|
scaled_width >>= 1;
|
|
}
|
|
if ( globalImages->image_roundDown.GetBool() && scaled_height > h ) {
|
|
scaled_height >>= 1;
|
|
}
|
|
|
|
resampledBuffer = R_ResampleTexture( *pic, w, h, scaled_width, scaled_height );
|
|
R_StaticFree( *pic );
|
|
*pic = resampledBuffer;
|
|
*width = scaled_width;
|
|
*height = scaled_height;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=======================
|
|
R_LoadCubeImages
|
|
|
|
Loads six files with proper extensions
|
|
=======================
|
|
*/
|
|
bool R_LoadCubeImages( const char *imgName, cubeFiles_t extensions, byte *pics[6], int *outSize, ID_TIME_T *timestamp ) {
|
|
int i, j;
|
|
const char *cameraSides[6] = { "_forward.tga", "_back.tga", "_left.tga", "_right.tga",
|
|
"_up.tga", "_down.tga" };
|
|
const char *axisSides[6] = { "_px.tga", "_nx.tga", "_py.tga", "_ny.tga",
|
|
"_pz.tga", "_nz.tga" };
|
|
const char **sides;
|
|
char fullName[MAX_IMAGE_NAME];
|
|
int width, height, size = 0;
|
|
|
|
if ( extensions == CF_CAMERA ) {
|
|
sides = cameraSides;
|
|
} else {
|
|
sides = axisSides;
|
|
}
|
|
|
|
// FIXME: precompressed cube map files
|
|
if ( pics ) {
|
|
memset( pics, 0, 6*sizeof(pics[0]) );
|
|
}
|
|
if ( timestamp ) {
|
|
*timestamp = 0;
|
|
}
|
|
|
|
for ( i = 0 ; i < 6 ; i++ ) {
|
|
idStr::snPrintf( fullName, sizeof( fullName ), "%s%s", imgName, sides[i] );
|
|
|
|
ID_TIME_T thisTime;
|
|
if ( !pics ) {
|
|
// just checking timestamps
|
|
R_LoadImageProgram( fullName, NULL, &width, &height, &thisTime );
|
|
} else {
|
|
R_LoadImageProgram( fullName, &pics[i], &width, &height, &thisTime );
|
|
}
|
|
if ( thisTime == FILE_NOT_FOUND_TIMESTAMP ) {
|
|
break;
|
|
}
|
|
if ( i == 0 ) {
|
|
size = width;
|
|
}
|
|
if ( width != size || height != size ) {
|
|
common->Warning( "Mismatched sizes on cube map '%s'", imgName );
|
|
break;
|
|
}
|
|
if ( timestamp ) {
|
|
if ( thisTime > *timestamp ) {
|
|
*timestamp = thisTime;
|
|
}
|
|
}
|
|
if ( pics && extensions == CF_CAMERA ) {
|
|
// convert from "camera" images to native cube map images
|
|
switch( i ) {
|
|
case 0: // forward
|
|
R_RotatePic( pics[i], width);
|
|
break;
|
|
case 1: // back
|
|
R_RotatePic( pics[i], width);
|
|
R_HorizontalFlip( pics[i], width, height );
|
|
R_VerticalFlip( pics[i], width, height );
|
|
break;
|
|
case 2: // left
|
|
R_VerticalFlip( pics[i], width, height );
|
|
break;
|
|
case 3: // right
|
|
R_HorizontalFlip( pics[i], width, height );
|
|
break;
|
|
case 4: // up
|
|
R_RotatePic( pics[i], width);
|
|
break;
|
|
case 5: // down
|
|
R_RotatePic( pics[i], width);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( i != 6 ) {
|
|
// we had an error, so free everything
|
|
if ( pics ) {
|
|
for ( j = 0 ; j < i ; j++ ) {
|
|
R_StaticFree( pics[j] );
|
|
}
|
|
}
|
|
|
|
if ( timestamp ) {
|
|
*timestamp = 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if ( outSize ) {
|
|
*outSize = size;
|
|
}
|
|
return true;
|
|
}
|