mirror of
https://github.com/UberGames/ioef.git
synced 2024-11-24 13:11:30 +00:00
4997c4764a
* Add developer warning when texture loading falls back on jpg from tga * Remove uppercase extension hack from texture loading since the Q3 pk3 file system is case insensitive anyway and you would likely want to know about the failures when loading images from the native FS
5099 lines
117 KiB
C
5099 lines
117 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
|
|
This file is part of Quake III Arena source code.
|
|
|
|
Quake III Arena 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 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
Quake III Arena 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 Quake III Arena source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
// tr_image.c
|
|
#include "tr_local.h"
|
|
|
|
/*
|
|
* 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".
|
|
*/
|
|
|
|
#define JPEG_INTERNALS
|
|
#include "../jpeg-6/jpeglib.h"
|
|
|
|
#include "../qcommon/puff.h"
|
|
|
|
|
|
static void LoadBMP( const char *name, byte **pic, int *width, int *height );
|
|
static void LoadTGA( const char *name, byte **pic, int *width, int *height );
|
|
static void LoadJPG( const char *name, byte **pic, int *width, int *height );
|
|
static void LoadPNG( const char *name, byte **pic, int *width, int *height );
|
|
|
|
static byte s_intensitytable[256];
|
|
static unsigned char s_gammatable[256];
|
|
|
|
int gl_filter_min = GL_LINEAR_MIPMAP_NEAREST;
|
|
int gl_filter_max = GL_LINEAR;
|
|
|
|
#define FILE_HASH_SIZE 1024
|
|
static image_t* hashTable[FILE_HASH_SIZE];
|
|
|
|
/*
|
|
** R_GammaCorrect
|
|
*/
|
|
void R_GammaCorrect( byte *buffer, int bufSize ) {
|
|
int i;
|
|
|
|
for ( i = 0; i < bufSize; i++ ) {
|
|
buffer[i] = s_gammatable[buffer[i]];
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
char *name;
|
|
int minimize, maximize;
|
|
} textureMode_t;
|
|
|
|
textureMode_t modes[] = {
|
|
{"GL_NEAREST", GL_NEAREST, GL_NEAREST},
|
|
{"GL_LINEAR", GL_LINEAR, GL_LINEAR},
|
|
{"GL_NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST},
|
|
{"GL_LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR},
|
|
{"GL_NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST},
|
|
{"GL_LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR}
|
|
};
|
|
|
|
/*
|
|
================
|
|
return a hash value for the filename
|
|
================
|
|
*/
|
|
static long generateHashValue( const char *fname ) {
|
|
int i;
|
|
long hash;
|
|
char letter;
|
|
|
|
hash = 0;
|
|
i = 0;
|
|
while (fname[i] != '\0') {
|
|
letter = tolower(fname[i]);
|
|
if (letter =='.') break; // don't include extension
|
|
if (letter =='\\') letter = '/'; // damn path names
|
|
hash+=(long)(letter)*(i+119);
|
|
i++;
|
|
}
|
|
hash &= (FILE_HASH_SIZE-1);
|
|
return hash;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
GL_TextureMode
|
|
===============
|
|
*/
|
|
void GL_TextureMode( const char *string ) {
|
|
int i;
|
|
image_t *glt;
|
|
|
|
for ( i=0 ; i< 6 ; i++ ) {
|
|
if ( !Q_stricmp( modes[i].name, string ) ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// hack to prevent trilinear from being set on voodoo,
|
|
// because their driver freaks...
|
|
if ( i == 5 && glConfig.hardwareType == GLHW_3DFX_2D3D ) {
|
|
ri.Printf( PRINT_ALL, "Refusing to set trilinear on a voodoo.\n" );
|
|
i = 3;
|
|
}
|
|
|
|
|
|
if ( i == 6 ) {
|
|
ri.Printf (PRINT_ALL, "bad filter name\n");
|
|
return;
|
|
}
|
|
|
|
gl_filter_min = modes[i].minimize;
|
|
gl_filter_max = modes[i].maximize;
|
|
|
|
// change all the existing mipmap texture objects
|
|
for ( i = 0 ; i < tr.numImages ; i++ ) {
|
|
glt = tr.images[ i ];
|
|
if ( glt->mipmap ) {
|
|
GL_Bind (glt);
|
|
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
|
|
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_SumOfUsedImages
|
|
===============
|
|
*/
|
|
int R_SumOfUsedImages( void ) {
|
|
int total;
|
|
int i;
|
|
|
|
total = 0;
|
|
for ( i = 0; i < tr.numImages; i++ ) {
|
|
if ( tr.images[i]->frameUsed == tr.frameCount ) {
|
|
total += tr.images[i]->uploadWidth * tr.images[i]->uploadHeight;
|
|
}
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_ImageList_f
|
|
===============
|
|
*/
|
|
void R_ImageList_f( void ) {
|
|
int i;
|
|
image_t *image;
|
|
int texels;
|
|
const char *yesno[] = {
|
|
"no ", "yes"
|
|
};
|
|
|
|
ri.Printf (PRINT_ALL, "\n -w-- -h-- -mm- -TMU- -if-- wrap --name-------\n");
|
|
texels = 0;
|
|
|
|
for ( i = 0 ; i < tr.numImages ; i++ ) {
|
|
image = tr.images[ i ];
|
|
|
|
texels += image->uploadWidth*image->uploadHeight;
|
|
ri.Printf (PRINT_ALL, "%4i: %4i %4i %s %d ",
|
|
i, image->uploadWidth, image->uploadHeight, yesno[image->mipmap], image->TMU );
|
|
switch ( image->internalFormat ) {
|
|
case 1:
|
|
ri.Printf( PRINT_ALL, "I " );
|
|
break;
|
|
case 2:
|
|
ri.Printf( PRINT_ALL, "IA " );
|
|
break;
|
|
case 3:
|
|
ri.Printf( PRINT_ALL, "RGB " );
|
|
break;
|
|
case 4:
|
|
ri.Printf( PRINT_ALL, "RGBA " );
|
|
break;
|
|
case GL_RGBA8:
|
|
ri.Printf( PRINT_ALL, "RGBA8" );
|
|
break;
|
|
case GL_RGB8:
|
|
ri.Printf( PRINT_ALL, "RGB8" );
|
|
break;
|
|
case GL_RGB4_S3TC:
|
|
ri.Printf( PRINT_ALL, "S3TC " );
|
|
break;
|
|
case GL_RGBA4:
|
|
ri.Printf( PRINT_ALL, "RGBA4" );
|
|
break;
|
|
case GL_RGB5:
|
|
ri.Printf( PRINT_ALL, "RGB5 " );
|
|
break;
|
|
default:
|
|
ri.Printf( PRINT_ALL, "???? " );
|
|
}
|
|
|
|
switch ( image->wrapClampMode ) {
|
|
case GL_REPEAT:
|
|
ri.Printf( PRINT_ALL, "rept " );
|
|
break;
|
|
case GL_CLAMP:
|
|
ri.Printf( PRINT_ALL, "clmp " );
|
|
break;
|
|
default:
|
|
ri.Printf( PRINT_ALL, "%4i ", image->wrapClampMode );
|
|
break;
|
|
}
|
|
|
|
ri.Printf( PRINT_ALL, " %s\n", image->imgName );
|
|
}
|
|
ri.Printf (PRINT_ALL, " ---------\n");
|
|
ri.Printf (PRINT_ALL, " %i total texels (not including mipmaps)\n", texels);
|
|
ri.Printf (PRINT_ALL, " %i total images\n\n", tr.numImages );
|
|
}
|
|
|
|
//=======================================================================
|
|
|
|
/*
|
|
================
|
|
ResampleTexture
|
|
|
|
Used to resample images in a more general than quartering fashion.
|
|
|
|
This will only be filtered properly if the resampled size
|
|
is greater than half the original size.
|
|
|
|
If a larger shrinking is needed, use the mipmap function
|
|
before or after.
|
|
================
|
|
*/
|
|
static void ResampleTexture( unsigned *in, int inwidth, int inheight, unsigned *out,
|
|
int outwidth, int outheight ) {
|
|
int i, j;
|
|
unsigned *inrow, *inrow2;
|
|
unsigned frac, fracstep;
|
|
unsigned p1[2048], p2[2048];
|
|
byte *pix1, *pix2, *pix3, *pix4;
|
|
|
|
if (outwidth>2048)
|
|
ri.Error(ERR_DROP, "ResampleTexture: max width");
|
|
|
|
fracstep = inwidth*0x10000/outwidth;
|
|
|
|
frac = fracstep>>2;
|
|
for ( i=0 ; i<outwidth ; i++ ) {
|
|
p1[i] = 4*(frac>>16);
|
|
frac += fracstep;
|
|
}
|
|
frac = 3*(fracstep>>2);
|
|
for ( i=0 ; i<outwidth ; i++ ) {
|
|
p2[i] = 4*(frac>>16);
|
|
frac += fracstep;
|
|
}
|
|
|
|
for (i=0 ; i<outheight ; i++, out += outwidth) {
|
|
inrow = in + inwidth*(int)((i+0.25)*inheight/outheight);
|
|
inrow2 = in + inwidth*(int)((i+0.75)*inheight/outheight);
|
|
frac = fracstep >> 1;
|
|
for (j=0 ; j<outwidth ; j++) {
|
|
pix1 = (byte *)inrow + p1[j];
|
|
pix2 = (byte *)inrow + p2[j];
|
|
pix3 = (byte *)inrow2 + p1[j];
|
|
pix4 = (byte *)inrow2 + p2[j];
|
|
((byte *)(out+j))[0] = (pix1[0] + pix2[0] + pix3[0] + pix4[0])>>2;
|
|
((byte *)(out+j))[1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1])>>2;
|
|
((byte *)(out+j))[2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2])>>2;
|
|
((byte *)(out+j))[3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3])>>2;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_LightScaleTexture
|
|
|
|
Scale up the pixel values in a texture to increase the
|
|
lighting range
|
|
================
|
|
*/
|
|
void R_LightScaleTexture (unsigned *in, int inwidth, int inheight, qboolean only_gamma )
|
|
{
|
|
if ( only_gamma )
|
|
{
|
|
if ( !glConfig.deviceSupportsGamma )
|
|
{
|
|
int i, c;
|
|
byte *p;
|
|
|
|
p = (byte *)in;
|
|
|
|
c = inwidth*inheight;
|
|
for (i=0 ; i<c ; i++, p+=4)
|
|
{
|
|
p[0] = s_gammatable[p[0]];
|
|
p[1] = s_gammatable[p[1]];
|
|
p[2] = s_gammatable[p[2]];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int i, c;
|
|
byte *p;
|
|
|
|
p = (byte *)in;
|
|
|
|
c = inwidth*inheight;
|
|
|
|
if ( glConfig.deviceSupportsGamma )
|
|
{
|
|
for (i=0 ; i<c ; i++, p+=4)
|
|
{
|
|
p[0] = s_intensitytable[p[0]];
|
|
p[1] = s_intensitytable[p[1]];
|
|
p[2] = s_intensitytable[p[2]];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i=0 ; i<c ; i++, p+=4)
|
|
{
|
|
p[0] = s_gammatable[s_intensitytable[p[0]]];
|
|
p[1] = s_gammatable[s_intensitytable[p[1]]];
|
|
p[2] = s_gammatable[s_intensitytable[p[2]]];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
R_MipMap2
|
|
|
|
Operates in place, quartering the size of the texture
|
|
Proper linear filter
|
|
================
|
|
*/
|
|
static void R_MipMap2( unsigned *in, int inWidth, int inHeight ) {
|
|
int i, j, k;
|
|
byte *outpix;
|
|
int inWidthMask, inHeightMask;
|
|
int total;
|
|
int outWidth, outHeight;
|
|
unsigned *temp;
|
|
|
|
outWidth = inWidth >> 1;
|
|
outHeight = inHeight >> 1;
|
|
temp = ri.Hunk_AllocateTempMemory( outWidth * outHeight * 4 );
|
|
|
|
inWidthMask = inWidth - 1;
|
|
inHeightMask = inHeight - 1;
|
|
|
|
for ( i = 0 ; i < outHeight ; i++ ) {
|
|
for ( j = 0 ; j < outWidth ; j++ ) {
|
|
outpix = (byte *) ( temp + i * outWidth + j );
|
|
for ( k = 0 ; k < 4 ; k++ ) {
|
|
total =
|
|
1 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] +
|
|
2 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] +
|
|
2 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] +
|
|
1 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] +
|
|
|
|
2 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] +
|
|
4 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] +
|
|
4 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] +
|
|
2 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] +
|
|
|
|
2 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] +
|
|
4 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] +
|
|
4 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] +
|
|
2 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] +
|
|
|
|
1 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] +
|
|
2 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] +
|
|
2 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] +
|
|
1 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k];
|
|
outpix[k] = total / 36;
|
|
}
|
|
}
|
|
}
|
|
|
|
Com_Memcpy( in, temp, outWidth * outHeight * 4 );
|
|
ri.Hunk_FreeTempMemory( temp );
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_MipMap
|
|
|
|
Operates in place, quartering the size of the texture
|
|
================
|
|
*/
|
|
static void R_MipMap (byte *in, int width, int height) {
|
|
int i, j;
|
|
byte *out;
|
|
int row;
|
|
|
|
if ( !r_simpleMipMaps->integer ) {
|
|
R_MipMap2( (unsigned *)in, width, height );
|
|
return;
|
|
}
|
|
|
|
if ( width == 1 && height == 1 ) {
|
|
return;
|
|
}
|
|
|
|
row = width * 4;
|
|
out = in;
|
|
width >>= 1;
|
|
height >>= 1;
|
|
|
|
if ( width == 0 || height == 0 ) {
|
|
width += height; // get largest
|
|
for (i=0 ; i<width ; i++, out+=4, in+=8 ) {
|
|
out[0] = ( in[0] + in[4] )>>1;
|
|
out[1] = ( in[1] + in[5] )>>1;
|
|
out[2] = ( in[2] + in[6] )>>1;
|
|
out[3] = ( in[3] + in[7] )>>1;
|
|
}
|
|
return;
|
|
}
|
|
|
|
for (i=0 ; i<height ; i++, in+=row) {
|
|
for (j=0 ; j<width ; j++, out+=4, in+=8) {
|
|
out[0] = (in[0] + in[4] + in[row+0] + in[row+4])>>2;
|
|
out[1] = (in[1] + in[5] + in[row+1] + in[row+5])>>2;
|
|
out[2] = (in[2] + in[6] + in[row+2] + in[row+6])>>2;
|
|
out[3] = (in[3] + in[7] + in[row+3] + in[row+7])>>2;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
R_BlendOverTexture
|
|
|
|
Apply a color blend over a set of pixels
|
|
==================
|
|
*/
|
|
static void R_BlendOverTexture( byte *data, int pixelCount, byte blend[4] ) {
|
|
int i;
|
|
int inverseAlpha;
|
|
int premult[3];
|
|
|
|
inverseAlpha = 255 - blend[3];
|
|
premult[0] = blend[0] * blend[3];
|
|
premult[1] = blend[1] * blend[3];
|
|
premult[2] = blend[2] * blend[3];
|
|
|
|
for ( i = 0 ; i < pixelCount ; i++, data+=4 ) {
|
|
data[0] = ( data[0] * inverseAlpha + premult[0] ) >> 9;
|
|
data[1] = ( data[1] * inverseAlpha + premult[1] ) >> 9;
|
|
data[2] = ( data[2] * inverseAlpha + premult[2] ) >> 9;
|
|
}
|
|
}
|
|
|
|
byte mipBlendColors[16][4] = {
|
|
{0,0,0,0},
|
|
{255,0,0,128},
|
|
{0,255,0,128},
|
|
{0,0,255,128},
|
|
{255,0,0,128},
|
|
{0,255,0,128},
|
|
{0,0,255,128},
|
|
{255,0,0,128},
|
|
{0,255,0,128},
|
|
{0,0,255,128},
|
|
{255,0,0,128},
|
|
{0,255,0,128},
|
|
{0,0,255,128},
|
|
{255,0,0,128},
|
|
{0,255,0,128},
|
|
{0,0,255,128},
|
|
};
|
|
|
|
|
|
/*
|
|
===============
|
|
Upload32
|
|
|
|
===============
|
|
*/
|
|
extern qboolean charSet;
|
|
static void Upload32( unsigned *data,
|
|
int width, int height,
|
|
qboolean mipmap,
|
|
qboolean picmip,
|
|
qboolean lightMap,
|
|
int *format,
|
|
int *pUploadWidth, int *pUploadHeight )
|
|
{
|
|
int samples;
|
|
unsigned *scaledBuffer = NULL;
|
|
unsigned *resampledBuffer = NULL;
|
|
int scaled_width, scaled_height;
|
|
int i, c;
|
|
byte *scan;
|
|
GLenum internalFormat = GL_RGB;
|
|
float rMax = 0, gMax = 0, bMax = 0;
|
|
|
|
//
|
|
// convert to exact power of 2 sizes
|
|
//
|
|
for (scaled_width = 1 ; scaled_width < width ; scaled_width<<=1)
|
|
;
|
|
for (scaled_height = 1 ; scaled_height < height ; scaled_height<<=1)
|
|
;
|
|
if ( r_roundImagesDown->integer && scaled_width > width )
|
|
scaled_width >>= 1;
|
|
if ( r_roundImagesDown->integer && scaled_height > height )
|
|
scaled_height >>= 1;
|
|
|
|
if ( scaled_width != width || scaled_height != height ) {
|
|
resampledBuffer = ri.Hunk_AllocateTempMemory( scaled_width * scaled_height * 4 );
|
|
ResampleTexture (data, width, height, resampledBuffer, scaled_width, scaled_height);
|
|
data = resampledBuffer;
|
|
width = scaled_width;
|
|
height = scaled_height;
|
|
}
|
|
|
|
//
|
|
// perform optional picmip operation
|
|
//
|
|
if ( picmip ) {
|
|
scaled_width >>= r_picmip->integer;
|
|
scaled_height >>= r_picmip->integer;
|
|
}
|
|
|
|
//
|
|
// clamp to minimum size
|
|
//
|
|
if (scaled_width < 1) {
|
|
scaled_width = 1;
|
|
}
|
|
if (scaled_height < 1) {
|
|
scaled_height = 1;
|
|
}
|
|
|
|
//
|
|
// clamp to the current upper OpenGL limit
|
|
// scale both axis down equally so we don't have to
|
|
// deal with a half mip resampling
|
|
//
|
|
while ( scaled_width > glConfig.maxTextureSize
|
|
|| scaled_height > glConfig.maxTextureSize ) {
|
|
scaled_width >>= 1;
|
|
scaled_height >>= 1;
|
|
}
|
|
|
|
scaledBuffer = ri.Hunk_AllocateTempMemory( sizeof( unsigned ) * scaled_width * scaled_height );
|
|
|
|
//
|
|
// scan the texture for each channel's max values
|
|
// and verify if the alpha channel is being used or not
|
|
//
|
|
c = width*height;
|
|
scan = ((byte *)data);
|
|
samples = 3;
|
|
if (!lightMap) {
|
|
for ( i = 0; i < c; i++ )
|
|
{
|
|
if ( scan[i*4+0] > rMax )
|
|
{
|
|
rMax = scan[i*4+0];
|
|
}
|
|
if ( scan[i*4+1] > gMax )
|
|
{
|
|
gMax = scan[i*4+1];
|
|
}
|
|
if ( scan[i*4+2] > bMax )
|
|
{
|
|
bMax = scan[i*4+2];
|
|
}
|
|
if ( scan[i*4 + 3] != 255 )
|
|
{
|
|
samples = 4;
|
|
break;
|
|
}
|
|
}
|
|
// select proper internal format
|
|
if ( samples == 3 )
|
|
{
|
|
if ( glConfig.textureCompression == TC_S3TC )
|
|
{
|
|
internalFormat = GL_RGB4_S3TC;
|
|
}
|
|
else if ( r_texturebits->integer == 16 )
|
|
{
|
|
internalFormat = GL_RGB5;
|
|
}
|
|
else if ( r_texturebits->integer == 32 )
|
|
{
|
|
internalFormat = GL_RGB8;
|
|
}
|
|
else
|
|
{
|
|
internalFormat = 3;
|
|
}
|
|
}
|
|
else if ( samples == 4 )
|
|
{
|
|
if ( r_texturebits->integer == 16 )
|
|
{
|
|
internalFormat = GL_RGBA4;
|
|
}
|
|
else if ( r_texturebits->integer == 32 )
|
|
{
|
|
internalFormat = GL_RGBA8;
|
|
}
|
|
else
|
|
{
|
|
internalFormat = 4;
|
|
}
|
|
}
|
|
} else {
|
|
internalFormat = 3;
|
|
}
|
|
// copy or resample data as appropriate for first MIP level
|
|
if ( ( scaled_width == width ) &&
|
|
( scaled_height == height ) ) {
|
|
if (!mipmap)
|
|
{
|
|
qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
|
|
*pUploadWidth = scaled_width;
|
|
*pUploadHeight = scaled_height;
|
|
*format = internalFormat;
|
|
|
|
goto done;
|
|
}
|
|
Com_Memcpy (scaledBuffer, data, width*height*4);
|
|
}
|
|
else
|
|
{
|
|
// use the normal mip-mapping function to go down from here
|
|
while ( width > scaled_width || height > scaled_height ) {
|
|
R_MipMap( (byte *)data, width, height );
|
|
width >>= 1;
|
|
height >>= 1;
|
|
if ( width < 1 ) {
|
|
width = 1;
|
|
}
|
|
if ( height < 1 ) {
|
|
height = 1;
|
|
}
|
|
}
|
|
Com_Memcpy( scaledBuffer, data, width * height * 4 );
|
|
}
|
|
|
|
R_LightScaleTexture (scaledBuffer, scaled_width, scaled_height, !mipmap );
|
|
|
|
*pUploadWidth = scaled_width;
|
|
*pUploadHeight = scaled_height;
|
|
*format = internalFormat;
|
|
|
|
qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer );
|
|
|
|
if (mipmap)
|
|
{
|
|
int miplevel;
|
|
|
|
miplevel = 0;
|
|
while (scaled_width > 1 || scaled_height > 1)
|
|
{
|
|
R_MipMap( (byte *)scaledBuffer, scaled_width, scaled_height );
|
|
scaled_width >>= 1;
|
|
scaled_height >>= 1;
|
|
if (scaled_width < 1)
|
|
scaled_width = 1;
|
|
if (scaled_height < 1)
|
|
scaled_height = 1;
|
|
miplevel++;
|
|
|
|
if ( r_colorMipLevels->integer ) {
|
|
R_BlendOverTexture( (byte *)scaledBuffer, scaled_width * scaled_height, mipBlendColors[miplevel] );
|
|
}
|
|
|
|
qglTexImage2D (GL_TEXTURE_2D, miplevel, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer );
|
|
}
|
|
}
|
|
done:
|
|
|
|
if (mipmap)
|
|
{
|
|
if ( textureFilterAnisotropic )
|
|
qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT,
|
|
(GLint)Com_Clamp( 1, maxAnisotropy, r_ext_max_anisotropy->integer ) );
|
|
|
|
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
|
|
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
|
|
}
|
|
else
|
|
{
|
|
if ( textureFilterAnisotropic )
|
|
qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1 );
|
|
|
|
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
|
|
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
|
|
}
|
|
|
|
GL_CheckErrors();
|
|
|
|
if ( scaledBuffer != 0 )
|
|
ri.Hunk_FreeTempMemory( scaledBuffer );
|
|
if ( resampledBuffer != 0 )
|
|
ri.Hunk_FreeTempMemory( resampledBuffer );
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
R_CreateImage
|
|
|
|
This is the only way any image_t are created
|
|
================
|
|
*/
|
|
image_t *R_CreateImage( const char *name, const byte *pic, int width, int height,
|
|
qboolean mipmap, qboolean allowPicmip, int glWrapClampMode ) {
|
|
image_t *image;
|
|
qboolean isLightmap = qfalse;
|
|
long hash;
|
|
|
|
if (strlen(name) >= MAX_QPATH ) {
|
|
ri.Error (ERR_DROP, "R_CreateImage: \"%s\" is too long\n", name);
|
|
}
|
|
if ( !strncmp( name, "*lightmap", 9 ) ) {
|
|
isLightmap = qtrue;
|
|
}
|
|
|
|
if ( tr.numImages == MAX_DRAWIMAGES ) {
|
|
ri.Error( ERR_DROP, "R_CreateImage: MAX_DRAWIMAGES hit\n");
|
|
}
|
|
|
|
image = tr.images[tr.numImages] = ri.Hunk_Alloc( sizeof( image_t ), h_low );
|
|
image->texnum = 1024 + tr.numImages;
|
|
tr.numImages++;
|
|
|
|
image->mipmap = mipmap;
|
|
image->allowPicmip = allowPicmip;
|
|
|
|
strcpy (image->imgName, name);
|
|
|
|
image->width = width;
|
|
image->height = height;
|
|
image->wrapClampMode = glWrapClampMode;
|
|
|
|
// lightmaps are always allocated on TMU 1
|
|
if ( qglActiveTextureARB && isLightmap ) {
|
|
image->TMU = 1;
|
|
} else {
|
|
image->TMU = 0;
|
|
}
|
|
|
|
if ( qglActiveTextureARB ) {
|
|
GL_SelectTexture( image->TMU );
|
|
}
|
|
|
|
GL_Bind(image);
|
|
|
|
Upload32( (unsigned *)pic, image->width, image->height,
|
|
image->mipmap,
|
|
allowPicmip,
|
|
isLightmap,
|
|
&image->internalFormat,
|
|
&image->uploadWidth,
|
|
&image->uploadHeight );
|
|
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glWrapClampMode );
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glWrapClampMode );
|
|
|
|
qglBindTexture( GL_TEXTURE_2D, 0 );
|
|
|
|
if ( image->TMU == 1 ) {
|
|
GL_SelectTexture( 0 );
|
|
}
|
|
|
|
hash = generateHashValue(name);
|
|
image->next = hashTable[hash];
|
|
hashTable[hash] = image;
|
|
|
|
return image;
|
|
}
|
|
|
|
|
|
/*
|
|
=========================================================
|
|
|
|
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;
|
|
unsigned 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;
|
|
|
|
Com_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(columns <= 0 || !rows || numPixels > 0x1FFFFFFF // 4*1FFFFFFF == 0x7FFFFFFC < 0x7FFFFFFF
|
|
|| ((numPixels * 4) / columns) / 4 != rows)
|
|
{
|
|
ri.Error (ERR_DROP, "LoadBMP: %s has an invalid image size\n", name);
|
|
}
|
|
|
|
if ( width )
|
|
*width = columns;
|
|
if ( height )
|
|
*height = rows;
|
|
|
|
bmpRGBA = 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 );
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
=================================================================
|
|
|
|
PCX LOADING
|
|
|
|
=================================================================
|
|
*/
|
|
|
|
|
|
/*
|
|
==============
|
|
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;
|
|
unsigned 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 = ri.Malloc ( (ymax+1) * (xmax+1) );
|
|
|
|
*pic = out;
|
|
|
|
pix = out;
|
|
|
|
if (palette)
|
|
{
|
|
*palette = ri.Malloc(768);
|
|
Com_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;
|
|
}
|
|
|
|
// LoadPCX32 ensures width, height < 1024
|
|
c = (*width) * (*height);
|
|
pic32 = *pic = 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);
|
|
}
|
|
|
|
/*
|
|
=========================================================
|
|
|
|
TARGA LOADING
|
|
|
|
=========================================================
|
|
*/
|
|
|
|
/*
|
|
=============
|
|
LoadTGA
|
|
=============
|
|
*/
|
|
static void LoadTGA ( const char *name, byte **pic, int *width, int *height)
|
|
{
|
|
unsigned columns, rows, numPixels;
|
|
byte *pixbuf;
|
|
int row, column;
|
|
byte *buf_p;
|
|
byte *buffer;
|
|
TargaHeader targa_header;
|
|
byte *targa_rgba;
|
|
|
|
*pic = NULL;
|
|
|
|
//
|
|
// load the file
|
|
//
|
|
ri.FS_ReadFile ( ( char * ) name, (void **)&buffer);
|
|
if (!buffer) {
|
|
return;
|
|
}
|
|
|
|
buf_p = buffer;
|
|
|
|
targa_header.id_length = buf_p[0];
|
|
targa_header.colormap_type = buf_p[1];
|
|
targa_header.image_type = buf_p[2];
|
|
|
|
memcpy(&targa_header.colormap_index, &buf_p[3], 2);
|
|
memcpy(&targa_header.colormap_length, &buf_p[5], 2);
|
|
targa_header.colormap_size = buf_p[7];
|
|
memcpy(&targa_header.x_origin, &buf_p[8], 2);
|
|
memcpy(&targa_header.y_origin, &buf_p[10], 2);
|
|
memcpy(&targa_header.width, &buf_p[12], 2);
|
|
memcpy(&targa_header.height, &buf_p[14], 2);
|
|
targa_header.pixel_size = buf_p[16];
|
|
targa_header.attributes = buf_p[17];
|
|
|
|
targa_header.colormap_index = LittleShort(targa_header.colormap_index);
|
|
targa_header.colormap_length = LittleShort(targa_header.colormap_length);
|
|
targa_header.x_origin = LittleShort(targa_header.x_origin);
|
|
targa_header.y_origin = LittleShort(targa_header.y_origin);
|
|
targa_header.width = LittleShort(targa_header.width);
|
|
targa_header.height = LittleShort(targa_header.height);
|
|
|
|
buf_p += 18;
|
|
|
|
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 * 4;
|
|
|
|
if (width)
|
|
*width = columns;
|
|
if (height)
|
|
*height = rows;
|
|
|
|
if(!columns || !rows || numPixels > 0x7FFFFFFF || numPixels / columns / 4 != rows)
|
|
{
|
|
ri.Error (ERR_DROP, "LoadTGA: %s has an invalid image size\n", name);
|
|
}
|
|
|
|
targa_rgba = ri.Malloc (numPixels);
|
|
*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:
|
|
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 = 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:
|
|
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 = 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:
|
|
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 = targa_rgba + row*columns*4;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
breakOut:;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
// TTimo: this is the chunk of code to ensure a behavior that meets TGA specs
|
|
// bk0101024 - fix from Leonardo
|
|
// bit 5 set => top-down
|
|
if (targa_header.attributes & 0x20) {
|
|
unsigned char *flip = (unsigned char*)malloc (columns*4);
|
|
unsigned char *src, *dst;
|
|
|
|
for (row = 0; row < rows/2; row++) {
|
|
src = targa_rgba + row * 4 * columns;
|
|
dst = targa_rgba + (rows - row - 1) * 4 * columns;
|
|
|
|
memcpy (flip, src, columns*4);
|
|
memcpy (src, dst, columns*4);
|
|
memcpy (dst, flip, columns*4);
|
|
}
|
|
free (flip);
|
|
}
|
|
#endif
|
|
// instead we just print a warning
|
|
if (targa_header.attributes & 0x20) {
|
|
ri.Printf( PRINT_WARNING, "WARNING: '%s' TGA file header declares top-down image, ignoring\n", name);
|
|
}
|
|
|
|
ri.FS_FreeFile (buffer);
|
|
}
|
|
|
|
static void LoadJPG( const char *filename, unsigned char **pic, int *width, int *height ) {
|
|
/* 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 = {NULL};
|
|
/* 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 */
|
|
unsigned row_stride; /* physical row width in output buffer */
|
|
unsigned pixelcount, memcount;
|
|
unsigned char *out;
|
|
byte *fbuffer;
|
|
byte *buf;
|
|
|
|
/* 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.
|
|
*/
|
|
|
|
ri.FS_ReadFile ( ( char * ) filename, (void **)&fbuffer);
|
|
if (!fbuffer) {
|
|
return;
|
|
}
|
|
|
|
/* 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) */
|
|
|
|
jpeg_stdio_src(&cinfo, fbuffer);
|
|
|
|
/* 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 */
|
|
|
|
pixelcount = cinfo.output_width * cinfo.output_height;
|
|
|
|
if(!cinfo.output_width || !cinfo.output_height
|
|
|| ((pixelcount * 4) / cinfo.output_width) / 4 != cinfo.output_height
|
|
|| pixelcount > 0x1FFFFFFF || cinfo.output_components > 4) // 4*1FFFFFFF == 0x7FFFFFFC < 0x7FFFFFFF
|
|
{
|
|
ri.Error (ERR_DROP, "LoadJPG: %s has an invalid image size: %dx%d*4=%d, components: %d\n", filename,
|
|
cinfo.output_width, cinfo.output_height, pixelcount * 4, cinfo.output_components);
|
|
}
|
|
|
|
memcount = pixelcount * 4;
|
|
row_stride = cinfo.output_width * cinfo.output_components;
|
|
|
|
out = ri.Malloc(memcount);
|
|
|
|
*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.
|
|
*/
|
|
buf = ((out+(row_stride*cinfo.output_scanline)));
|
|
buffer = &buf;
|
|
(void) jpeg_read_scanlines(&cinfo, buffer, 1);
|
|
}
|
|
|
|
buf = out;
|
|
|
|
// If we are processing an 8-bit JPEG (greyscale), we'll have to convert
|
|
// the greyscale values to RGBA.
|
|
if(cinfo.output_components == 1)
|
|
{
|
|
int sindex = pixelcount, dindex = memcount;
|
|
unsigned char greyshade;
|
|
|
|
// Only pixelcount number of bytes have been written.
|
|
// Expand the color values over the rest of the buffer, starting
|
|
// from the end.
|
|
do
|
|
{
|
|
greyshade = buf[--sindex];
|
|
|
|
buf[--dindex] = 255;
|
|
buf[--dindex] = greyshade;
|
|
buf[--dindex] = greyshade;
|
|
buf[--dindex] = greyshade;
|
|
} while(sindex);
|
|
}
|
|
else
|
|
{
|
|
// clear all the alphas to 255
|
|
int i;
|
|
|
|
for ( i = 3 ; i < memcount ; i+=4 )
|
|
{
|
|
buf[i] = 255;
|
|
}
|
|
}
|
|
|
|
*pic = out;
|
|
|
|
/* 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...)
|
|
*/
|
|
ri.FS_FreeFile (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! */
|
|
}
|
|
|
|
|
|
/* Expanded data destination object for stdio output */
|
|
|
|
typedef struct {
|
|
struct jpeg_destination_mgr pub; /* public fields */
|
|
|
|
byte* outfile; /* target stream */
|
|
int size;
|
|
} my_destination_mgr;
|
|
|
|
typedef my_destination_mgr * my_dest_ptr;
|
|
|
|
|
|
/*
|
|
* Initialize destination --- called by jpeg_start_compress
|
|
* before any data is actually written.
|
|
*/
|
|
|
|
void init_destination (j_compress_ptr cinfo)
|
|
{
|
|
my_dest_ptr dest = (my_dest_ptr) cinfo->dest;
|
|
|
|
dest->pub.next_output_byte = dest->outfile;
|
|
dest->pub.free_in_buffer = dest->size;
|
|
}
|
|
|
|
|
|
/*
|
|
* Empty the output buffer --- called whenever buffer fills up.
|
|
*
|
|
* In typical applications, this should write the entire output buffer
|
|
* (ignoring the current state of next_output_byte & free_in_buffer),
|
|
* reset the pointer & count to the start of the buffer, and return TRUE
|
|
* indicating that the buffer has been dumped.
|
|
*
|
|
* In applications that need to be able to suspend compression due to output
|
|
* overrun, a FALSE return indicates that the buffer cannot be emptied now.
|
|
* In this situation, the compressor will return to its caller (possibly with
|
|
* an indication that it has not accepted all the supplied scanlines). The
|
|
* application should resume compression after it has made more room in the
|
|
* output buffer. Note that there are substantial restrictions on the use of
|
|
* suspension --- see the documentation.
|
|
*
|
|
* When suspending, the compressor will back up to a convenient restart point
|
|
* (typically the start of the current MCU). next_output_byte & free_in_buffer
|
|
* indicate where the restart point will be if the current call returns FALSE.
|
|
* Data beyond this point will be regenerated after resumption, so do not
|
|
* write it out when emptying the buffer externally.
|
|
*/
|
|
|
|
boolean empty_output_buffer (j_compress_ptr cinfo)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*
|
|
* Compression initialization.
|
|
* Before calling this, all parameters and a data destination must be set up.
|
|
*
|
|
* We require a write_all_tables parameter as a failsafe check when writing
|
|
* multiple datastreams from the same compression object. Since prior runs
|
|
* will have left all the tables marked sent_table=TRUE, a subsequent run
|
|
* would emit an abbreviated stream (no tables) by default. This may be what
|
|
* is wanted, but for safety's sake it should not be the default behavior:
|
|
* programmers should have to make a deliberate choice to emit abbreviated
|
|
* images. Therefore the documentation and examples should encourage people
|
|
* to pass write_all_tables=TRUE; then it will take active thought to do the
|
|
* wrong thing.
|
|
*/
|
|
|
|
GLOBAL void
|
|
jpeg_start_compress (j_compress_ptr cinfo, boolean write_all_tables)
|
|
{
|
|
if (cinfo->global_state != CSTATE_START)
|
|
ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
|
|
|
|
if (write_all_tables)
|
|
jpeg_suppress_tables(cinfo, FALSE); /* mark all tables to be written */
|
|
|
|
/* (Re)initialize error mgr and destination modules */
|
|
(*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo);
|
|
(*cinfo->dest->init_destination) (cinfo);
|
|
/* Perform master selection of active modules */
|
|
jinit_compress_master(cinfo);
|
|
/* Set up for the first pass */
|
|
(*cinfo->master->prepare_for_pass) (cinfo);
|
|
/* Ready for application to drive first pass through jpeg_write_scanlines
|
|
* or jpeg_write_raw_data.
|
|
*/
|
|
cinfo->next_scanline = 0;
|
|
cinfo->global_state = (cinfo->raw_data_in ? CSTATE_RAW_OK : CSTATE_SCANNING);
|
|
}
|
|
|
|
|
|
/*
|
|
* Write some scanlines of data to the JPEG compressor.
|
|
*
|
|
* The return value will be the number of lines actually written.
|
|
* This should be less than the supplied num_lines only in case that
|
|
* the data destination module has requested suspension of the compressor,
|
|
* or if more than image_height scanlines are passed in.
|
|
*
|
|
* Note: we warn about excess calls to jpeg_write_scanlines() since
|
|
* this likely signals an application programmer error. However,
|
|
* excess scanlines passed in the last valid call are *silently* ignored,
|
|
* so that the application need not adjust num_lines for end-of-image
|
|
* when using a multiple-scanline buffer.
|
|
*/
|
|
|
|
GLOBAL JDIMENSION
|
|
jpeg_write_scanlines (j_compress_ptr cinfo, JSAMPARRAY scanlines,
|
|
JDIMENSION num_lines)
|
|
{
|
|
JDIMENSION row_ctr, rows_left;
|
|
|
|
if (cinfo->global_state != CSTATE_SCANNING)
|
|
ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
|
|
if (cinfo->next_scanline >= cinfo->image_height)
|
|
WARNMS(cinfo, JWRN_TOO_MUCH_DATA);
|
|
|
|
/* Call progress monitor hook if present */
|
|
if (cinfo->progress != NULL) {
|
|
cinfo->progress->pass_counter = (long) cinfo->next_scanline;
|
|
cinfo->progress->pass_limit = (long) cinfo->image_height;
|
|
(*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo);
|
|
}
|
|
|
|
/* Give master control module another chance if this is first call to
|
|
* jpeg_write_scanlines. This lets output of the frame/scan headers be
|
|
* delayed so that application can write COM, etc, markers between
|
|
* jpeg_start_compress and jpeg_write_scanlines.
|
|
*/
|
|
if (cinfo->master->call_pass_startup)
|
|
(*cinfo->master->pass_startup) (cinfo);
|
|
|
|
/* Ignore any extra scanlines at bottom of image. */
|
|
rows_left = cinfo->image_height - cinfo->next_scanline;
|
|
if (num_lines > rows_left)
|
|
num_lines = rows_left;
|
|
|
|
row_ctr = 0;
|
|
(*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, num_lines);
|
|
cinfo->next_scanline += row_ctr;
|
|
return row_ctr;
|
|
}
|
|
|
|
/*
|
|
* Terminate destination --- called by jpeg_finish_compress
|
|
* after all data has been written. Usually needs to flush buffer.
|
|
*
|
|
* NB: *not* called by jpeg_abort or jpeg_destroy; surrounding
|
|
* application must deal with any cleanup that should happen even
|
|
* for error exit.
|
|
*/
|
|
|
|
static int hackSize;
|
|
|
|
void term_destination (j_compress_ptr cinfo)
|
|
{
|
|
my_dest_ptr dest = (my_dest_ptr) cinfo->dest;
|
|
size_t datacount = dest->size - dest->pub.free_in_buffer;
|
|
hackSize = datacount;
|
|
}
|
|
|
|
|
|
/*
|
|
* Prepare for output to a stdio stream.
|
|
* The caller must have already opened the stream, and is responsible
|
|
* for closing it after finishing compression.
|
|
*/
|
|
|
|
void jpegDest (j_compress_ptr cinfo, byte* outfile, int size)
|
|
{
|
|
my_dest_ptr dest;
|
|
|
|
/* The destination object is made permanent so that multiple JPEG images
|
|
* can be written to the same file without re-executing jpeg_stdio_dest.
|
|
* This makes it dangerous to use this manager and a different destination
|
|
* manager serially with the same JPEG object, because their private object
|
|
* sizes may be different. Caveat programmer.
|
|
*/
|
|
if (cinfo->dest == NULL) { /* first time for this JPEG object? */
|
|
cinfo->dest = (struct jpeg_destination_mgr *)
|
|
(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
|
|
sizeof(my_destination_mgr));
|
|
}
|
|
|
|
dest = (my_dest_ptr) cinfo->dest;
|
|
dest->pub.init_destination = init_destination;
|
|
dest->pub.empty_output_buffer = empty_output_buffer;
|
|
dest->pub.term_destination = term_destination;
|
|
dest->outfile = outfile;
|
|
dest->size = size;
|
|
}
|
|
|
|
void SaveJPG(char * filename, int quality, int image_width, int image_height, unsigned char *image_buffer) {
|
|
/* This struct contains the JPEG compression parameters and pointers to
|
|
* working space (which is allocated as needed by the JPEG library).
|
|
* It is possible to have several such structures, representing multiple
|
|
* compression/decompression processes, in existence at once. We refer
|
|
* to any one struct (and its associated working data) as a "JPEG object".
|
|
*/
|
|
struct jpeg_compress_struct cinfo;
|
|
/* 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 */
|
|
JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
|
|
int row_stride; /* physical row width in image buffer */
|
|
unsigned char *out;
|
|
|
|
/* Step 1: allocate and initialize JPEG compression 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 compression object. */
|
|
jpeg_create_compress(&cinfo);
|
|
|
|
/* Step 2: specify data destination (eg, a file) */
|
|
/* Note: steps 2 and 3 can be done in either order. */
|
|
|
|
/* Here we use the library-supplied code to send compressed data to a
|
|
* stdio stream. You can also write your own code to do something else.
|
|
* VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
|
|
* requires it in order to write binary files.
|
|
*/
|
|
out = ri.Hunk_AllocateTempMemory(image_width*image_height*4);
|
|
jpegDest(&cinfo, out, image_width*image_height*4);
|
|
|
|
/* Step 3: set parameters for compression */
|
|
|
|
/* First we supply a description of the input image.
|
|
* Four fields of the cinfo struct must be filled in:
|
|
*/
|
|
cinfo.image_width = image_width; /* image width and height, in pixels */
|
|
cinfo.image_height = image_height;
|
|
cinfo.input_components = 4; /* # of color components per pixel */
|
|
cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
|
|
/* Now use the library's routine to set default compression parameters.
|
|
* (You must set at least cinfo.in_color_space before calling this,
|
|
* since the defaults depend on the source color space.)
|
|
*/
|
|
jpeg_set_defaults(&cinfo);
|
|
/* Now you can set any non-default parameters you wish to.
|
|
* Here we just illustrate the use of quality (quantization table) scaling:
|
|
*/
|
|
jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
|
|
/* If quality is set high, disable chroma subsampling */
|
|
if (quality >= 85) {
|
|
cinfo.comp_info[0].h_samp_factor = 1;
|
|
cinfo.comp_info[0].v_samp_factor = 1;
|
|
}
|
|
|
|
/* Step 4: Start compressor */
|
|
|
|
/* TRUE ensures that we will write a complete interchange-JPEG file.
|
|
* Pass TRUE unless you are very sure of what you're doing.
|
|
*/
|
|
jpeg_start_compress(&cinfo, TRUE);
|
|
|
|
/* Step 5: while (scan lines remain to be written) */
|
|
/* jpeg_write_scanlines(...); */
|
|
|
|
/* Here we use the library's state variable cinfo.next_scanline as the
|
|
* loop counter, so that we don't have to keep track ourselves.
|
|
* To keep things simple, we pass one scanline per call; you can pass
|
|
* more if you wish, though.
|
|
*/
|
|
row_stride = image_width * 4; /* JSAMPLEs per row in image_buffer */
|
|
|
|
while (cinfo.next_scanline < cinfo.image_height) {
|
|
/* jpeg_write_scanlines expects an array of pointers to scanlines.
|
|
* Here the array is only one element long, but you could pass
|
|
* more than one scanline at a time if that's more convenient.
|
|
*/
|
|
row_pointer[0] = & image_buffer[((cinfo.image_height-1)*row_stride)-cinfo.next_scanline * row_stride];
|
|
(void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
|
|
}
|
|
|
|
/* Step 6: Finish compression */
|
|
|
|
jpeg_finish_compress(&cinfo);
|
|
/* After finish_compress, we can close the output file. */
|
|
ri.FS_WriteFile( filename, out, hackSize );
|
|
|
|
ri.Hunk_FreeTempMemory(out);
|
|
|
|
/* Step 7: release JPEG compression object */
|
|
|
|
/* This is an important step since it will release a good deal of memory. */
|
|
jpeg_destroy_compress(&cinfo);
|
|
|
|
/* And we're done! */
|
|
}
|
|
|
|
/*
|
|
=================
|
|
SaveJPGToBuffer
|
|
=================
|
|
*/
|
|
int SaveJPGToBuffer( byte *buffer, int quality,
|
|
int image_width, int image_height,
|
|
byte *image_buffer )
|
|
{
|
|
struct jpeg_compress_struct cinfo;
|
|
struct jpeg_error_mgr jerr;
|
|
JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
|
|
int row_stride; /* physical row width in image buffer */
|
|
|
|
/* Step 1: allocate and initialize JPEG compression object */
|
|
cinfo.err = jpeg_std_error(&jerr);
|
|
/* Now we can initialize the JPEG compression object. */
|
|
jpeg_create_compress(&cinfo);
|
|
|
|
/* Step 2: specify data destination (eg, a file) */
|
|
/* Note: steps 2 and 3 can be done in either order. */
|
|
jpegDest(&cinfo, buffer, image_width*image_height*4);
|
|
|
|
/* Step 3: set parameters for compression */
|
|
cinfo.image_width = image_width; /* image width and height, in pixels */
|
|
cinfo.image_height = image_height;
|
|
cinfo.input_components = 4; /* # of color components per pixel */
|
|
cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
|
|
|
|
jpeg_set_defaults(&cinfo);
|
|
jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
|
|
/* If quality is set high, disable chroma subsampling */
|
|
if (quality >= 85) {
|
|
cinfo.comp_info[0].h_samp_factor = 1;
|
|
cinfo.comp_info[0].v_samp_factor = 1;
|
|
}
|
|
|
|
/* Step 4: Start compressor */
|
|
jpeg_start_compress(&cinfo, TRUE);
|
|
|
|
/* Step 5: while (scan lines remain to be written) */
|
|
/* jpeg_write_scanlines(...); */
|
|
row_stride = image_width * 4; /* JSAMPLEs per row in image_buffer */
|
|
|
|
while (cinfo.next_scanline < cinfo.image_height) {
|
|
/* jpeg_write_scanlines expects an array of pointers to scanlines.
|
|
* Here the array is only one element long, but you could pass
|
|
* more than one scanline at a time if that's more convenient.
|
|
*/
|
|
row_pointer[0] = & image_buffer[((cinfo.image_height-1)*row_stride)-cinfo.next_scanline * row_stride];
|
|
(void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
|
|
}
|
|
|
|
/* Step 6: Finish compression */
|
|
jpeg_finish_compress(&cinfo);
|
|
|
|
/* Step 7: release JPEG compression object */
|
|
jpeg_destroy_compress(&cinfo);
|
|
|
|
/* And we're done! */
|
|
return hackSize;
|
|
}
|
|
|
|
//===================================================================
|
|
|
|
/*
|
|
=================
|
|
PNG LOADING
|
|
=================
|
|
*/
|
|
|
|
/*
|
|
* Quake 3 image format : RGBA
|
|
*/
|
|
|
|
#define Q3IMAGE_BYTESPERPIXEL (4)
|
|
|
|
/*
|
|
* PNG specifications
|
|
*/
|
|
|
|
/*
|
|
* The first 8 Bytes of every PNG-File are a fixed signature
|
|
* to identify the file as a PNG.
|
|
*/
|
|
|
|
#define PNG_Signature "\x89\x50\x4E\x47\xD\xA\x1A\xA"
|
|
#define PNG_Signature_Size (8)
|
|
|
|
/*
|
|
* After the signature diverse chunks follow.
|
|
* A chunk consists of a header and if Length
|
|
* is bigger than 0 a body and a CRC of the body follow.
|
|
*/
|
|
|
|
struct PNG_ChunkHeader
|
|
{
|
|
uint32_t Length;
|
|
uint32_t Type;
|
|
};
|
|
|
|
#define PNG_ChunkHeader_Size (8)
|
|
|
|
typedef uint32_t PNG_ChunkCRC;
|
|
|
|
#define PNG_ChunkCRC_Size (4)
|
|
|
|
/*
|
|
* We use the following ChunkTypes.
|
|
* All others are ignored.
|
|
*/
|
|
|
|
#define MAKE_CHUNKTYPE(a,b,c,d) (((a) << 24) | ((b) << 16) | ((c) << 8) | ((d)))
|
|
|
|
#define PNG_ChunkType_IHDR MAKE_CHUNKTYPE('I', 'H', 'D', 'R')
|
|
#define PNG_ChunkType_PLTE MAKE_CHUNKTYPE('P', 'L', 'T', 'E')
|
|
#define PNG_ChunkType_IDAT MAKE_CHUNKTYPE('I', 'D', 'A', 'T')
|
|
#define PNG_ChunkType_IEND MAKE_CHUNKTYPE('I', 'E', 'N', 'D')
|
|
#define PNG_ChunkType_tRNS MAKE_CHUNKTYPE('t', 'R', 'N', 'S')
|
|
|
|
/*
|
|
* Per specification the first chunk after the signature SHALL be IHDR.
|
|
*/
|
|
|
|
struct PNG_Chunk_IHDR
|
|
{
|
|
uint32_t Width;
|
|
uint32_t Height;
|
|
uint8_t BitDepth;
|
|
uint8_t ColourType;
|
|
uint8_t CompressionMethod;
|
|
uint8_t FilterMethod;
|
|
uint8_t InterlaceMethod;
|
|
};
|
|
|
|
#define PNG_Chunk_IHDR_Size (13)
|
|
|
|
/*
|
|
* ColourTypes
|
|
*/
|
|
|
|
#define PNG_ColourType_Grey (0)
|
|
#define PNG_ColourType_True (2)
|
|
#define PNG_ColourType_Indexed (3)
|
|
#define PNG_ColourType_GreyAlpha (4)
|
|
#define PNG_ColourType_TrueAlpha (6)
|
|
|
|
/*
|
|
* number of colour components
|
|
*
|
|
* Grey : 1 grey
|
|
* True : 1 R, 1 G, 1 B
|
|
* Indexed : 1 index
|
|
* GreyAlpha : 1 grey, 1 alpha
|
|
* TrueAlpha : 1 R, 1 G, 1 B, 1 alpha
|
|
*/
|
|
|
|
#define PNG_NumColourComponents_Grey (1)
|
|
#define PNG_NumColourComponents_True (3)
|
|
#define PNG_NumColourComponents_Indexed (1)
|
|
#define PNG_NumColourComponents_GreyAlpha (2)
|
|
#define PNG_NumColourComponents_TrueAlpha (4)
|
|
|
|
/*
|
|
* For the different ColourTypes
|
|
* different BitDepths are specified.
|
|
*/
|
|
|
|
#define PNG_BitDepth_1 ( 1)
|
|
#define PNG_BitDepth_2 ( 2)
|
|
#define PNG_BitDepth_4 ( 4)
|
|
#define PNG_BitDepth_8 ( 8)
|
|
#define PNG_BitDepth_16 (16)
|
|
|
|
/*
|
|
* Only one valid CompressionMethod is standardized.
|
|
*/
|
|
|
|
#define PNG_CompressionMethod_0 (0)
|
|
|
|
/*
|
|
* Only one valid FilterMethod is currently standardized.
|
|
*/
|
|
|
|
#define PNG_FilterMethod_0 (0)
|
|
|
|
/*
|
|
* This FilterMethod defines 5 FilterTypes
|
|
*/
|
|
|
|
#define PNG_FilterType_None (0)
|
|
#define PNG_FilterType_Sub (1)
|
|
#define PNG_FilterType_Up (2)
|
|
#define PNG_FilterType_Average (3)
|
|
#define PNG_FilterType_Paeth (4)
|
|
|
|
/*
|
|
* Two InterlaceMethods are standardized :
|
|
* 0 - NonInterlaced
|
|
* 1 - Interlaced
|
|
*/
|
|
|
|
#define PNG_InterlaceMethod_NonInterlaced (0)
|
|
#define PNG_InterlaceMethod_Interlaced (1)
|
|
|
|
/*
|
|
* The Adam7 interlace method uses 7 passes.
|
|
*/
|
|
|
|
#define PNG_Adam7_NumPasses (7)
|
|
|
|
/*
|
|
* The compressed data starts with a header ...
|
|
*/
|
|
|
|
struct PNG_ZlibHeader
|
|
{
|
|
uint8_t CompressionMethod;
|
|
uint8_t Flags;
|
|
};
|
|
|
|
#define PNG_ZlibHeader_Size (2)
|
|
|
|
/*
|
|
* ... and is followed by a check value
|
|
*/
|
|
|
|
#define PNG_ZlibCheckValue_Size (4)
|
|
|
|
/*
|
|
* Some support functions for buffered files follow.
|
|
*/
|
|
|
|
/*
|
|
* buffered file representation
|
|
*/
|
|
|
|
struct BufferedFile
|
|
{
|
|
byte *Buffer;
|
|
int Length;
|
|
byte *Ptr;
|
|
int BytesLeft;
|
|
};
|
|
|
|
/*
|
|
* Read a file into a buffer.
|
|
*/
|
|
|
|
static struct BufferedFile *ReadBufferedFile(const char *name)
|
|
{
|
|
struct BufferedFile *BF;
|
|
|
|
/*
|
|
* input verification
|
|
*/
|
|
|
|
if(!name)
|
|
{
|
|
return(NULL);
|
|
}
|
|
|
|
/*
|
|
* Allocate control struct.
|
|
*/
|
|
|
|
BF = ri.Malloc(sizeof(struct BufferedFile));
|
|
if(!BF)
|
|
{
|
|
return(NULL);
|
|
}
|
|
|
|
/*
|
|
* Initialize the structs components.
|
|
*/
|
|
|
|
BF->Length = 0;
|
|
BF->Buffer = NULL;
|
|
BF->Ptr = NULL;
|
|
BF->BytesLeft = 0;
|
|
|
|
/*
|
|
* Read the file.
|
|
*/
|
|
|
|
BF->Length = ri.FS_ReadFile((char *) name, (void **) &BF->Buffer);
|
|
|
|
/*
|
|
* Did we get it? Is it big enough?
|
|
*/
|
|
|
|
if(!(BF->Buffer && (BF->Length > 0)))
|
|
{
|
|
ri.Free(BF);
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
/*
|
|
* Set the pointers and counters.
|
|
*/
|
|
|
|
BF->Ptr = BF->Buffer;
|
|
BF->BytesLeft = BF->Length;
|
|
|
|
return(BF);
|
|
}
|
|
|
|
/*
|
|
* Close a buffered file.
|
|
*/
|
|
|
|
static void CloseBufferedFile(struct BufferedFile *BF)
|
|
{
|
|
if(BF)
|
|
{
|
|
if(BF->Buffer)
|
|
{
|
|
ri.FS_FreeFile(BF->Buffer);
|
|
}
|
|
|
|
ri.Free(BF);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get a pointer to the requested bytes.
|
|
*/
|
|
|
|
static void *BufferedFileRead(struct BufferedFile *BF, int Length)
|
|
{
|
|
void *RetVal;
|
|
|
|
/*
|
|
* input verification
|
|
*/
|
|
|
|
if(!(BF && Length))
|
|
{
|
|
return(NULL);
|
|
}
|
|
|
|
/*
|
|
* not enough bytes left
|
|
*/
|
|
|
|
if(Length > BF->BytesLeft)
|
|
{
|
|
return(NULL);
|
|
}
|
|
|
|
/*
|
|
* the pointer to the requested data
|
|
*/
|
|
|
|
RetVal = BF->Ptr;
|
|
|
|
/*
|
|
* Raise the pointer and counter.
|
|
*/
|
|
|
|
BF->Ptr += Length;
|
|
BF->BytesLeft -= Length;
|
|
|
|
return(RetVal);
|
|
}
|
|
|
|
/*
|
|
* Rewind the buffer.
|
|
*/
|
|
|
|
static qboolean BufferedFileRewind(struct BufferedFile *BF, int Offset)
|
|
{
|
|
int BytesRead;
|
|
|
|
/*
|
|
* input verification
|
|
*/
|
|
|
|
if(!BF)
|
|
{
|
|
return(qfalse);
|
|
}
|
|
|
|
/*
|
|
* special trick to rewind to the beginning of the buffer
|
|
*/
|
|
|
|
if(Offset == -1)
|
|
{
|
|
BF->Ptr = BF->Buffer;
|
|
BF->BytesLeft = BF->Length;
|
|
|
|
return(qtrue);
|
|
}
|
|
|
|
/*
|
|
* How many bytes do we have already read?
|
|
*/
|
|
|
|
BytesRead = BF->Ptr - BF->Buffer;
|
|
|
|
/*
|
|
* We can only rewind to the beginning of the BufferedFile.
|
|
*/
|
|
|
|
if(Offset > BytesRead)
|
|
{
|
|
return(qfalse);
|
|
}
|
|
|
|
/*
|
|
* lower the pointer and counter.
|
|
*/
|
|
|
|
BF->Ptr -= Offset;
|
|
BF->BytesLeft += Offset;
|
|
|
|
return(qtrue);
|
|
}
|
|
|
|
/*
|
|
* Skip some bytes.
|
|
*/
|
|
|
|
static qboolean BufferedFileSkip(struct BufferedFile *BF, int Offset)
|
|
{
|
|
/*
|
|
* input verification
|
|
*/
|
|
|
|
if(!BF)
|
|
{
|
|
return(qfalse);
|
|
}
|
|
|
|
/*
|
|
* We can only skip to the end of the BufferedFile.
|
|
*/
|
|
|
|
if(Offset > BF->BytesLeft)
|
|
{
|
|
return(qfalse);
|
|
}
|
|
|
|
/*
|
|
* lower the pointer and counter.
|
|
*/
|
|
|
|
BF->Ptr += Offset;
|
|
BF->BytesLeft -= Offset;
|
|
|
|
return(qtrue);
|
|
}
|
|
|
|
/*
|
|
* Find a chunk
|
|
*/
|
|
|
|
static qboolean FindChunk(struct BufferedFile *BF, uint32_t ChunkType)
|
|
{
|
|
struct PNG_ChunkHeader *CH;
|
|
|
|
uint32_t Length;
|
|
uint32_t Type;
|
|
|
|
/*
|
|
* input verification
|
|
*/
|
|
|
|
if(!BF)
|
|
{
|
|
return(qfalse);
|
|
}
|
|
|
|
/*
|
|
* cycle trough the chunks
|
|
*/
|
|
|
|
while(qtrue)
|
|
{
|
|
/*
|
|
* Read the chunk-header.
|
|
*/
|
|
|
|
CH = BufferedFileRead(BF, PNG_ChunkHeader_Size);
|
|
if(!CH)
|
|
{
|
|
return(qfalse);
|
|
}
|
|
|
|
/*
|
|
* Do not swap the original types
|
|
* they might be needed later.
|
|
*/
|
|
|
|
Length = BigLong(CH->Length);
|
|
Type = BigLong(CH->Type);
|
|
|
|
/*
|
|
* We found it!
|
|
*/
|
|
|
|
if(Type == ChunkType)
|
|
{
|
|
/*
|
|
* Rewind to the start of the chunk.
|
|
*/
|
|
|
|
BufferedFileRewind(BF, PNG_ChunkHeader_Size);
|
|
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Skip the rest of the chunk.
|
|
*/
|
|
|
|
if(Length)
|
|
{
|
|
if(!BufferedFileSkip(BF, Length + PNG_ChunkCRC_Size))
|
|
{
|
|
return(qfalse);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return(qtrue);
|
|
}
|
|
|
|
/*
|
|
* Decompress all IDATs
|
|
*/
|
|
|
|
static uint32_t DecompressIDATs(struct BufferedFile *BF, uint8_t **Buffer)
|
|
{
|
|
uint8_t *DecompressedData;
|
|
uint32_t DecompressedDataLength;
|
|
|
|
uint8_t *CompressedData;
|
|
uint8_t *CompressedDataPtr;
|
|
uint32_t CompressedDataLength;
|
|
|
|
struct PNG_ChunkHeader *CH;
|
|
|
|
uint32_t Length;
|
|
uint32_t Type;
|
|
|
|
int BytesToRewind;
|
|
|
|
int32_t puffResult;
|
|
uint8_t *puffDest;
|
|
uint32_t puffDestLen;
|
|
uint8_t *puffSrc;
|
|
uint32_t puffSrcLen;
|
|
|
|
/*
|
|
* input verification
|
|
*/
|
|
|
|
if(!(BF && Buffer))
|
|
{
|
|
return(-1);
|
|
}
|
|
|
|
/*
|
|
* some zeroing
|
|
*/
|
|
|
|
DecompressedData = NULL;
|
|
DecompressedDataLength = 0;
|
|
*Buffer = DecompressedData;
|
|
|
|
CompressedData = NULL;
|
|
CompressedDataLength = 0;
|
|
|
|
BytesToRewind = 0;
|
|
|
|
/*
|
|
* Find the first IDAT chunk.
|
|
*/
|
|
|
|
if(!FindChunk(BF, PNG_ChunkType_IDAT))
|
|
{
|
|
return(-1);
|
|
}
|
|
|
|
/*
|
|
* Count the size of the uncompressed data
|
|
*/
|
|
|
|
while(qtrue)
|
|
{
|
|
/*
|
|
* Read chunk header
|
|
*/
|
|
|
|
CH = BufferedFileRead(BF, PNG_ChunkHeader_Size);
|
|
if(!CH)
|
|
{
|
|
/*
|
|
* Rewind to the start of this adventure
|
|
* and return unsuccessfull
|
|
*/
|
|
|
|
BufferedFileRewind(BF, BytesToRewind);
|
|
|
|
return(-1);
|
|
}
|
|
|
|
/*
|
|
* Length and Type of chunk
|
|
*/
|
|
|
|
Length = BigLong(CH->Length);
|
|
Type = BigLong(CH->Type);
|
|
|
|
/*
|
|
* We have reached the end of the IDAT chunks
|
|
*/
|
|
|
|
if(!(Type == PNG_ChunkType_IDAT))
|
|
{
|
|
BufferedFileRewind(BF, PNG_ChunkHeader_Size);
|
|
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Add chunk header to count.
|
|
*/
|
|
|
|
BytesToRewind += PNG_ChunkHeader_Size;
|
|
|
|
/*
|
|
* Skip to next chunk
|
|
*/
|
|
|
|
if(Length)
|
|
{
|
|
if(!BufferedFileSkip(BF, Length + PNG_ChunkCRC_Size))
|
|
{
|
|
BufferedFileRewind(BF, BytesToRewind);
|
|
|
|
return(-1);
|
|
}
|
|
|
|
BytesToRewind += Length + PNG_ChunkCRC_Size;
|
|
CompressedDataLength += Length;
|
|
}
|
|
}
|
|
|
|
BufferedFileRewind(BF, BytesToRewind);
|
|
|
|
CompressedData = ri.Malloc(CompressedDataLength);
|
|
if(!CompressedData)
|
|
{
|
|
return(-1);
|
|
}
|
|
|
|
CompressedDataPtr = CompressedData;
|
|
|
|
/*
|
|
* Collect the compressed Data
|
|
*/
|
|
|
|
while(qtrue)
|
|
{
|
|
/*
|
|
* Read chunk header
|
|
*/
|
|
|
|
CH = BufferedFileRead(BF, PNG_ChunkHeader_Size);
|
|
if(!CH)
|
|
{
|
|
ri.Free(CompressedData);
|
|
|
|
return(-1);
|
|
}
|
|
|
|
/*
|
|
* Length and Type of chunk
|
|
*/
|
|
|
|
Length = BigLong(CH->Length);
|
|
Type = BigLong(CH->Type);
|
|
|
|
/*
|
|
* We have reached the end of the IDAT chunks
|
|
*/
|
|
|
|
if(!(Type == PNG_ChunkType_IDAT))
|
|
{
|
|
BufferedFileRewind(BF, PNG_ChunkHeader_Size);
|
|
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Copy the Data
|
|
*/
|
|
|
|
if(Length)
|
|
{
|
|
uint8_t *OrigCompressedData;
|
|
|
|
OrigCompressedData = BufferedFileRead(BF, Length);
|
|
if(!OrigCompressedData)
|
|
{
|
|
ri.Free(CompressedData);
|
|
|
|
return(-1);
|
|
}
|
|
|
|
if(!BufferedFileSkip(BF, PNG_ChunkCRC_Size))
|
|
{
|
|
ri.Free(CompressedData);
|
|
|
|
return(-1);
|
|
}
|
|
|
|
memcpy(CompressedDataPtr, OrigCompressedData, Length);
|
|
CompressedDataPtr += Length;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Let puff() calculate the decompressed data length.
|
|
*/
|
|
|
|
puffDest = NULL;
|
|
puffDestLen = 0;
|
|
|
|
/*
|
|
* The zlib header and checkvalue don't belong to the compressed data.
|
|
*/
|
|
|
|
puffSrc = CompressedData + PNG_ZlibHeader_Size;
|
|
puffSrcLen = CompressedDataLength - PNG_ZlibHeader_Size - PNG_ZlibCheckValue_Size;
|
|
|
|
/*
|
|
* first puff() to calculate the size of the uncompressed data
|
|
*/
|
|
|
|
puffResult = puff(puffDest, &puffDestLen, puffSrc, &puffSrcLen);
|
|
if(!((puffResult == 0) && (puffDestLen > 0)))
|
|
{
|
|
ri.Free(CompressedData);
|
|
|
|
return(-1);
|
|
}
|
|
|
|
/*
|
|
* Allocate the buffer for the uncompressed data.
|
|
*/
|
|
|
|
DecompressedData = ri.Malloc(puffDestLen);
|
|
if(!DecompressedData)
|
|
{
|
|
ri.Free(CompressedData);
|
|
|
|
return(-1);
|
|
}
|
|
|
|
/*
|
|
* Set the input again in case something was changed by the last puff() .
|
|
*/
|
|
|
|
puffDest = DecompressedData;
|
|
puffSrc = CompressedData + PNG_ZlibHeader_Size;
|
|
puffSrcLen = CompressedDataLength - PNG_ZlibHeader_Size - PNG_ZlibCheckValue_Size;
|
|
|
|
/*
|
|
* decompression puff()
|
|
*/
|
|
|
|
puffResult = puff(puffDest, &puffDestLen, puffSrc, &puffSrcLen);
|
|
|
|
/*
|
|
* The compressed data is not needed anymore.
|
|
*/
|
|
|
|
ri.Free(CompressedData);
|
|
|
|
/*
|
|
* Check if the last puff() was successfull.
|
|
*/
|
|
|
|
if(!((puffResult == 0) && (puffDestLen > 0)))
|
|
{
|
|
ri.Free(DecompressedData);
|
|
|
|
return(-1);
|
|
}
|
|
|
|
/*
|
|
* Set the output of this function.
|
|
*/
|
|
|
|
DecompressedDataLength = puffDestLen;
|
|
*Buffer = DecompressedData;
|
|
|
|
return(DecompressedDataLength);
|
|
}
|
|
|
|
/*
|
|
* the Paeth predictor
|
|
*/
|
|
|
|
static uint8_t PredictPaeth(uint8_t a, uint8_t b, uint8_t c)
|
|
{
|
|
/*
|
|
* a == Left
|
|
* b == Up
|
|
* c == UpLeft
|
|
*/
|
|
|
|
uint8_t Pr;
|
|
int p;
|
|
int pa, pb, pc;
|
|
|
|
Pr = 0;
|
|
|
|
p = ((int) a) + ((int) b) - ((int) c);
|
|
pa = abs(p - ((int) a));
|
|
pb = abs(p - ((int) b));
|
|
pc = abs(p - ((int) c));
|
|
|
|
if((pa <= pb) && (pa <= pc))
|
|
{
|
|
Pr = a;
|
|
}
|
|
else if(pb <= pc)
|
|
{
|
|
Pr = b;
|
|
}
|
|
else
|
|
{
|
|
Pr = c;
|
|
}
|
|
|
|
return(Pr);
|
|
|
|
}
|
|
|
|
/*
|
|
* Reverse the filters.
|
|
*/
|
|
|
|
static qboolean UnfilterImage(uint8_t *DecompressedData,
|
|
uint32_t ImageHeight,
|
|
uint32_t BytesPerScanline,
|
|
uint32_t BytesPerPixel)
|
|
{
|
|
uint8_t *DecompPtr;
|
|
uint8_t FilterType;
|
|
uint8_t *PixelLeft, *PixelUp, *PixelUpLeft;
|
|
uint32_t w, h, p;
|
|
|
|
/*
|
|
* some zeros for the filters
|
|
*/
|
|
|
|
uint8_t Zeros[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
|
|
|
/*
|
|
* input verification
|
|
*
|
|
* ImageHeight and BytesPerScanline are not checked,
|
|
* because these can be zero in some interlace passes.
|
|
*/
|
|
|
|
if(!(DecompressedData && BytesPerPixel))
|
|
{
|
|
return(qfalse);
|
|
}
|
|
|
|
|
|
/*
|
|
* Set the pointer to the start of the decompressed Data.
|
|
*/
|
|
|
|
DecompPtr = DecompressedData;
|
|
|
|
/*
|
|
* Un-filtering is done in place.
|
|
*/
|
|
|
|
/*
|
|
* Go trough all scanlines.
|
|
*/
|
|
|
|
for(h = 0; h < ImageHeight; h++)
|
|
{
|
|
/*
|
|
* Every scanline starts with a FilterType byte.
|
|
*/
|
|
|
|
FilterType = *DecompPtr;
|
|
DecompPtr++;
|
|
|
|
/*
|
|
* Left pixel of the first byte in a scanline is zero.
|
|
*/
|
|
|
|
PixelLeft = Zeros;
|
|
|
|
/*
|
|
* Set PixelUp to previous line only if we are on the second line or above.
|
|
*
|
|
* Plus one byte for the FilterType
|
|
*/
|
|
|
|
if(h > 0)
|
|
{
|
|
PixelUp = DecompPtr - (BytesPerScanline + 1);
|
|
}
|
|
else
|
|
{
|
|
PixelUp = Zeros;
|
|
}
|
|
|
|
/*
|
|
* The pixel left to the first pixel of the previous scanline is zero too.
|
|
*/
|
|
|
|
PixelUpLeft = Zeros;
|
|
|
|
/*
|
|
* Cycle trough all pixels of the scanline.
|
|
*/
|
|
|
|
for(w = 0; w < (BytesPerScanline / BytesPerPixel); w++)
|
|
{
|
|
/*
|
|
* Cycle trough the bytes of the pixel.
|
|
*/
|
|
|
|
for(p = 0; p < BytesPerPixel; p++)
|
|
{
|
|
switch(FilterType)
|
|
{
|
|
case PNG_FilterType_None :
|
|
{
|
|
/*
|
|
* The byte is unfiltered.
|
|
*/
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_FilterType_Sub :
|
|
{
|
|
DecompPtr[p] += PixelLeft[p];
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_FilterType_Up :
|
|
{
|
|
DecompPtr[p] += PixelUp[p];
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_FilterType_Average :
|
|
{
|
|
DecompPtr[p] += ((uint8_t) ((((uint16_t) PixelLeft[p]) + ((uint16_t) PixelUp[p])) / 2));
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_FilterType_Paeth :
|
|
{
|
|
DecompPtr[p] += PredictPaeth(PixelLeft[p], PixelUp[p], PixelUpLeft[p]);
|
|
|
|
break;
|
|
}
|
|
|
|
default :
|
|
{
|
|
return(qfalse);
|
|
}
|
|
}
|
|
}
|
|
|
|
PixelLeft = DecompPtr;
|
|
|
|
/*
|
|
* We only have a upleft pixel if we are on the second line or above.
|
|
*/
|
|
|
|
if(h > 0)
|
|
{
|
|
PixelUpLeft = DecompPtr - (BytesPerScanline + 1);
|
|
}
|
|
|
|
/*
|
|
* Skip to the next pixel.
|
|
*/
|
|
|
|
DecompPtr += BytesPerPixel;
|
|
|
|
/*
|
|
* We only have a previous line if we are on the second line and above.
|
|
*/
|
|
|
|
if(h > 0)
|
|
{
|
|
PixelUp = DecompPtr - (BytesPerScanline + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
return(qtrue);
|
|
}
|
|
|
|
/*
|
|
* Convert a raw input pixel to Quake 3 RGA format.
|
|
*/
|
|
|
|
static qboolean ConvertPixel(struct PNG_Chunk_IHDR *IHDR,
|
|
byte *OutPtr,
|
|
uint8_t *DecompPtr,
|
|
qboolean HasTransparentColour,
|
|
uint8_t *TransparentColour,
|
|
uint8_t *OutPal)
|
|
{
|
|
/*
|
|
* input verification
|
|
*/
|
|
|
|
if(!(IHDR && OutPtr && DecompPtr && TransparentColour && OutPal))
|
|
{
|
|
return(qfalse);
|
|
}
|
|
|
|
switch(IHDR->ColourType)
|
|
{
|
|
case PNG_ColourType_Grey :
|
|
{
|
|
switch(IHDR->BitDepth)
|
|
{
|
|
case PNG_BitDepth_1 :
|
|
case PNG_BitDepth_2 :
|
|
case PNG_BitDepth_4 :
|
|
{
|
|
uint8_t Step;
|
|
uint8_t GreyValue;
|
|
|
|
Step = 0xFF / ((1 << IHDR->BitDepth) - 1);
|
|
|
|
GreyValue = DecompPtr[0] * Step;
|
|
|
|
OutPtr[0] = GreyValue;
|
|
OutPtr[1] = GreyValue;
|
|
OutPtr[2] = GreyValue;
|
|
OutPtr[3] = 0xFF;
|
|
|
|
/*
|
|
* Grey supports full transparency for one specified colour
|
|
*/
|
|
|
|
if(HasTransparentColour)
|
|
{
|
|
if(TransparentColour[1] == DecompPtr[0])
|
|
{
|
|
OutPtr[3] = 0x00;
|
|
}
|
|
}
|
|
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_BitDepth_8 :
|
|
case PNG_BitDepth_16 :
|
|
{
|
|
OutPtr[0] = DecompPtr[0];
|
|
OutPtr[1] = DecompPtr[0];
|
|
OutPtr[2] = DecompPtr[0];
|
|
OutPtr[3] = 0xFF;
|
|
|
|
/*
|
|
* Grey supports full transparency for one specified colour
|
|
*/
|
|
|
|
if(HasTransparentColour)
|
|
{
|
|
if(IHDR->BitDepth == PNG_BitDepth_8)
|
|
{
|
|
if(TransparentColour[1] == DecompPtr[0])
|
|
{
|
|
OutPtr[3] = 0x00;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if((TransparentColour[0] == DecompPtr[0]) && (TransparentColour[1] == DecompPtr[1]))
|
|
{
|
|
OutPtr[3] = 0x00;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default :
|
|
{
|
|
return(qfalse);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_ColourType_True :
|
|
{
|
|
switch(IHDR->BitDepth)
|
|
{
|
|
case PNG_BitDepth_8 :
|
|
{
|
|
OutPtr[0] = DecompPtr[0];
|
|
OutPtr[1] = DecompPtr[1];
|
|
OutPtr[2] = DecompPtr[2];
|
|
OutPtr[3] = 0xFF;
|
|
|
|
/*
|
|
* True supports full transparency for one specified colour
|
|
*/
|
|
|
|
if(HasTransparentColour)
|
|
{
|
|
if((TransparentColour[1] == DecompPtr[0]) &&
|
|
(TransparentColour[3] == DecompPtr[1]) &&
|
|
(TransparentColour[5] == DecompPtr[3]))
|
|
{
|
|
OutPtr[3] = 0x00;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_BitDepth_16 :
|
|
{
|
|
/*
|
|
* We use only the upper byte.
|
|
*/
|
|
|
|
OutPtr[0] = DecompPtr[0];
|
|
OutPtr[1] = DecompPtr[2];
|
|
OutPtr[2] = DecompPtr[4];
|
|
OutPtr[3] = 0xFF;
|
|
|
|
/*
|
|
* True supports full transparency for one specified colour
|
|
*/
|
|
|
|
if(HasTransparentColour)
|
|
{
|
|
if((TransparentColour[0] == DecompPtr[0]) && (TransparentColour[1] == DecompPtr[1]) &&
|
|
(TransparentColour[2] == DecompPtr[2]) && (TransparentColour[3] == DecompPtr[3]) &&
|
|
(TransparentColour[4] == DecompPtr[4]) && (TransparentColour[5] == DecompPtr[5]))
|
|
{
|
|
OutPtr[3] = 0x00;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default :
|
|
{
|
|
return(qfalse);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_ColourType_Indexed :
|
|
{
|
|
OutPtr[0] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 0];
|
|
OutPtr[1] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 1];
|
|
OutPtr[2] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 2];
|
|
OutPtr[3] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 3];
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_ColourType_GreyAlpha :
|
|
{
|
|
switch(IHDR->BitDepth)
|
|
{
|
|
case PNG_BitDepth_8 :
|
|
{
|
|
OutPtr[0] = DecompPtr[0];
|
|
OutPtr[1] = DecompPtr[0];
|
|
OutPtr[2] = DecompPtr[0];
|
|
OutPtr[3] = DecompPtr[1];
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_BitDepth_16 :
|
|
{
|
|
/*
|
|
* We use only the upper byte.
|
|
*/
|
|
|
|
OutPtr[0] = DecompPtr[0];
|
|
OutPtr[1] = DecompPtr[0];
|
|
OutPtr[2] = DecompPtr[0];
|
|
OutPtr[3] = DecompPtr[2];
|
|
|
|
break;
|
|
}
|
|
|
|
default :
|
|
{
|
|
return(qfalse);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_ColourType_TrueAlpha :
|
|
{
|
|
switch(IHDR->BitDepth)
|
|
{
|
|
case PNG_BitDepth_8 :
|
|
{
|
|
OutPtr[0] = DecompPtr[0];
|
|
OutPtr[1] = DecompPtr[1];
|
|
OutPtr[2] = DecompPtr[2];
|
|
OutPtr[3] = DecompPtr[3];
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_BitDepth_16 :
|
|
{
|
|
/*
|
|
* We use only the upper byte.
|
|
*/
|
|
|
|
OutPtr[0] = DecompPtr[0];
|
|
OutPtr[1] = DecompPtr[2];
|
|
OutPtr[2] = DecompPtr[4];
|
|
OutPtr[3] = DecompPtr[6];
|
|
|
|
break;
|
|
}
|
|
|
|
default :
|
|
{
|
|
return(qfalse);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default :
|
|
{
|
|
return(qfalse);
|
|
}
|
|
}
|
|
|
|
return(qtrue);
|
|
}
|
|
|
|
|
|
/*
|
|
* Decode a non-interlaced image.
|
|
*/
|
|
|
|
static qboolean DecodeImageNonInterlaced(struct PNG_Chunk_IHDR *IHDR,
|
|
byte *OutBuffer,
|
|
uint8_t *DecompressedData,
|
|
uint32_t DecompressedDataLength,
|
|
qboolean HasTransparentColour,
|
|
uint8_t *TransparentColour,
|
|
uint8_t *OutPal)
|
|
{
|
|
uint32_t IHDR_Width;
|
|
uint32_t IHDR_Height;
|
|
uint32_t BytesPerScanline, BytesPerPixel, PixelsPerByte;
|
|
uint32_t w, h, p;
|
|
byte *OutPtr;
|
|
uint8_t *DecompPtr;
|
|
|
|
/*
|
|
* input verification
|
|
*/
|
|
|
|
if(!(IHDR && OutBuffer && DecompressedData && DecompressedDataLength && TransparentColour && OutPal))
|
|
{
|
|
return(qfalse);
|
|
}
|
|
|
|
/*
|
|
* byte swapping
|
|
*/
|
|
|
|
IHDR_Width = BigLong(IHDR->Width);
|
|
IHDR_Height = BigLong(IHDR->Height);
|
|
|
|
/*
|
|
* information for un-filtering
|
|
*/
|
|
|
|
switch(IHDR->ColourType)
|
|
{
|
|
case PNG_ColourType_Grey :
|
|
{
|
|
switch(IHDR->BitDepth)
|
|
{
|
|
case PNG_BitDepth_1 :
|
|
case PNG_BitDepth_2 :
|
|
case PNG_BitDepth_4 :
|
|
{
|
|
BytesPerPixel = 1;
|
|
PixelsPerByte = 8 / IHDR->BitDepth;
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_BitDepth_8 :
|
|
case PNG_BitDepth_16 :
|
|
{
|
|
BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_Grey;
|
|
PixelsPerByte = 1;
|
|
|
|
break;
|
|
}
|
|
|
|
default :
|
|
{
|
|
return(qfalse);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_ColourType_True :
|
|
{
|
|
switch(IHDR->BitDepth)
|
|
{
|
|
case PNG_BitDepth_8 :
|
|
case PNG_BitDepth_16 :
|
|
{
|
|
BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_True;
|
|
PixelsPerByte = 1;
|
|
|
|
break;
|
|
}
|
|
|
|
default :
|
|
{
|
|
return(qfalse);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_ColourType_Indexed :
|
|
{
|
|
switch(IHDR->BitDepth)
|
|
{
|
|
case PNG_BitDepth_1 :
|
|
case PNG_BitDepth_2 :
|
|
case PNG_BitDepth_4 :
|
|
{
|
|
BytesPerPixel = 1;
|
|
PixelsPerByte = 8 / IHDR->BitDepth;
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_BitDepth_8 :
|
|
{
|
|
BytesPerPixel = PNG_NumColourComponents_Indexed;
|
|
PixelsPerByte = 1;
|
|
|
|
break;
|
|
}
|
|
|
|
default :
|
|
{
|
|
return(qfalse);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_ColourType_GreyAlpha :
|
|
{
|
|
switch(IHDR->BitDepth)
|
|
{
|
|
case PNG_BitDepth_8 :
|
|
case PNG_BitDepth_16 :
|
|
{
|
|
BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_GreyAlpha;
|
|
PixelsPerByte = 1;
|
|
|
|
break;
|
|
}
|
|
|
|
default :
|
|
{
|
|
return(qfalse);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_ColourType_TrueAlpha :
|
|
{
|
|
switch(IHDR->BitDepth)
|
|
{
|
|
case PNG_BitDepth_8 :
|
|
case PNG_BitDepth_16 :
|
|
{
|
|
BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_TrueAlpha;
|
|
PixelsPerByte = 1;
|
|
|
|
break;
|
|
}
|
|
|
|
default :
|
|
{
|
|
return(qfalse);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default :
|
|
{
|
|
return(qfalse);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Calculate the size of one scanline
|
|
*/
|
|
|
|
BytesPerScanline = (IHDR_Width * BytesPerPixel + (PixelsPerByte - 1)) / PixelsPerByte;
|
|
|
|
/*
|
|
* Check if we have enough data for the whole image.
|
|
*/
|
|
|
|
if(!(DecompressedDataLength == ((BytesPerScanline + 1) * IHDR_Height)))
|
|
{
|
|
return(qfalse);
|
|
}
|
|
|
|
/*
|
|
* Unfilter the image.
|
|
*/
|
|
|
|
if(!UnfilterImage(DecompressedData, IHDR_Height, BytesPerScanline, BytesPerPixel))
|
|
{
|
|
return(qfalse);
|
|
}
|
|
|
|
/*
|
|
* Set the working pointers to the beginning of the buffers.
|
|
*/
|
|
|
|
OutPtr = OutBuffer;
|
|
DecompPtr = DecompressedData;
|
|
|
|
/*
|
|
* Create the output image.
|
|
*/
|
|
|
|
for(h = 0; h < IHDR_Height; h++)
|
|
{
|
|
/*
|
|
* Count the pixels on the scanline for those multipixel bytes
|
|
*/
|
|
|
|
uint32_t CurrPixel;
|
|
|
|
/*
|
|
* skip FilterType
|
|
*/
|
|
|
|
DecompPtr++;
|
|
|
|
/*
|
|
* Reset the pixel count.
|
|
*/
|
|
|
|
CurrPixel = 0;
|
|
|
|
for(w = 0; w < (BytesPerScanline / BytesPerPixel); w++)
|
|
{
|
|
if(PixelsPerByte > 1)
|
|
{
|
|
uint8_t Mask;
|
|
uint32_t Shift;
|
|
uint8_t SinglePixel;
|
|
|
|
for(p = 0; p < PixelsPerByte; p++)
|
|
{
|
|
if(CurrPixel < IHDR_Width)
|
|
{
|
|
Mask = (1 << IHDR->BitDepth) - 1;
|
|
Shift = (PixelsPerByte - 1 - p) * IHDR->BitDepth;
|
|
|
|
SinglePixel = ((DecompPtr[0] & (Mask << Shift)) >> Shift);
|
|
|
|
if(!ConvertPixel(IHDR, OutPtr, &SinglePixel, HasTransparentColour, TransparentColour, OutPal))
|
|
{
|
|
return(qfalse);
|
|
}
|
|
|
|
OutPtr += Q3IMAGE_BYTESPERPIXEL;
|
|
CurrPixel++;
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
if(!ConvertPixel(IHDR, OutPtr, DecompPtr, HasTransparentColour, TransparentColour, OutPal))
|
|
{
|
|
return(qfalse);
|
|
}
|
|
|
|
|
|
OutPtr += Q3IMAGE_BYTESPERPIXEL;
|
|
}
|
|
|
|
DecompPtr += BytesPerPixel;
|
|
}
|
|
}
|
|
|
|
return(qtrue);
|
|
}
|
|
|
|
/*
|
|
* Decode an interlaced image.
|
|
*/
|
|
|
|
static qboolean DecodeImageInterlaced(struct PNG_Chunk_IHDR *IHDR,
|
|
byte *OutBuffer,
|
|
uint8_t *DecompressedData,
|
|
uint32_t DecompressedDataLength,
|
|
qboolean HasTransparentColour,
|
|
uint8_t *TransparentColour,
|
|
uint8_t *OutPal)
|
|
{
|
|
uint32_t IHDR_Width;
|
|
uint32_t IHDR_Height;
|
|
uint32_t BytesPerScanline[PNG_Adam7_NumPasses], BytesPerPixel, PixelsPerByte;
|
|
uint32_t PassWidth[PNG_Adam7_NumPasses], PassHeight[PNG_Adam7_NumPasses];
|
|
uint32_t WSkip[PNG_Adam7_NumPasses], WOffset[PNG_Adam7_NumPasses], HSkip[PNG_Adam7_NumPasses], HOffset[PNG_Adam7_NumPasses];
|
|
uint32_t w, h, p, a;
|
|
byte *OutPtr;
|
|
uint8_t *DecompPtr;
|
|
uint32_t TargetLength;
|
|
|
|
/*
|
|
* input verification
|
|
*/
|
|
|
|
if(!(IHDR && OutBuffer && DecompressedData && DecompressedDataLength && TransparentColour && OutPal))
|
|
{
|
|
return(qfalse);
|
|
}
|
|
|
|
/*
|
|
* byte swapping
|
|
*/
|
|
|
|
IHDR_Width = BigLong(IHDR->Width);
|
|
IHDR_Height = BigLong(IHDR->Height);
|
|
|
|
/*
|
|
* Skip and Offset for the passes.
|
|
*/
|
|
|
|
WSkip[0] = 8;
|
|
WOffset[0] = 0;
|
|
HSkip[0] = 8;
|
|
HOffset[0] = 0;
|
|
|
|
WSkip[1] = 8;
|
|
WOffset[1] = 4;
|
|
HSkip[1] = 8;
|
|
HOffset[1] = 0;
|
|
|
|
WSkip[2] = 4;
|
|
WOffset[2] = 0;
|
|
HSkip[2] = 8;
|
|
HOffset[2] = 4;
|
|
|
|
WSkip[3] = 4;
|
|
WOffset[3] = 2;
|
|
HSkip[3] = 4;
|
|
HOffset[3] = 0;
|
|
|
|
WSkip[4] = 2;
|
|
WOffset[4] = 0;
|
|
HSkip[4] = 4;
|
|
HOffset[4] = 2;
|
|
|
|
WSkip[5] = 2;
|
|
WOffset[5] = 1;
|
|
HSkip[5] = 2;
|
|
HOffset[5] = 0;
|
|
|
|
WSkip[6] = 1;
|
|
WOffset[6] = 0;
|
|
HSkip[6] = 2;
|
|
HOffset[6] = 1;
|
|
|
|
/*
|
|
* Calculate the sizes of the passes.
|
|
*/
|
|
|
|
PassWidth[0] = (IHDR_Width + 7) / 8;
|
|
PassHeight[0] = (IHDR_Height + 7) / 8;
|
|
|
|
PassWidth[1] = (IHDR_Width + 3) / 8;
|
|
PassHeight[1] = (IHDR_Height + 7) / 8;
|
|
|
|
PassWidth[2] = (IHDR_Width + 3) / 4;
|
|
PassHeight[2] = (IHDR_Height + 3) / 8;
|
|
|
|
PassWidth[3] = (IHDR_Width + 1) / 4;
|
|
PassHeight[3] = (IHDR_Height + 3) / 4;
|
|
|
|
PassWidth[4] = (IHDR_Width + 1) / 2;
|
|
PassHeight[4] = (IHDR_Height + 1) / 4;
|
|
|
|
PassWidth[5] = (IHDR_Width + 0) / 2;
|
|
PassHeight[5] = (IHDR_Height + 1) / 2;
|
|
|
|
PassWidth[6] = (IHDR_Width + 0) / 1;
|
|
PassHeight[6] = (IHDR_Height + 0) / 2;
|
|
|
|
/*
|
|
* information for un-filtering
|
|
*/
|
|
|
|
switch(IHDR->ColourType)
|
|
{
|
|
case PNG_ColourType_Grey :
|
|
{
|
|
switch(IHDR->BitDepth)
|
|
{
|
|
case PNG_BitDepth_1 :
|
|
case PNG_BitDepth_2 :
|
|
case PNG_BitDepth_4 :
|
|
{
|
|
BytesPerPixel = 1;
|
|
PixelsPerByte = 8 / IHDR->BitDepth;
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_BitDepth_8 :
|
|
case PNG_BitDepth_16 :
|
|
{
|
|
BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_Grey;
|
|
PixelsPerByte = 1;
|
|
|
|
break;
|
|
}
|
|
|
|
default :
|
|
{
|
|
return(qfalse);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_ColourType_True :
|
|
{
|
|
switch(IHDR->BitDepth)
|
|
{
|
|
case PNG_BitDepth_8 :
|
|
case PNG_BitDepth_16 :
|
|
{
|
|
BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_True;
|
|
PixelsPerByte = 1;
|
|
|
|
break;
|
|
}
|
|
|
|
default :
|
|
{
|
|
return(qfalse);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_ColourType_Indexed :
|
|
{
|
|
switch(IHDR->BitDepth)
|
|
{
|
|
case PNG_BitDepth_1 :
|
|
case PNG_BitDepth_2 :
|
|
case PNG_BitDepth_4 :
|
|
{
|
|
BytesPerPixel = 1;
|
|
PixelsPerByte = 8 / IHDR->BitDepth;
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_BitDepth_8 :
|
|
{
|
|
BytesPerPixel = PNG_NumColourComponents_Indexed;
|
|
PixelsPerByte = 1;
|
|
|
|
break;
|
|
}
|
|
|
|
default :
|
|
{
|
|
return(qfalse);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_ColourType_GreyAlpha :
|
|
{
|
|
switch(IHDR->BitDepth)
|
|
{
|
|
case PNG_BitDepth_8 :
|
|
case PNG_BitDepth_16 :
|
|
{
|
|
BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_GreyAlpha;
|
|
PixelsPerByte = 1;
|
|
|
|
break;
|
|
}
|
|
|
|
default :
|
|
{
|
|
return(qfalse);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_ColourType_TrueAlpha :
|
|
{
|
|
switch(IHDR->BitDepth)
|
|
{
|
|
case PNG_BitDepth_8 :
|
|
case PNG_BitDepth_16 :
|
|
{
|
|
BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_TrueAlpha;
|
|
PixelsPerByte = 1;
|
|
|
|
break;
|
|
}
|
|
|
|
default :
|
|
{
|
|
return(qfalse);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default :
|
|
{
|
|
return(qfalse);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Calculate the size of the scanlines per pass
|
|
*/
|
|
|
|
for(a = 0; a < PNG_Adam7_NumPasses; a++)
|
|
{
|
|
BytesPerScanline[a] = (PassWidth[a] * BytesPerPixel + (PixelsPerByte - 1)) / PixelsPerByte;
|
|
}
|
|
|
|
/*
|
|
* Calculate the size of all passes
|
|
*/
|
|
|
|
TargetLength = 0;
|
|
|
|
for(a = 0; a < PNG_Adam7_NumPasses; a++)
|
|
{
|
|
TargetLength += ((BytesPerScanline[a] + (BytesPerScanline[a] ? 1 : 0)) * PassHeight[a]);
|
|
}
|
|
|
|
/*
|
|
* Check if we have enough data for the whole image.
|
|
*/
|
|
|
|
if(!(DecompressedDataLength == TargetLength))
|
|
{
|
|
return(qfalse);
|
|
}
|
|
|
|
/*
|
|
* Unfilter the image.
|
|
*/
|
|
|
|
DecompPtr = DecompressedData;
|
|
|
|
for(a = 0; a < PNG_Adam7_NumPasses; a++)
|
|
{
|
|
if(!UnfilterImage(DecompPtr, PassHeight[a], BytesPerScanline[a], BytesPerPixel))
|
|
{
|
|
return(qfalse);
|
|
}
|
|
|
|
DecompPtr += ((BytesPerScanline[a] + (BytesPerScanline[a] ? 1 : 0)) * PassHeight[a]);
|
|
}
|
|
|
|
/*
|
|
* Set the working pointers to the beginning of the buffers.
|
|
*/
|
|
|
|
DecompPtr = DecompressedData;
|
|
|
|
/*
|
|
* Create the output image.
|
|
*/
|
|
|
|
for(a = 0; a < PNG_Adam7_NumPasses; a++)
|
|
{
|
|
for(h = 0; h < PassHeight[a]; h++)
|
|
{
|
|
/*
|
|
* Count the pixels on the scanline for those multipixel bytes
|
|
*/
|
|
|
|
uint32_t CurrPixel;
|
|
|
|
/*
|
|
* skip FilterType
|
|
*/
|
|
|
|
DecompPtr++;
|
|
|
|
/*
|
|
* Reset the pixel count.
|
|
*/
|
|
|
|
CurrPixel = 0;
|
|
|
|
for(w = 0; w < (BytesPerScanline[a] / BytesPerPixel); w++)
|
|
{
|
|
if(PixelsPerByte > 1)
|
|
{
|
|
uint8_t Mask;
|
|
uint32_t Shift;
|
|
uint8_t SinglePixel;
|
|
|
|
for(p = 0; p < PixelsPerByte; p++)
|
|
{
|
|
if(CurrPixel < PassWidth[a])
|
|
{
|
|
Mask = (1 << IHDR->BitDepth) - 1;
|
|
Shift = (PixelsPerByte - 1 - p) * IHDR->BitDepth;
|
|
|
|
SinglePixel = ((DecompPtr[0] & (Mask << Shift)) >> Shift);
|
|
|
|
OutPtr = OutBuffer + (((((h * HSkip[a]) + HOffset[a]) * IHDR_Width) + ((CurrPixel * WSkip[a]) + WOffset[a])) * Q3IMAGE_BYTESPERPIXEL);
|
|
|
|
if(!ConvertPixel(IHDR, OutPtr, &SinglePixel, HasTransparentColour, TransparentColour, OutPal))
|
|
{
|
|
return(qfalse);
|
|
}
|
|
|
|
CurrPixel++;
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
OutPtr = OutBuffer + (((((h * HSkip[a]) + HOffset[a]) * IHDR_Width) + ((w * WSkip[a]) + WOffset[a])) * Q3IMAGE_BYTESPERPIXEL);
|
|
|
|
if(!ConvertPixel(IHDR, OutPtr, DecompPtr, HasTransparentColour, TransparentColour, OutPal))
|
|
{
|
|
return(qfalse);
|
|
}
|
|
}
|
|
|
|
DecompPtr += BytesPerPixel;
|
|
}
|
|
}
|
|
}
|
|
|
|
return(qtrue);
|
|
}
|
|
|
|
/*
|
|
* The PNG loader
|
|
*/
|
|
|
|
static void LoadPNG(const char *name, byte **pic, int *width, int *height)
|
|
{
|
|
struct BufferedFile *ThePNG;
|
|
byte *OutBuffer;
|
|
uint8_t *Signature;
|
|
struct PNG_ChunkHeader *CH;
|
|
uint32_t ChunkHeaderLength;
|
|
uint32_t ChunkHeaderType;
|
|
struct PNG_Chunk_IHDR *IHDR;
|
|
uint32_t IHDR_Width;
|
|
uint32_t IHDR_Height;
|
|
PNG_ChunkCRC *CRC;
|
|
uint8_t *InPal;
|
|
uint8_t *DecompressedData;
|
|
uint32_t DecompressedDataLength;
|
|
uint32_t i;
|
|
|
|
/*
|
|
* palette with 256 RGBA entries
|
|
*/
|
|
|
|
uint8_t OutPal[1024];
|
|
|
|
/*
|
|
* transparent colour from the tRNS chunk
|
|
*/
|
|
|
|
qboolean HasTransparentColour = qfalse;
|
|
uint8_t TransparentColour[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
|
|
|
/*
|
|
* input verification
|
|
*/
|
|
|
|
if(!(name && pic))
|
|
{
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Zero out return values.
|
|
*/
|
|
|
|
*pic = NULL;
|
|
|
|
if(width)
|
|
{
|
|
*width = 0;
|
|
}
|
|
|
|
if(height)
|
|
{
|
|
*height = 0;
|
|
}
|
|
|
|
/*
|
|
* Read the file.
|
|
*/
|
|
|
|
ThePNG = ReadBufferedFile(name);
|
|
if(!ThePNG)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Read the siganture of the file.
|
|
*/
|
|
|
|
Signature = BufferedFileRead(ThePNG, PNG_Signature_Size);
|
|
if(!Signature)
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Is it a PNG?
|
|
*/
|
|
|
|
if(memcmp(Signature, PNG_Signature, PNG_Signature_Size))
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Read the first chunk-header.
|
|
*/
|
|
|
|
CH = BufferedFileRead(ThePNG, PNG_ChunkHeader_Size);
|
|
if(!CH)
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* PNG multi-byte types are in Big Endian
|
|
*/
|
|
|
|
ChunkHeaderLength = BigLong(CH->Length);
|
|
ChunkHeaderType = BigLong(CH->Type);
|
|
|
|
/*
|
|
* Check if the first chunk is an IHDR.
|
|
*/
|
|
|
|
if(!((ChunkHeaderType == PNG_ChunkType_IHDR) && (ChunkHeaderLength == PNG_Chunk_IHDR_Size)))
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Read the IHDR.
|
|
*/
|
|
|
|
IHDR = BufferedFileRead(ThePNG, PNG_Chunk_IHDR_Size);
|
|
if(!IHDR)
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Read the CRC for IHDR
|
|
*/
|
|
|
|
CRC = BufferedFileRead(ThePNG, PNG_ChunkCRC_Size);
|
|
if(!CRC)
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Here we could check the CRC if we wanted to.
|
|
*/
|
|
|
|
/*
|
|
* multi-byte type swapping
|
|
*/
|
|
|
|
IHDR_Width = BigLong(IHDR->Width);
|
|
IHDR_Height = BigLong(IHDR->Height);
|
|
|
|
/*
|
|
* Check if Width and Height are valid.
|
|
*/
|
|
|
|
if(!((IHDR_Width > 0) && (IHDR_Height > 0)))
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Do we need to check if the dimensions of the image are valid for Quake3?
|
|
*/
|
|
|
|
/*
|
|
* Check if CompressionMethod and FilterMethod are valid.
|
|
*/
|
|
|
|
if(!((IHDR->CompressionMethod == PNG_CompressionMethod_0) && (IHDR->FilterMethod == PNG_FilterMethod_0)))
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Check if InterlaceMethod is valid.
|
|
*/
|
|
|
|
if(!((IHDR->InterlaceMethod == PNG_InterlaceMethod_NonInterlaced) || (IHDR->InterlaceMethod == PNG_InterlaceMethod_Interlaced)))
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Read palette for an indexed image.
|
|
*/
|
|
|
|
if(IHDR->ColourType == PNG_ColourType_Indexed)
|
|
{
|
|
/*
|
|
* We need the palette first.
|
|
*/
|
|
|
|
if(!FindChunk(ThePNG, PNG_ChunkType_PLTE))
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Read the chunk-header.
|
|
*/
|
|
|
|
CH = BufferedFileRead(ThePNG, PNG_ChunkHeader_Size);
|
|
if(!CH)
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* PNG multi-byte types are in Big Endian
|
|
*/
|
|
|
|
ChunkHeaderLength = BigLong(CH->Length);
|
|
ChunkHeaderType = BigLong(CH->Type);
|
|
|
|
/*
|
|
* Check if the chunk is an PLTE.
|
|
*/
|
|
|
|
if(!(ChunkHeaderType == PNG_ChunkType_PLTE))
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Check if Length is divisible by 3
|
|
*/
|
|
|
|
if(ChunkHeaderLength % 3)
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Read the raw palette data
|
|
*/
|
|
|
|
InPal = BufferedFileRead(ThePNG, ChunkHeaderLength);
|
|
if(!InPal)
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Read the CRC for the palette
|
|
*/
|
|
|
|
CRC = BufferedFileRead(ThePNG, PNG_ChunkCRC_Size);
|
|
if(!CRC)
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Set some default values.
|
|
*/
|
|
|
|
for(i = 0; i < 256; i++)
|
|
{
|
|
OutPal[i * Q3IMAGE_BYTESPERPIXEL + 0] = 0x00;
|
|
OutPal[i * Q3IMAGE_BYTESPERPIXEL + 1] = 0x00;
|
|
OutPal[i * Q3IMAGE_BYTESPERPIXEL + 2] = 0x00;
|
|
OutPal[i * Q3IMAGE_BYTESPERPIXEL + 3] = 0xFF;
|
|
}
|
|
|
|
/*
|
|
* Convert to the Quake3 RGBA-format.
|
|
*/
|
|
|
|
for(i = 0; i < (ChunkHeaderLength / 3); i++)
|
|
{
|
|
OutPal[i * Q3IMAGE_BYTESPERPIXEL + 0] = InPal[i*3+0];
|
|
OutPal[i * Q3IMAGE_BYTESPERPIXEL + 1] = InPal[i*3+1];
|
|
OutPal[i * Q3IMAGE_BYTESPERPIXEL + 2] = InPal[i*3+2];
|
|
OutPal[i * Q3IMAGE_BYTESPERPIXEL + 3] = 0xFF;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* transparency information is sometimes stored in an tRNS chunk
|
|
*/
|
|
|
|
/*
|
|
* Let's see if there is a tRNS chunk
|
|
*/
|
|
|
|
if(FindChunk(ThePNG, PNG_ChunkType_tRNS))
|
|
{
|
|
uint8_t *Trans;
|
|
|
|
/*
|
|
* Read the chunk-header.
|
|
*/
|
|
|
|
CH = BufferedFileRead(ThePNG, PNG_ChunkHeader_Size);
|
|
if(!CH)
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* PNG multi-byte types are in Big Endian
|
|
*/
|
|
|
|
ChunkHeaderLength = BigLong(CH->Length);
|
|
ChunkHeaderType = BigLong(CH->Type);
|
|
|
|
/*
|
|
* Check if the chunk is an tRNS.
|
|
*/
|
|
|
|
if(!(ChunkHeaderType == PNG_ChunkType_tRNS))
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Read the transparency information.
|
|
*/
|
|
|
|
Trans = BufferedFileRead(ThePNG, ChunkHeaderLength);
|
|
if(!Trans)
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Read the CRC.
|
|
*/
|
|
|
|
CRC = BufferedFileRead(ThePNG, PNG_ChunkCRC_Size);
|
|
if(!CRC)
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Only for Grey, True and Indexed ColourType should tRNS exist.
|
|
*/
|
|
|
|
switch(IHDR->ColourType)
|
|
{
|
|
case PNG_ColourType_Grey :
|
|
{
|
|
if(!ChunkHeaderLength == 2)
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
HasTransparentColour = qtrue;
|
|
|
|
/*
|
|
* Grey can have one colour which is completely transparent.
|
|
* This colour is always stored in 16 bits.
|
|
*/
|
|
|
|
TransparentColour[0] = Trans[0];
|
|
TransparentColour[1] = Trans[1];
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_ColourType_True :
|
|
{
|
|
if(!ChunkHeaderLength == 6)
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
HasTransparentColour = qtrue;
|
|
|
|
/*
|
|
* True can have one colour which is completely transparent.
|
|
* This colour is always stored in 16 bits.
|
|
*/
|
|
|
|
TransparentColour[0] = Trans[0];
|
|
TransparentColour[1] = Trans[1];
|
|
TransparentColour[2] = Trans[2];
|
|
TransparentColour[3] = Trans[3];
|
|
TransparentColour[4] = Trans[4];
|
|
TransparentColour[5] = Trans[5];
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_ColourType_Indexed :
|
|
{
|
|
/*
|
|
* Maximum of 256 one byte transparency entries.
|
|
*/
|
|
|
|
if(ChunkHeaderLength > 256)
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
HasTransparentColour = qtrue;
|
|
|
|
/*
|
|
* alpha values for palette entries
|
|
*/
|
|
|
|
for(i = 0; i < ChunkHeaderLength; i++)
|
|
{
|
|
OutPal[i * Q3IMAGE_BYTESPERPIXEL + 3] = Trans[i];
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* All other ColourTypes should not have tRNS chunks
|
|
*/
|
|
|
|
default :
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Rewind to the start of the file.
|
|
*/
|
|
|
|
if(!BufferedFileRewind(ThePNG, -1))
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Skip the signature
|
|
*/
|
|
|
|
if(!BufferedFileSkip(ThePNG, PNG_Signature_Size))
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Decompress all IDAT chunks
|
|
*/
|
|
|
|
DecompressedDataLength = DecompressIDATs(ThePNG, &DecompressedData);
|
|
if(!(DecompressedDataLength && DecompressedData))
|
|
{
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Allocate output buffer.
|
|
*/
|
|
|
|
OutBuffer = ri.Malloc(IHDR_Width * IHDR_Height * Q3IMAGE_BYTESPERPIXEL);
|
|
if(!OutBuffer)
|
|
{
|
|
ri.Free(DecompressedData);
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Interlaced and Non-interlaced images need to be handled differently.
|
|
*/
|
|
|
|
switch(IHDR->InterlaceMethod)
|
|
{
|
|
case PNG_InterlaceMethod_NonInterlaced :
|
|
{
|
|
if(!DecodeImageNonInterlaced(IHDR, OutBuffer, DecompressedData, DecompressedDataLength, HasTransparentColour, TransparentColour, OutPal))
|
|
{
|
|
ri.Free(OutBuffer);
|
|
ri.Free(DecompressedData);
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PNG_InterlaceMethod_Interlaced :
|
|
{
|
|
if(!DecodeImageInterlaced(IHDR, OutBuffer, DecompressedData, DecompressedDataLength, HasTransparentColour, TransparentColour, OutPal))
|
|
{
|
|
ri.Free(OutBuffer);
|
|
ri.Free(DecompressedData);
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default :
|
|
{
|
|
ri.Free(OutBuffer);
|
|
ri.Free(DecompressedData);
|
|
CloseBufferedFile(ThePNG);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* update the pointer to the image data
|
|
*/
|
|
|
|
*pic = OutBuffer;
|
|
|
|
/*
|
|
* Fill width and height.
|
|
*/
|
|
|
|
if(width)
|
|
{
|
|
*width = IHDR_Width;
|
|
}
|
|
|
|
if(height)
|
|
{
|
|
*height = IHDR_Height;
|
|
}
|
|
|
|
/*
|
|
* DecompressedData is not needed anymore.
|
|
*/
|
|
|
|
ri.Free(DecompressedData);
|
|
|
|
/*
|
|
* We have all data, so close the file.
|
|
*/
|
|
|
|
CloseBufferedFile(ThePNG);
|
|
}
|
|
|
|
//===================================================================
|
|
|
|
/*
|
|
=================
|
|
R_LoadImage
|
|
|
|
Loads any of the supported image types into a cannonical
|
|
32 bit format.
|
|
=================
|
|
*/
|
|
void R_LoadImage( const char *name, byte **pic, int *width, int *height ) {
|
|
int len;
|
|
|
|
*pic = NULL;
|
|
*width = 0;
|
|
*height = 0;
|
|
|
|
len = strlen(name);
|
|
if (len<5) {
|
|
return;
|
|
}
|
|
|
|
if ( !Q_stricmp( name+len-4, ".tga" ) ) {
|
|
LoadTGA( name, pic, width, height );
|
|
|
|
// This is a hack to get around the fact that some
|
|
// baseq3 shaders refer to tga files where the images
|
|
// are actually jpgs
|
|
if (!*pic) {
|
|
// try jpg in place of tga
|
|
char altname[MAX_QPATH];
|
|
|
|
strcpy( altname, name );
|
|
len = strlen( altname );
|
|
altname[len-3] = 'j';
|
|
altname[len-2] = 'p';
|
|
altname[len-1] = 'g';
|
|
|
|
ri.Printf( PRINT_DEVELOPER, "WARNING: %s failed, trying %s\n", name, altname );
|
|
LoadJPG( altname, pic, width, height );
|
|
}
|
|
}
|
|
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" ) )
|
|
{
|
|
LoadPNG( name, pic, width, height );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
R_FindImageFile
|
|
|
|
Finds or loads the given image.
|
|
Returns NULL if it fails, not a default image.
|
|
==============
|
|
*/
|
|
image_t *R_FindImageFile( const char *name, qboolean mipmap, qboolean allowPicmip, int glWrapClampMode ) {
|
|
image_t *image;
|
|
int width, height;
|
|
byte *pic;
|
|
long hash;
|
|
|
|
if (!name) {
|
|
return NULL;
|
|
}
|
|
|
|
hash = generateHashValue(name);
|
|
|
|
//
|
|
// see if the image is already loaded
|
|
//
|
|
for (image=hashTable[hash]; image; image=image->next) {
|
|
if ( !strcmp( name, image->imgName ) ) {
|
|
// the white image can be used with any set of parms, but other mismatches are errors
|
|
if ( strcmp( name, "*white" ) ) {
|
|
if ( image->mipmap != mipmap ) {
|
|
ri.Printf( PRINT_DEVELOPER, "WARNING: reused image %s with mixed mipmap parm\n", name );
|
|
}
|
|
if ( image->allowPicmip != allowPicmip ) {
|
|
ri.Printf( PRINT_DEVELOPER, "WARNING: reused image %s with mixed allowPicmip parm\n", name );
|
|
}
|
|
if ( image->wrapClampMode != glWrapClampMode ) {
|
|
ri.Printf( PRINT_ALL, "WARNING: reused image %s with mixed glWrapClampMode parm\n", name );
|
|
}
|
|
}
|
|
return image;
|
|
}
|
|
}
|
|
|
|
//
|
|
// load the pic from disk
|
|
//
|
|
R_LoadImage( name, &pic, &width, &height );
|
|
if ( pic == NULL ) {
|
|
return NULL;
|
|
}
|
|
|
|
image = R_CreateImage( ( char * ) name, pic, width, height, mipmap, allowPicmip, glWrapClampMode );
|
|
ri.Free( pic );
|
|
return image;
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
R_CreateDlightImage
|
|
================
|
|
*/
|
|
#define DLIGHT_SIZE 16
|
|
static void R_CreateDlightImage( void ) {
|
|
int x,y;
|
|
byte data[DLIGHT_SIZE][DLIGHT_SIZE][4];
|
|
int b;
|
|
|
|
// make a centered inverse-square falloff blob for dynamic lighting
|
|
for (x=0 ; x<DLIGHT_SIZE ; x++) {
|
|
for (y=0 ; y<DLIGHT_SIZE ; y++) {
|
|
float d;
|
|
|
|
d = ( DLIGHT_SIZE/2 - 0.5f - x ) * ( DLIGHT_SIZE/2 - 0.5f - x ) +
|
|
( DLIGHT_SIZE/2 - 0.5f - y ) * ( DLIGHT_SIZE/2 - 0.5f - y );
|
|
b = 4000 / d;
|
|
if (b > 255) {
|
|
b = 255;
|
|
} else if ( b < 75 ) {
|
|
b = 0;
|
|
}
|
|
data[y][x][0] =
|
|
data[y][x][1] =
|
|
data[y][x][2] = b;
|
|
data[y][x][3] = 255;
|
|
}
|
|
}
|
|
tr.dlightImage = R_CreateImage("*dlight", (byte *)data, DLIGHT_SIZE, DLIGHT_SIZE, qfalse, qfalse, GL_CLAMP );
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
R_InitFogTable
|
|
=================
|
|
*/
|
|
void R_InitFogTable( void ) {
|
|
int i;
|
|
float d;
|
|
float exp;
|
|
|
|
exp = 0.5;
|
|
|
|
for ( i = 0 ; i < FOG_TABLE_SIZE ; i++ ) {
|
|
d = pow ( (float)i/(FOG_TABLE_SIZE-1), exp );
|
|
|
|
tr.fogTable[i] = d;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_FogFactor
|
|
|
|
Returns a 0.0 to 1.0 fog density value
|
|
This is called for each texel of the fog texture on startup
|
|
and for each vertex of transparent shaders in fog dynamically
|
|
================
|
|
*/
|
|
float R_FogFactor( float s, float t ) {
|
|
float d;
|
|
|
|
s -= 1.0/512;
|
|
if ( s < 0 ) {
|
|
return 0;
|
|
}
|
|
if ( t < 1.0/32 ) {
|
|
return 0;
|
|
}
|
|
if ( t < 31.0/32 ) {
|
|
s *= (t - 1.0f/32.0f) / (30.0f/32.0f);
|
|
}
|
|
|
|
// we need to leave a lot of clamp range
|
|
s *= 8;
|
|
|
|
if ( s > 1.0 ) {
|
|
s = 1.0;
|
|
}
|
|
|
|
d = tr.fogTable[ (int)(s * (FOG_TABLE_SIZE-1)) ];
|
|
|
|
return d;
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_CreateFogImage
|
|
================
|
|
*/
|
|
#define FOG_S 256
|
|
#define FOG_T 32
|
|
static void R_CreateFogImage( void ) {
|
|
int x,y;
|
|
byte *data;
|
|
float g;
|
|
float d;
|
|
float borderColor[4];
|
|
|
|
data = ri.Hunk_AllocateTempMemory( FOG_S * FOG_T * 4 );
|
|
|
|
g = 2.0;
|
|
|
|
// S is distance, T is depth
|
|
for (x=0 ; x<FOG_S ; x++) {
|
|
for (y=0 ; y<FOG_T ; y++) {
|
|
d = R_FogFactor( ( x + 0.5f ) / FOG_S, ( y + 0.5f ) / FOG_T );
|
|
|
|
data[(y*FOG_S+x)*4+0] =
|
|
data[(y*FOG_S+x)*4+1] =
|
|
data[(y*FOG_S+x)*4+2] = 255;
|
|
data[(y*FOG_S+x)*4+3] = 255*d;
|
|
}
|
|
}
|
|
// standard openGL clamping doesn't really do what we want -- it includes
|
|
// the border color at the edges. OpenGL 1.2 has clamp-to-edge, which does
|
|
// what we want.
|
|
tr.fogImage = R_CreateImage("*fog", (byte *)data, FOG_S, FOG_T, qfalse, qfalse, GL_CLAMP );
|
|
ri.Hunk_FreeTempMemory( data );
|
|
|
|
borderColor[0] = 1.0;
|
|
borderColor[1] = 1.0;
|
|
borderColor[2] = 1.0;
|
|
borderColor[3] = 1;
|
|
|
|
qglTexParameterfv( GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
R_CreateDefaultImage
|
|
==================
|
|
*/
|
|
#define DEFAULT_SIZE 16
|
|
static void R_CreateDefaultImage( void ) {
|
|
int x;
|
|
byte data[DEFAULT_SIZE][DEFAULT_SIZE][4];
|
|
|
|
// the default image will be a box, to allow you to see the mapping coordinates
|
|
Com_Memset( data, 32, sizeof( data ) );
|
|
for ( x = 0 ; x < DEFAULT_SIZE ; x++ ) {
|
|
data[0][x][0] =
|
|
data[0][x][1] =
|
|
data[0][x][2] =
|
|
data[0][x][3] = 255;
|
|
|
|
data[x][0][0] =
|
|
data[x][0][1] =
|
|
data[x][0][2] =
|
|
data[x][0][3] = 255;
|
|
|
|
data[DEFAULT_SIZE-1][x][0] =
|
|
data[DEFAULT_SIZE-1][x][1] =
|
|
data[DEFAULT_SIZE-1][x][2] =
|
|
data[DEFAULT_SIZE-1][x][3] = 255;
|
|
|
|
data[x][DEFAULT_SIZE-1][0] =
|
|
data[x][DEFAULT_SIZE-1][1] =
|
|
data[x][DEFAULT_SIZE-1][2] =
|
|
data[x][DEFAULT_SIZE-1][3] = 255;
|
|
}
|
|
tr.defaultImage = R_CreateImage("*default", (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE, qtrue, qfalse, GL_REPEAT );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
R_CreateBuiltinImages
|
|
==================
|
|
*/
|
|
void R_CreateBuiltinImages( void ) {
|
|
int x,y;
|
|
byte data[DEFAULT_SIZE][DEFAULT_SIZE][4];
|
|
|
|
R_CreateDefaultImage();
|
|
|
|
// we use a solid white image instead of disabling texturing
|
|
Com_Memset( data, 255, sizeof( data ) );
|
|
tr.whiteImage = R_CreateImage("*white", (byte *)data, 8, 8, qfalse, qfalse, GL_REPEAT );
|
|
|
|
// with overbright bits active, we need an image which is some fraction of full color,
|
|
// for default lightmaps, etc
|
|
for (x=0 ; x<DEFAULT_SIZE ; x++) {
|
|
for (y=0 ; y<DEFAULT_SIZE ; y++) {
|
|
data[y][x][0] =
|
|
data[y][x][1] =
|
|
data[y][x][2] = tr.identityLightByte;
|
|
data[y][x][3] = 255;
|
|
}
|
|
}
|
|
|
|
tr.identityLightImage = R_CreateImage("*identityLight", (byte *)data, 8, 8, qfalse, qfalse, GL_REPEAT );
|
|
|
|
|
|
for(x=0;x<32;x++) {
|
|
// scratchimage is usually used for cinematic drawing
|
|
tr.scratchImage[x] = R_CreateImage("*scratch", (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE, qfalse, qtrue, GL_CLAMP );
|
|
}
|
|
|
|
R_CreateDlightImage();
|
|
R_CreateFogImage();
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
R_SetColorMappings
|
|
===============
|
|
*/
|
|
void R_SetColorMappings( void ) {
|
|
int i, j;
|
|
float g;
|
|
int inf;
|
|
int shift;
|
|
|
|
// setup the overbright lighting
|
|
tr.overbrightBits = r_overBrightBits->integer;
|
|
if ( !glConfig.deviceSupportsGamma ) {
|
|
tr.overbrightBits = 0; // need hardware gamma for overbright
|
|
}
|
|
|
|
// never overbright in windowed mode
|
|
if ( !glConfig.isFullscreen )
|
|
{
|
|
tr.overbrightBits = 0;
|
|
}
|
|
|
|
// allow 2 overbright bits in 24 bit, but only 1 in 16 bit
|
|
if ( glConfig.colorBits > 16 ) {
|
|
if ( tr.overbrightBits > 2 ) {
|
|
tr.overbrightBits = 2;
|
|
}
|
|
} else {
|
|
if ( tr.overbrightBits > 1 ) {
|
|
tr.overbrightBits = 1;
|
|
}
|
|
}
|
|
if ( tr.overbrightBits < 0 ) {
|
|
tr.overbrightBits = 0;
|
|
}
|
|
|
|
tr.identityLight = 1.0f / ( 1 << tr.overbrightBits );
|
|
tr.identityLightByte = 255 * tr.identityLight;
|
|
|
|
|
|
if ( r_intensity->value <= 1 ) {
|
|
ri.Cvar_Set( "r_intensity", "1" );
|
|
}
|
|
|
|
if ( r_gamma->value < 0.5f ) {
|
|
ri.Cvar_Set( "r_gamma", "0.5" );
|
|
} else if ( r_gamma->value > 3.0f ) {
|
|
ri.Cvar_Set( "r_gamma", "3.0" );
|
|
}
|
|
|
|
g = r_gamma->value;
|
|
|
|
shift = tr.overbrightBits;
|
|
|
|
for ( i = 0; i < 256; i++ ) {
|
|
if ( g == 1 ) {
|
|
inf = i;
|
|
} else {
|
|
inf = 255 * pow ( i/255.0f, 1.0f / g ) + 0.5f;
|
|
}
|
|
inf <<= shift;
|
|
if (inf < 0) {
|
|
inf = 0;
|
|
}
|
|
if (inf > 255) {
|
|
inf = 255;
|
|
}
|
|
s_gammatable[i] = inf;
|
|
}
|
|
|
|
for (i=0 ; i<256 ; i++) {
|
|
j = i * r_intensity->value;
|
|
if (j > 255) {
|
|
j = 255;
|
|
}
|
|
s_intensitytable[i] = j;
|
|
}
|
|
|
|
if ( glConfig.deviceSupportsGamma )
|
|
{
|
|
GLimp_SetGamma( s_gammatable, s_gammatable, s_gammatable );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_InitImages
|
|
===============
|
|
*/
|
|
void R_InitImages( void ) {
|
|
Com_Memset(hashTable, 0, sizeof(hashTable));
|
|
// build brightness translation tables
|
|
R_SetColorMappings();
|
|
|
|
// create default texture and white texture
|
|
R_CreateBuiltinImages();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_DeleteTextures
|
|
===============
|
|
*/
|
|
void R_DeleteTextures( void ) {
|
|
int i;
|
|
|
|
for ( i=0; i<tr.numImages ; i++ ) {
|
|
qglDeleteTextures( 1, &tr.images[i]->texnum );
|
|
}
|
|
Com_Memset( tr.images, 0, sizeof( tr.images ) );
|
|
|
|
tr.numImages = 0;
|
|
|
|
Com_Memset( glState.currenttextures, 0, sizeof( glState.currenttextures ) );
|
|
if ( qglBindTexture ) {
|
|
if ( qglActiveTextureARB ) {
|
|
GL_SelectTexture( 1 );
|
|
qglBindTexture( GL_TEXTURE_2D, 0 );
|
|
GL_SelectTexture( 0 );
|
|
qglBindTexture( GL_TEXTURE_2D, 0 );
|
|
} else {
|
|
qglBindTexture( GL_TEXTURE_2D, 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============================================================================
|
|
|
|
SKINS
|
|
|
|
============================================================================
|
|
*/
|
|
|
|
/*
|
|
==================
|
|
CommaParse
|
|
|
|
This is unfortunate, but the skin files aren't
|
|
compatable with our normal parsing rules.
|
|
==================
|
|
*/
|
|
static char *CommaParse( char **data_p ) {
|
|
int c = 0, len;
|
|
char *data;
|
|
static char com_token[MAX_TOKEN_CHARS];
|
|
|
|
data = *data_p;
|
|
len = 0;
|
|
com_token[0] = 0;
|
|
|
|
// make sure incoming data is valid
|
|
if ( !data ) {
|
|
*data_p = NULL;
|
|
return com_token;
|
|
}
|
|
|
|
while ( 1 ) {
|
|
// skip whitespace
|
|
while( (c = *data) <= ' ') {
|
|
if( !c ) {
|
|
break;
|
|
}
|
|
data++;
|
|
}
|
|
|
|
|
|
c = *data;
|
|
|
|
// skip double slash comments
|
|
if ( c == '/' && data[1] == '/' )
|
|
{
|
|
while (*data && *data != '\n')
|
|
data++;
|
|
}
|
|
// skip /* */ comments
|
|
else if ( c=='/' && data[1] == '*' )
|
|
{
|
|
while ( *data && ( *data != '*' || data[1] != '/' ) )
|
|
{
|
|
data++;
|
|
}
|
|
if ( *data )
|
|
{
|
|
data += 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( c == 0 ) {
|
|
return "";
|
|
}
|
|
|
|
// handle quoted strings
|
|
if (c == '\"')
|
|
{
|
|
data++;
|
|
while (1)
|
|
{
|
|
c = *data++;
|
|
if (c=='\"' || !c)
|
|
{
|
|
com_token[len] = 0;
|
|
*data_p = ( char * ) data;
|
|
return com_token;
|
|
}
|
|
if (len < MAX_TOKEN_CHARS)
|
|
{
|
|
com_token[len] = c;
|
|
len++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// parse a regular word
|
|
do
|
|
{
|
|
if (len < MAX_TOKEN_CHARS)
|
|
{
|
|
com_token[len] = c;
|
|
len++;
|
|
}
|
|
data++;
|
|
c = *data;
|
|
} while (c>32 && c != ',' );
|
|
|
|
if (len == MAX_TOKEN_CHARS)
|
|
{
|
|
// Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS);
|
|
len = 0;
|
|
}
|
|
com_token[len] = 0;
|
|
|
|
*data_p = ( char * ) data;
|
|
return com_token;
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
RE_RegisterSkin
|
|
|
|
===============
|
|
*/
|
|
qhandle_t RE_RegisterSkin( const char *name ) {
|
|
qhandle_t hSkin;
|
|
skin_t *skin;
|
|
skinSurface_t *surf;
|
|
char *text, *text_p;
|
|
char *token;
|
|
char surfName[MAX_QPATH];
|
|
|
|
if ( !name || !name[0] ) {
|
|
Com_Printf( "Empty name passed to RE_RegisterSkin\n" );
|
|
return 0;
|
|
}
|
|
|
|
if ( strlen( name ) >= MAX_QPATH ) {
|
|
Com_Printf( "Skin name exceeds MAX_QPATH\n" );
|
|
return 0;
|
|
}
|
|
|
|
|
|
// see if the skin is already loaded
|
|
for ( hSkin = 1; hSkin < tr.numSkins ; hSkin++ ) {
|
|
skin = tr.skins[hSkin];
|
|
if ( !Q_stricmp( skin->name, name ) ) {
|
|
if( skin->numSurfaces == 0 ) {
|
|
return 0; // default skin
|
|
}
|
|
return hSkin;
|
|
}
|
|
}
|
|
|
|
// allocate a new skin
|
|
if ( tr.numSkins == MAX_SKINS ) {
|
|
ri.Printf( PRINT_WARNING, "WARNING: RE_RegisterSkin( '%s' ) MAX_SKINS hit\n", name );
|
|
return 0;
|
|
}
|
|
tr.numSkins++;
|
|
skin = ri.Hunk_Alloc( sizeof( skin_t ), h_low );
|
|
tr.skins[hSkin] = skin;
|
|
Q_strncpyz( skin->name, name, sizeof( skin->name ) );
|
|
skin->numSurfaces = 0;
|
|
|
|
// make sure the render thread is stopped
|
|
R_SyncRenderThread();
|
|
|
|
// If not a .skin file, load as a single shader
|
|
if ( strcmp( name + strlen( name ) - 5, ".skin" ) ) {
|
|
skin->numSurfaces = 1;
|
|
skin->surfaces[0] = ri.Hunk_Alloc( sizeof(skin->surfaces[0]), h_low );
|
|
skin->surfaces[0]->shader = R_FindShader( name, LIGHTMAP_NONE, qtrue );
|
|
return hSkin;
|
|
}
|
|
|
|
// load and parse the skin file
|
|
ri.FS_ReadFile( name, (void **)&text );
|
|
if ( !text ) {
|
|
return 0;
|
|
}
|
|
|
|
text_p = text;
|
|
while ( text_p && *text_p ) {
|
|
// get surface name
|
|
token = CommaParse( &text_p );
|
|
Q_strncpyz( surfName, token, sizeof( surfName ) );
|
|
|
|
if ( !token[0] ) {
|
|
break;
|
|
}
|
|
// lowercase the surface name so skin compares are faster
|
|
Q_strlwr( surfName );
|
|
|
|
if ( *text_p == ',' ) {
|
|
text_p++;
|
|
}
|
|
|
|
if ( strstr( token, "tag_" ) ) {
|
|
continue;
|
|
}
|
|
|
|
// parse the shader name
|
|
token = CommaParse( &text_p );
|
|
|
|
surf = skin->surfaces[ skin->numSurfaces ] = ri.Hunk_Alloc( sizeof( *skin->surfaces[0] ), h_low );
|
|
Q_strncpyz( surf->name, surfName, sizeof( surf->name ) );
|
|
surf->shader = R_FindShader( token, LIGHTMAP_NONE, qtrue );
|
|
skin->numSurfaces++;
|
|
}
|
|
|
|
ri.FS_FreeFile( text );
|
|
|
|
|
|
// never let a skin have 0 shaders
|
|
if ( skin->numSurfaces == 0 ) {
|
|
return 0; // use default skin
|
|
}
|
|
|
|
return hSkin;
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
R_InitSkins
|
|
===============
|
|
*/
|
|
void R_InitSkins( void ) {
|
|
skin_t *skin;
|
|
|
|
tr.numSkins = 1;
|
|
|
|
// make the default skin have all default shaders
|
|
skin = tr.skins[0] = ri.Hunk_Alloc( sizeof( skin_t ), h_low );
|
|
Q_strncpyz( skin->name, "<default skin>", sizeof( skin->name ) );
|
|
skin->numSurfaces = 1;
|
|
skin->surfaces[0] = ri.Hunk_Alloc( sizeof( *skin->surfaces ), h_low );
|
|
skin->surfaces[0]->shader = tr.defaultShader;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_GetSkinByHandle
|
|
===============
|
|
*/
|
|
skin_t *R_GetSkinByHandle( qhandle_t hSkin ) {
|
|
if ( hSkin < 1 || hSkin >= tr.numSkins ) {
|
|
return tr.skins[0];
|
|
}
|
|
return tr.skins[ hSkin ];
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_SkinList_f
|
|
===============
|
|
*/
|
|
void R_SkinList_f( void ) {
|
|
int i, j;
|
|
skin_t *skin;
|
|
|
|
ri.Printf (PRINT_ALL, "------------------\n");
|
|
|
|
for ( i = 0 ; i < tr.numSkins ; i++ ) {
|
|
skin = tr.skins[i];
|
|
|
|
ri.Printf( PRINT_ALL, "%3i:%s\n", i, skin->name );
|
|
for ( j = 0 ; j < skin->numSurfaces ; j++ ) {
|
|
ri.Printf( PRINT_ALL, " %s = %s\n",
|
|
skin->surfaces[j]->name, skin->surfaces[j]->shader->name );
|
|
}
|
|
}
|
|
ri.Printf (PRINT_ALL, "------------------\n");
|
|
}
|
|
|