diff options
Diffstat (limited to 'code')
-rw-r--r-- | code/qcommon/qfiles.h | 44 | ||||
-rw-r--r-- | code/renderer/tr_image.c | 1216 | ||||
-rw-r--r-- | code/renderer/tr_image_bmp.c | 236 | ||||
-rw-r--r-- | code/renderer/tr_image_bmp.h | 1 | ||||
-rw-r--r-- | code/renderer/tr_image_jpg.c | 581 | ||||
-rw-r--r-- | code/renderer/tr_image_jpg.h | 1 | ||||
-rw-r--r-- | code/renderer/tr_image_pcx.c | 166 | ||||
-rw-r--r-- | code/renderer/tr_image_pcx.h | 1 | ||||
-rw-r--r-- | code/renderer/tr_image_tga.c | 317 | ||||
-rw-r--r-- | code/renderer/tr_image_tga.h | 1 |
10 files changed, 1309 insertions, 1255 deletions
diff --git a/code/qcommon/qfiles.h b/code/qcommon/qfiles.h index 7b7ffe2..7f1ef96 100644 --- a/code/qcommon/qfiles.h +++ b/code/qcommon/qfiles.h @@ -69,50 +69,6 @@ typedef struct { int jtrgLength; // number of jump table targets } vmHeader_t; - -/* -======================================================================== - -PCX files are used for 8 bit images - -======================================================================== -*/ - -typedef struct { - char manufacturer; - char version; - char encoding; - char bits_per_pixel; - unsigned short xmin,ymin,xmax,ymax; - unsigned short hres,vres; - unsigned char palette[48]; - char reserved; - char color_planes; - unsigned short bytes_per_line; - unsigned short palette_type; - char filler[58]; - unsigned char data; // unbounded -} pcx_t; - - -/* -======================================================================== - -TGA files are used for 24/32 bit images - -======================================================================== -*/ - -typedef struct _TargaHeader { - unsigned char id_length, colormap_type, image_type; - unsigned short colormap_index, colormap_length; - unsigned char colormap_size; - unsigned short x_origin, y_origin, width, height; - unsigned char pixel_size, attributes; -} TargaHeader; - - - /* ======================================================================== diff --git a/code/renderer/tr_image.c b/code/renderer/tr_image.c index a478609..81a478e 100644 --- a/code/renderer/tr_image.c +++ b/code/renderer/tr_image.c @@ -22,22 +22,12 @@ 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 "tr_image_bmp.h" +#include "tr_image_jpg.h" +#include "tr_image_pcx.h" #include "tr_image_png.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 ); +#include "tr_image_tga.h" static byte s_intensitytable[256]; static unsigned char s_gammatable[256]; @@ -799,1202 +789,6 @@ image_t *R_CreateImage( const char *name, const byte *pic, int width, int height return image; } - -/* -========================================================= - -BMP LOADING - -========================================================= -*/ -typedef struct -{ - char id[2]; - unsigned fileSize; - unsigned reserved0; - unsigned bitmapDataOffset; - unsigned bitmapHeaderSize; - unsigned width; - unsigned height; - unsigned short planes; - unsigned short bitsPerPixel; - unsigned compression; - unsigned bitmapDataSize; - unsigned hRes; - unsigned vRes; - unsigned colors; - unsigned 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 *end; - byte *buffer = NULL; - int length; - BMPHeader_t bmpHeader; - byte *bmpRGBA; - - *pic = NULL; - - if(width) - *width = 0; - - if(height) - *height = 0; - - // - // load the file - // - length = ri.FS_ReadFile( ( char * ) name, (void **)&buffer); - if (!buffer || length < 0) { - return; - } - - if (length < 54) - { - ri.Error( ERR_DROP, "LoadBMP: header too short (%s)\n", name ); - } - - buf_p = buffer; - end = buffer + length; - - bmpHeader.id[0] = *buf_p++; - bmpHeader.id[1] = *buf_p++; - bmpHeader.fileSize = LittleLong( * ( int * ) buf_p ); - buf_p += 4; - bmpHeader.reserved0 = LittleLong( * ( int * ) buf_p ); - buf_p += 4; - bmpHeader.bitmapDataOffset = LittleLong( * ( int * ) buf_p ); - buf_p += 4; - bmpHeader.bitmapHeaderSize = LittleLong( * ( int * ) buf_p ); - buf_p += 4; - bmpHeader.width = LittleLong( * ( int * ) buf_p ); - buf_p += 4; - bmpHeader.height = LittleLong( * ( int * ) buf_p ); - buf_p += 4; - bmpHeader.planes = LittleShort( * ( short * ) buf_p ); - buf_p += 2; - bmpHeader.bitsPerPixel = LittleShort( * ( short * ) buf_p ); - buf_p += 2; - bmpHeader.compression = LittleLong( * ( int * ) buf_p ); - buf_p += 4; - bmpHeader.bitmapDataSize = LittleLong( * ( int * ) buf_p ); - buf_p += 4; - bmpHeader.hRes = LittleLong( * ( int * ) buf_p ); - buf_p += 4; - bmpHeader.vRes = LittleLong( * ( int * ) buf_p ); - buf_p += 4; - bmpHeader.colors = LittleLong( * ( int * ) buf_p ); - buf_p += 4; - bmpHeader.importantColors = LittleLong( * ( int * ) buf_p ); - buf_p += 4; - - if ( bmpHeader.bitsPerPixel == 8 ) - { - if (buf_p + sizeof(bmpHeader.palette) > end) - ri.Error( ERR_DROP, "LoadBMP: header too short (%s)\n", name ); - - Com_Memcpy( bmpHeader.palette, buf_p, sizeof( bmpHeader.palette ) ); - buf_p += sizeof(bmpHeader.palette); - } - - if (buffer + bmpHeader.bitmapDataOffset > end) - { - ri.Error( ERR_DROP, "LoadBMP: invalid offset value in header (%s)\n", name ); - } - - buf_p = buffer + bmpHeader.bitmapDataOffset; - - 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 (%u vs. %u) (%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 ); - } - - switch ( bmpHeader.bitsPerPixel ) - { - case 8: - case 16: - case 24: - case 32: - break; - default: - ri.Error( ERR_DROP, "LoadBMP: illegal pixel_size '%hu' in file '%s'\n", bmpHeader.bitsPerPixel, name ); - break; - } - - 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(buf_p + numPixels*bmpHeader.bitsPerPixel/8 > end) - { - ri.Error (ERR_DROP, "LoadBMP: file truncated (%s)\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; - } - } - } - - 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 *end; - byte *buffer = NULL; - TargaHeader targa_header; - byte *targa_rgba; - int length; - - *pic = NULL; - - if(width) - *width = 0; - if(height) - *height = 0; - - // - // load the file - // - length = ri.FS_ReadFile ( ( char * ) name, (void **)&buffer); - if (!buffer || length < 0) { - return; - } - - if(length < 18) - { - ri.Error( ERR_DROP, "LoadTGA: header too short (%s)\n", name ); - } - - buf_p = buffer; - end = buffer + length; - - 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(!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); - - if (targa_header.id_length != 0) - { - if (buf_p + targa_header.id_length > end) - ri.Error( ERR_DROP, "LoadTGA: header too short (%s)\n", name ); - - buf_p += targa_header.id_length; // skip TARGA image comment - } - - if ( targa_header.image_type==2 || targa_header.image_type == 3 ) - { - if(buf_p + columns*rows*targa_header.pixel_size/8 > end) - { - ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)\n", name); - } - - // 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; ) { - if(buf_p + 1 > end) - ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)\n", name); - packetHeader= *buf_p++; - packetSize = 1 + (packetHeader & 0x7f); - if (packetHeader & 0x80) { // run-length packet - if(buf_p + targa_header.pixel_size/8 > end) - ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)\n", name); - 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 - - if(buf_p + targa_header.pixel_size/8*packetSize > end) - ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)\n", name); - 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 - // 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); - } - - if (width) - *width = columns; - if (height) - *height = rows; - - *pic = targa_rgba; - - 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; -} - //=================================================================== typedef struct @@ -2011,7 +805,7 @@ static imageExtToLoaderMap_t imageLoaders[ ] = { "jpg", LoadJPG }, { "jpeg", LoadJPG }, { "png", LoadPNG }, - { "pcx", LoadPCX32 }, + { "pcx", LoadPCX }, { "bmp", LoadBMP } }; diff --git a/code/renderer/tr_image_bmp.c b/code/renderer/tr_image_bmp.c new file mode 100644 index 0000000..191843f --- /dev/null +++ b/code/renderer/tr_image_bmp.c @@ -0,0 +1,236 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +#include "tr_local.h" + +typedef struct +{ + char id[2]; + unsigned fileSize; + unsigned reserved0; + unsigned bitmapDataOffset; + unsigned bitmapHeaderSize; + unsigned width; + unsigned height; + unsigned short planes; + unsigned short bitsPerPixel; + unsigned compression; + unsigned bitmapDataSize; + unsigned hRes; + unsigned vRes; + unsigned colors; + unsigned importantColors; + unsigned char palette[256][4]; +} BMPHeader_t; + +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 *end; + byte *buffer = NULL; + int length; + BMPHeader_t bmpHeader; + byte *bmpRGBA; + + *pic = NULL; + + if(width) + *width = 0; + + if(height) + *height = 0; + + // + // load the file + // + length = ri.FS_ReadFile( ( char * ) name, (void **)&buffer); + if (!buffer || length < 0) { + return; + } + + if (length < 54) + { + ri.Error( ERR_DROP, "LoadBMP: header too short (%s)\n", name ); + } + + buf_p = buffer; + end = buffer + length; + + bmpHeader.id[0] = *buf_p++; + bmpHeader.id[1] = *buf_p++; + bmpHeader.fileSize = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.reserved0 = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.bitmapDataOffset = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.bitmapHeaderSize = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.width = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.height = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.planes = LittleShort( * ( short * ) buf_p ); + buf_p += 2; + bmpHeader.bitsPerPixel = LittleShort( * ( short * ) buf_p ); + buf_p += 2; + bmpHeader.compression = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.bitmapDataSize = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.hRes = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.vRes = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.colors = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.importantColors = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + + if ( bmpHeader.bitsPerPixel == 8 ) + { + if (buf_p + sizeof(bmpHeader.palette) > end) + ri.Error( ERR_DROP, "LoadBMP: header too short (%s)\n", name ); + + Com_Memcpy( bmpHeader.palette, buf_p, sizeof( bmpHeader.palette ) ); + buf_p += sizeof(bmpHeader.palette); + } + + if (buffer + bmpHeader.bitmapDataOffset > end) + { + ri.Error( ERR_DROP, "LoadBMP: invalid offset value in header (%s)\n", name ); + } + + buf_p = buffer + bmpHeader.bitmapDataOffset; + + 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 (%u vs. %u) (%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 ); + } + + switch ( bmpHeader.bitsPerPixel ) + { + case 8: + case 16: + case 24: + case 32: + break; + default: + ri.Error( ERR_DROP, "LoadBMP: illegal pixel_size '%hu' in file '%s'\n", bmpHeader.bitsPerPixel, name ); + break; + } + + 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(buf_p + numPixels*bmpHeader.bitsPerPixel/8 > end) + { + ri.Error (ERR_DROP, "LoadBMP: file truncated (%s)\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; + } + } + } + + ri.FS_FreeFile( buffer ); + +} diff --git a/code/renderer/tr_image_bmp.h b/code/renderer/tr_image_bmp.h new file mode 100644 index 0000000..98b16b9 --- /dev/null +++ b/code/renderer/tr_image_bmp.h @@ -0,0 +1 @@ +void LoadBMP( const char *name, byte **pic, int *width, int *height ); diff --git a/code/renderer/tr_image_jpg.c b/code/renderer/tr_image_jpg.c new file mode 100644 index 0000000..cae7ccb --- /dev/null +++ b/code/renderer/tr_image_jpg.c @@ -0,0 +1,581 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +#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" + +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; +} diff --git a/code/renderer/tr_image_jpg.h b/code/renderer/tr_image_jpg.h new file mode 100644 index 0000000..159c613 --- /dev/null +++ b/code/renderer/tr_image_jpg.h @@ -0,0 +1 @@ +void LoadJPG( const char *name, byte **pic, int *width, int *height ); diff --git a/code/renderer/tr_image_pcx.c b/code/renderer/tr_image_pcx.c new file mode 100644 index 0000000..f369b76 --- /dev/null +++ b/code/renderer/tr_image_pcx.c @@ -0,0 +1,166 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +#include "tr_local.h" + +/* +======================================================================== + +PCX files are used for 8 bit images + +======================================================================== +*/ + +typedef struct { + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hres,vres; + unsigned char palette[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + char filler[58]; + unsigned char data; // unbounded +} pcx_t; + + +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); +} + + +void LoadPCX ( 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); +} diff --git a/code/renderer/tr_image_pcx.h b/code/renderer/tr_image_pcx.h new file mode 100644 index 0000000..0c8a5f7 --- /dev/null +++ b/code/renderer/tr_image_pcx.h @@ -0,0 +1 @@ +void LoadPCX( const char *name, byte **pic, int *width, int *height ); diff --git a/code/renderer/tr_image_tga.c b/code/renderer/tr_image_tga.c new file mode 100644 index 0000000..b375404 --- /dev/null +++ b/code/renderer/tr_image_tga.c @@ -0,0 +1,317 @@ +/* +=========================================================================== +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 +=========================================================================== +*/ + +#include "tr_local.h" + +/* +======================================================================== + +TGA files are used for 24/32 bit images + +======================================================================== +*/ + +typedef struct _TargaHeader { + unsigned char id_length, colormap_type, image_type; + unsigned short colormap_index, colormap_length; + unsigned char colormap_size; + unsigned short x_origin, y_origin, width, height; + unsigned char pixel_size, attributes; +} TargaHeader; + +void LoadTGA ( const char *name, byte **pic, int *width, int *height) +{ + unsigned columns, rows, numPixels; + byte *pixbuf; + int row, column; + byte *buf_p; + byte *end; + byte *buffer = NULL; + TargaHeader targa_header; + byte *targa_rgba; + int length; + + *pic = NULL; + + if(width) + *width = 0; + if(height) + *height = 0; + + // + // load the file + // + length = ri.FS_ReadFile ( ( char * ) name, (void **)&buffer); + if (!buffer || length < 0) { + return; + } + + if(length < 18) + { + ri.Error( ERR_DROP, "LoadTGA: header too short (%s)\n", name ); + } + + buf_p = buffer; + end = buffer + length; + + 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(!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); + + if (targa_header.id_length != 0) + { + if (buf_p + targa_header.id_length > end) + ri.Error( ERR_DROP, "LoadTGA: header too short (%s)\n", name ); + + buf_p += targa_header.id_length; // skip TARGA image comment + } + + if ( targa_header.image_type==2 || targa_header.image_type == 3 ) + { + if(buf_p + columns*rows*targa_header.pixel_size/8 > end) + { + ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)\n", name); + } + + // 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; ) { + if(buf_p + 1 > end) + ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)\n", name); + packetHeader= *buf_p++; + packetSize = 1 + (packetHeader & 0x7f); + if (packetHeader & 0x80) { // run-length packet + if(buf_p + targa_header.pixel_size/8 > end) + ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)\n", name); + 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 + + if(buf_p + targa_header.pixel_size/8*packetSize > end) + ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)\n", name); + 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 + // 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); + } + + if (width) + *width = columns; + if (height) + *height = rows; + + *pic = targa_rgba; + + ri.FS_FreeFile (buffer); +} diff --git a/code/renderer/tr_image_tga.h b/code/renderer/tr_image_tga.h new file mode 100644 index 0000000..05e9916 --- /dev/null +++ b/code/renderer/tr_image_tga.h @@ -0,0 +1 @@ +void LoadTGA( const char *name, byte **pic, int *width, int *height ); |