mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-11-23 21:02:11 +00:00
9e715ba7cc
This function only exists starting with libjpeg8. Check for existence and provide an implementation if necessary. This allows libjpeg6 to be used. Code borrowed from libjpeg8, adapt copyright in README accordingly.
1258 lines
31 KiB
C++
1258 lines
31 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.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "sys/platform.h"
|
|
|
|
#include <jpeglib.h>
|
|
#include <jerror.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 );
|
|
|
|
*/
|
|
|
|
/*
|
|
* Include file for users of JPEG library.
|
|
* You will need to have included system headers that define at least
|
|
* the typedefs FILE and size_t before you can include jpeglib.h.
|
|
* (stdio.h is sufficient on ANSI-conforming systems.)
|
|
* You may also wish to include "jerror.h".
|
|
*/
|
|
|
|
extern "C" {
|
|
// hooks from jpeg lib to our system
|
|
|
|
void jpg_Error( const char *fmt, ... ) {
|
|
va_list argptr;
|
|
char msg[2048];
|
|
|
|
va_start (argptr,fmt);
|
|
vsprintf (msg,fmt,argptr);
|
|
va_end (argptr);
|
|
|
|
common->FatalError( "%s", msg );
|
|
}
|
|
|
|
void jpg_Printf( const char *fmt, ... ) {
|
|
va_list argptr;
|
|
char msg[2048];
|
|
|
|
va_start (argptr,fmt);
|
|
vsprintf (msg,fmt,argptr);
|
|
va_end (argptr);
|
|
|
|
common->Printf( "%s", msg );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
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 = LittleLong( * ( int * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.reserved0 = LittleLong( * ( int * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.bitmapDataOffset = LittleLong( * ( int * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.bitmapHeaderSize = LittleLong( * ( int * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.width = LittleLong( * ( int * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.height = LittleLong( * ( 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 = LittleLong( * ( int * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.bitmapDataSize = LittleLong( * ( int * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.hRes = LittleLong( * ( int * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.vRes = LittleLong( * ( int * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.colors = LittleLong( * ( int * ) buf_p );
|
|
buf_p += 4;
|
|
bmpHeader.importantColors = LittleLong( * ( 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 );
|
|
}
|
|
|
|
/*
|
|
=========================================================
|
|
|
|
JPG LOADING
|
|
|
|
Interfaces with the huge libjpeg
|
|
=========================================================
|
|
*/
|
|
|
|
#ifdef HAVE_JPEG_MEM_SRC
|
|
#define j_mem_src jpeg_mem_src
|
|
#else
|
|
static void init_mem_source(j_decompress_ptr cinfo)
|
|
{
|
|
/* no work necessary here */
|
|
}
|
|
|
|
static boolean fill_mem_input_buffer(j_decompress_ptr cinfo)
|
|
{
|
|
static JOCTET mybuffer[4];
|
|
|
|
/* The whole JPEG data is expected to reside in the supplied memory
|
|
* buffer, so any request for more data beyond the given buffer size
|
|
* is treated as an error.
|
|
*/
|
|
WARNMS(cinfo, JWRN_JPEG_EOF);
|
|
/* Insert a fake EOI marker */
|
|
mybuffer[0] = (JOCTET) 0xFF;
|
|
mybuffer[1] = (JOCTET) JPEG_EOI;
|
|
|
|
cinfo->src->next_input_byte = mybuffer;
|
|
cinfo->src->bytes_in_buffer = 2;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void skip_input_data(j_decompress_ptr cinfo, long num_bytes)
|
|
{
|
|
struct jpeg_source_mgr *src = cinfo->src;
|
|
|
|
if (num_bytes > 0) {
|
|
while (num_bytes > (long)src->bytes_in_buffer) {
|
|
num_bytes -= (long)src->bytes_in_buffer;
|
|
(void)(*src->fill_input_buffer) (cinfo);
|
|
/* note we assume that fill_input_buffer will never return FALSE,
|
|
* so suspension need not be handled.
|
|
*/
|
|
}
|
|
src->next_input_byte += (size_t)num_bytes;
|
|
src->bytes_in_buffer -= (size_t)num_bytes;
|
|
}
|
|
}
|
|
|
|
static void term_source(j_decompress_ptr cinfo)
|
|
{
|
|
/* no work necessary here */
|
|
}
|
|
|
|
static void j_mem_src(j_decompress_ptr cinfo, unsigned char *inbuffer, unsigned long insize)
|
|
{
|
|
struct jpeg_source_mgr *src;
|
|
|
|
if (inbuffer == NULL || insize == 0) /* Treat empty input as fatal error */
|
|
ERREXIT(cinfo, JERR_INPUT_EMPTY);
|
|
|
|
/* The source object is made permanent so that a series of JPEG images
|
|
* can be read from the same buffer by calling jpeg_mem_src only before
|
|
* the first one.
|
|
*/
|
|
if (cinfo->src == NULL) { /* first time for this JPEG object? */
|
|
cinfo->src = (struct jpeg_source_mgr *)
|
|
(*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_PERMANENT,
|
|
sizeof(struct jpeg_source_mgr));
|
|
}
|
|
|
|
src = cinfo->src;
|
|
src->init_source = init_mem_source;
|
|
src->fill_input_buffer = fill_mem_input_buffer;
|
|
src->skip_input_data = skip_input_data;
|
|
src->resync_to_restart = jpeg_resync_to_restart; /* use default method */
|
|
src->term_source = term_source;
|
|
src->bytes_in_buffer = (size_t)insize;
|
|
src->next_input_byte = (JOCTET *)inbuffer;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
=============
|
|
LoadJPG
|
|
=============
|
|
*/
|
|
static void LoadJPG( const char *filename, unsigned char **pic, int *width, int *height, ID_TIME_T *timestamp ) {
|
|
/* This struct contains the JPEG decompression parameters and pointers to
|
|
* working space (which is allocated as needed by the JPEG library).
|
|
*/
|
|
struct jpeg_decompress_struct cinfo;
|
|
/* We use our private extension JPEG error handler.
|
|
* Note that this struct must live as long as the main JPEG parameter
|
|
* struct, to avoid dangling-pointer problems.
|
|
*/
|
|
/* This struct represents a JPEG error handler. It is declared separately
|
|
* because applications often want to supply a specialized error handler
|
|
* (see the second half of this file for an example). But here we just
|
|
* take the easy way out and use the standard error handler, which will
|
|
* print a message on stderr and call exit() if compression fails.
|
|
* Note that this struct must live as long as the main JPEG parameter
|
|
* struct, to avoid dangling-pointer problems.
|
|
*/
|
|
struct jpeg_error_mgr jerr;
|
|
/* More stuff */
|
|
JSAMPARRAY buffer; /* Output row buffer */
|
|
int row_stride; /* physical row width in output buffer */
|
|
unsigned char *out;
|
|
byte *fbuffer;
|
|
byte *bbuf;
|
|
|
|
/* In this example we want to open the input file before doing anything else,
|
|
* so that the setjmp() error recovery below can assume the file is open.
|
|
* VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
|
|
* requires it in order to read binary files.
|
|
*/
|
|
|
|
// JDC: because fill_input_buffer() blindly copies INPUT_BUF_SIZE bytes,
|
|
// we need to make sure the file buffer is padded or it may crash
|
|
if ( pic ) {
|
|
*pic = NULL; // until proven otherwise
|
|
}
|
|
|
|
int len;
|
|
idFile *f;
|
|
|
|
f = fileSystem->OpenFileRead( filename );
|
|
if ( !f ) {
|
|
return;
|
|
}
|
|
len = f->Length();
|
|
if ( timestamp ) {
|
|
*timestamp = f->Timestamp();
|
|
}
|
|
if ( !pic ) {
|
|
fileSystem->CloseFile( f );
|
|
return; // just getting timestamp
|
|
}
|
|
fbuffer = (byte *)Mem_ClearedAlloc( len + 4096 );
|
|
f->Read( fbuffer, len );
|
|
fileSystem->CloseFile( f );
|
|
|
|
/* Step 1: allocate and initialize JPEG decompression object */
|
|
|
|
/* We have to set up the error handler first, in case the initialization
|
|
* step fails. (Unlikely, but it could happen if you are out of memory.)
|
|
* This routine fills in the contents of struct jerr, and returns jerr's
|
|
* address which we place into the link field in cinfo.
|
|
*/
|
|
cinfo.err = jpeg_std_error(&jerr);
|
|
|
|
/* Now we can initialize the JPEG decompression object. */
|
|
jpeg_create_decompress(&cinfo);
|
|
|
|
/* Step 2: specify data source (eg, a file) */
|
|
|
|
j_mem_src(&cinfo, fbuffer, len);
|
|
|
|
/* Step 3: read file parameters with jpeg_read_header() */
|
|
|
|
(void) jpeg_read_header(&cinfo, true );
|
|
/* We can ignore the return value from jpeg_read_header since
|
|
* (a) suspension is not possible with the stdio data source, and
|
|
* (b) we passed TRUE to reject a tables-only JPEG file as an error.
|
|
* See libjpeg.doc for more info.
|
|
*/
|
|
|
|
/* Step 4: set parameters for decompression */
|
|
|
|
/* In this example, we don't need to change any of the defaults set by
|
|
* jpeg_read_header(), so we do nothing here.
|
|
*/
|
|
|
|
/* Step 5: Start decompressor */
|
|
|
|
(void) jpeg_start_decompress(&cinfo);
|
|
/* We can ignore the return value since suspension is not possible
|
|
* with the stdio data source.
|
|
*/
|
|
|
|
/* We may need to do some setup of our own at this point before reading
|
|
* the data. After jpeg_start_decompress() we have the correct scaled
|
|
* output image dimensions available, as well as the output colormap
|
|
* if we asked for color quantization.
|
|
* In this example, we need to make an output work buffer of the right size.
|
|
*/
|
|
/* JSAMPLEs per row in output buffer */
|
|
row_stride = cinfo.output_width * cinfo.output_components;
|
|
|
|
if (cinfo.output_components!=4) {
|
|
common->DWarning( "JPG %s is unsupported color depth (%d)",
|
|
filename, cinfo.output_components);
|
|
}
|
|
out = (byte *)R_StaticAlloc(cinfo.output_width*cinfo.output_height*4);
|
|
|
|
*pic = out;
|
|
*width = cinfo.output_width;
|
|
*height = cinfo.output_height;
|
|
|
|
/* Step 6: while (scan lines remain to be read) */
|
|
/* jpeg_read_scanlines(...); */
|
|
|
|
/* Here we use the library's state variable cinfo.output_scanline as the
|
|
* loop counter, so that we don't have to keep track ourselves.
|
|
*/
|
|
while (cinfo.output_scanline < cinfo.output_height) {
|
|
/* jpeg_read_scanlines expects an array of pointers to scanlines.
|
|
* Here the array is only one element long, but you could ask for
|
|
* more than one scanline at a time if that's more convenient.
|
|
*/
|
|
bbuf = ((out+(row_stride*cinfo.output_scanline)));
|
|
buffer = &bbuf;
|
|
(void) jpeg_read_scanlines(&cinfo, buffer, 1);
|
|
}
|
|
|
|
// clear all the alphas to 255
|
|
{
|
|
int i, j;
|
|
byte *buf;
|
|
|
|
buf = *pic;
|
|
|
|
j = cinfo.output_width * cinfo.output_height * 4;
|
|
for ( i = 3 ; i < j ; i+=4 ) {
|
|
buf[i] = 255;
|
|
}
|
|
}
|
|
|
|
/* Step 7: Finish decompression */
|
|
|
|
(void) jpeg_finish_decompress(&cinfo);
|
|
/* We can ignore the return value since suspension is not possible
|
|
* with the stdio data source.
|
|
*/
|
|
|
|
/* Step 8: Release JPEG decompression object */
|
|
|
|
/* This is an important step since it will release a good deal of memory. */
|
|
jpeg_destroy_decompress(&cinfo);
|
|
|
|
/* After finish_decompress, we can close the input file.
|
|
* Here we postpone it until after no more JPEG errors are possible,
|
|
* so as to simplify the setjmp error logic above. (Actually, I don't
|
|
* think that jpeg_destroy can do an error exit, but why assume anything...)
|
|
*/
|
|
Mem_Free( fbuffer );
|
|
|
|
/* At this point you may want to check to see whether any corrupt-data
|
|
* warnings occurred (test whether jerr.pub.num_warnings is nonzero).
|
|
*/
|
|
|
|
/* And we're done! */
|
|
}
|
|
|
|
//===================================================================
|
|
|
|
/*
|
|
=================
|
|
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 = 0xFFFFFFFF;
|
|
}
|
|
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 == -1 ) ) {
|
|
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;
|
|
}
|