/* ------------------------------------------------------------------------------- Copyright (C) 1999-2007 id Software, Inc. and contributors. For a list of contributors, see the accompanying CONTRIBUTORS file. This file is part of GtkRadiant. GtkRadiant 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. GtkRadiant 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 GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ---------------------------------------------------------------------------------- This code has been altered significantly from its original form, to support several games based on the Quake III Arena engine, in the form of "Q3Map2." ------------------------------------------------------------------------------- */ /* marker */ #define IMAGE_C /* dependencies */ #include "q3map2.h" /* ------------------------------------------------------------------------------- this file contains image pool management with reference counting. note: it isn't reentrant, so only call it from init/shutdown code or wrap calls in a mutex ------------------------------------------------------------------------------- */ /* LoadDDSBuffer() loads a dxtc (1, 3, 5) dds buffer into a valid rgba image */ static void LoadDDSBuffer( byte *buffer, int size, byte **pixels, int *width, int *height ) { int w, h; ddsPF_t pf; /* dummy check */ if( buffer == NULL || size <= 0 || pixels == NULL || width == NULL || height == NULL ) return; /* null out */ *pixels = 0; *width = 0; *height = 0; /* get dds info */ if( DDSGetInfo( (ddsBuffer_t*) buffer, &w, &h, &pf ) ) { Sys_Printf( "WARNING: Invalid DDS texture\n" ); return; } /* only certain types of dds textures are supported */ if( pf != DDS_PF_ARGB8888 && pf != DDS_PF_DXT1 && pf != DDS_PF_DXT3 && pf != DDS_PF_DXT5 ) { Sys_Printf( "WARNING: Only DDS texture formats ARGB8888, DXT1, DXT3, and DXT5 are supported (%d)\n", pf ); return; } /* create image pixel buffer */ *width = w; *height = h; *pixels = safe_malloc( w * h * 4 ); /* decompress the dds texture */ DDSDecompress( (ddsBuffer_t*) buffer, *pixels ); } /* PNGReadData() callback function for libpng to read from a memory buffer note: this function is a total hack, as it reads/writes the png struct directly! */ typedef struct pngBuffer_s { byte *buffer; png_size_t size, offset; } pngBuffer_t; void PNGReadData( png_struct *png, png_byte *buffer, png_size_t size ) { pngBuffer_t *pb = (pngBuffer_t*) png_get_io_ptr( png ); if( (pb->offset + size) > pb->size ) size = (pb->size - pb->offset); memcpy( buffer, &pb->buffer[ pb->offset ], size ); pb->offset += size; //% Sys_Printf( "Copying %d bytes from 0x%08X to 0x%08X (offset: %d of %d)\n", size, &pb->buffer[ pb->offset ], buffer, pb->offset, pb->size ); } /* LoadPNGBuffer() loads a png file buffer into a valid rgba image */ static void LoadPNGBuffer( byte *buffer, int size, byte **pixels, int *width, int *height ) { png_struct *png; png_info *info, *end; pngBuffer_t pb; int bitDepth, colorType; png_uint_32 w, h, i; byte **rowPointers; /* dummy check */ if( buffer == NULL || size <= 0 || pixels == NULL || width == NULL || height == NULL ) return; /* null out */ *pixels = 0; *width = 0; *height = 0; /* determine if this is a png file */ if( png_sig_cmp( buffer, 0, 8 ) != 0 ) { Sys_Printf( "WARNING: Invalid PNG file\n" ); return; } /* create png structs */ png = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL ); if( png == NULL ) { Sys_Printf( "WARNING: Unable to create PNG read struct\n" ); return; } info = png_create_info_struct( png ); if( info == NULL ) { Sys_Printf( "WARNING: Unable to create PNG info struct\n" ); png_destroy_read_struct( &png, NULL, NULL ); return; } end = png_create_info_struct( png ); if( end == NULL ) { Sys_Printf( "WARNING: Unable to create PNG end info struct\n" ); png_destroy_read_struct( &png, &info, NULL ); return; } /* set read callback */ pb.buffer = buffer; pb.size = size; pb.offset = 0; png_set_read_fn( png, &pb, PNGReadData ); png->io_ptr = &pb; /* hack! */ /* set error longjmp */ if( setjmp( png->jmpbuf ) ) { Sys_Printf( "WARNING: An error occurred reading PNG image\n" ); png_destroy_read_struct( &png, &info, &end ); return; } /* fixme: add proper i/o stuff here */ /* read png info */ png_read_info( png, info ); /* read image header chunk */ png_get_IHDR( png, info, &w, &h, &bitDepth, &colorType, NULL, NULL, NULL ); /* the following will probably bork on certain types of png images, but hey... */ /* force indexed/gray/trans chunk to rgb */ if( (colorType == PNG_COLOR_TYPE_PALETTE && bitDepth <= 8) || (colorType == PNG_COLOR_TYPE_GRAY && bitDepth <= 8) || png_get_valid( png, info, PNG_INFO_tRNS ) ) png_set_expand( png ); /* strip 16bpc -> 8bpc */ if( bitDepth == 16 ) png_set_strip_16( png ); /* pad rgb to rgba */ if( bitDepth == 8 && colorType == PNG_COLOR_TYPE_RGB) png_set_filler( png, 255, PNG_FILLER_AFTER ); /* create image pixel buffer */ *width = w; *height = h; *pixels = safe_malloc( w * h * 4 ); /* create row pointers */ rowPointers = safe_malloc( h * sizeof( byte* ) ); for( i = 0; i < h; i++ ) rowPointers[ i ] = *pixels + (i * w * 4); /* read the png */ png_read_image( png, rowPointers ); /* clean up */ free( rowPointers ); png_destroy_read_struct( &png, &info, &end ); } /* ImageInit() implicitly called by every function to set up image list */ static void ImageInit( void ) { int i; if( numImages <= 0 ) { /* clear images (fixme: this could theoretically leak) */ memset( images, 0, sizeof( images ) ); /* generate *bogus image */ images[ 0 ].name = safe_malloc( strlen( DEFAULT_IMAGE ) + 1 ); strcpy( images[ 0 ].name, DEFAULT_IMAGE ); images[ 0 ].filename = safe_malloc( strlen( DEFAULT_IMAGE ) + 1 ); strcpy( images[ 0 ].filename, DEFAULT_IMAGE ); images[ 0 ].width = 64; images[ 0 ].height = 64; images[ 0 ].refCount = 1; images[ 0 ].pixels = safe_malloc( 64 * 64 * 4 ); for( i = 0; i < (64 * 64 * 4); i++ ) images[ 0 ].pixels[ i ] = 255; } } /* ImageFree() frees an rgba image */ void ImageFree( image_t *image ) { /* dummy check */ if( image == NULL ) return; /* decrement refcount */ image->refCount--; /* free? */ if( image->refCount <= 0 ) { if( image->name != NULL ) free( image->name ); image->name = NULL; if( image->filename != NULL ) free( image->filename ); image->filename = NULL; free( image->pixels ); image->width = 0; image->height = 0; numImages--; } } /* ImageFind() finds an existing rgba image and returns a pointer to the image_t struct or NULL if not found */ image_t *ImageFind( const char *filename ) { int i; char name[ 1024 ]; /* init */ ImageInit(); /* dummy check */ if( filename == NULL || filename[ 0 ] == '\0' ) return NULL; /* strip file extension off name */ strcpy( name, filename ); StripExtension( name ); /* search list */ for( i = 0; i < MAX_IMAGES; i++ ) { if( images[ i ].name != NULL && !strcmp( name, images[ i ].name ) ) return &images[ i ]; } /* no matching image found */ return NULL; } /* ImageLoad() loads an rgba image and returns a pointer to the image_t struct or NULL if not found */ image_t *ImageLoad( const char *filename ) { int i; image_t *image; char name[ 1024 ]; int size; byte *buffer = NULL; qboolean alphaHack = qfalse; /* init */ ImageInit(); /* dummy check */ if( filename == NULL || filename[ 0 ] == '\0' ) return NULL; /* strip file extension off name */ strcpy( name, filename ); StripExtension( name ); /* try to find existing image */ image = ImageFind( name ); if( image != NULL ) { image->refCount++; return image; } /* none found, so find first non-null image */ image = NULL; for( i = 0; i < MAX_IMAGES; i++ ) { if( images[ i ].name == NULL ) { image = &images[ i ]; break; } } /* too many images? */ if( image == NULL ) Error( "MAX_IMAGES (%d) exceeded, there are too many image files referenced by the map.", MAX_IMAGES ); /* set it up */ image->name = safe_malloc( strlen( name ) + 1 ); strcpy( image->name, name ); /* attempt to load tga */ StripExtension( name ); strcat( name, ".tga" ); size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 ); if( size > 0 ) LoadTGABuffer( buffer, buffer + size, &image->pixels, &image->width, &image->height ); else { /* attempt to load png */ StripExtension( name ); strcat( name, ".png" ); size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 ); if( size > 0 ) LoadPNGBuffer( buffer, size, &image->pixels, &image->width, &image->height ); else { /* attempt to load jpg */ StripExtension( name ); strcat( name, ".jpg" ); size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 ); if( size > 0 ) { if( LoadJPGBuff( buffer, size, &image->pixels, &image->width, &image->height ) == -1 && image->pixels != NULL ) Sys_Printf( "WARNING: LoadJPGBuff: %s\n", (unsigned char*) image->pixels ); alphaHack = qtrue; } else { /* attempt to load dds */ StripExtension( name ); strcat( name, ".dds" ); size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 ); if( size > 0 ) { LoadDDSBuffer( buffer, size, &image->pixels, &image->width, &image->height ); /* debug code */ #if 1 { ddsPF_t pf; DDSGetInfo( (ddsBuffer_t*) buffer, NULL, NULL, &pf ); Sys_Printf( "pf = %d\n", pf ); if( image->width > 0 ) { StripExtension( name ); strcat( name, "_converted.tga" ); WriteTGA( "C:\\games\\quake3\\baseq3\\textures\\rad\\dds_converted.tga", image->pixels, image->width, image->height ); } } #endif } } } } /* free file buffer */ free( buffer ); /* make sure everything's kosher */ if( size <= 0 || image->width <= 0 || image->height <= 0 || image->pixels == NULL ) { //% Sys_Printf( "size = %d width = %d height = %d pixels = 0x%08x (%s)\n", //% size, image->width, image->height, image->pixels, name ); free( image->name ); image->name = NULL; return NULL; } /* set filename */ image->filename = safe_malloc( strlen( name ) + 1 ); strcpy( image->filename, name ); /* set count */ image->refCount = 1; numImages++; if(alphaHack) { StripExtension( name ); strcat( name, "_alpha.jpg" ); size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 ); if( size > 0 ) { unsigned char *pixels; int width, height; if( LoadJPGBuff( buffer, size, &pixels, &width, &height ) == -1 && pixels != NULL ) Sys_Printf( "WARNING: LoadJPGBuff: %s\n", (unsigned char*) image->pixels ); if(pixels && width == image->width && height == image->height) { int i; for(i = 0; i < width*height; ++i) image->pixels[4*i+3] = pixels[4*i+2]; // copy alpha from blue channel } free(pixels); free(buffer); } } /* return the image */ return image; }