diff --git a/Makefile b/Makefile index 192d987e..e42a14e4 100644 --- a/Makefile +++ b/Makefile @@ -1174,6 +1174,7 @@ Q3OBJ = \ $(B)/client/tr_flares.o \ $(B)/client/tr_font.o \ $(B)/client/tr_image.o \ + $(B)/client/tr_image_png.o \ $(B)/client/tr_init.o \ $(B)/client/tr_light.o \ $(B)/client/tr_main.o \ diff --git a/code/renderer/tr_image.c b/code/renderer/tr_image.c index 37b91c22..58122287 100644 --- a/code/renderer/tr_image.c +++ b/code/renderer/tr_image.c @@ -33,13 +33,11 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define JPEG_INTERNALS #include "../jpeg-6/jpeglib.h" -#include "../qcommon/puff.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 ); -static void LoadPNG( const char *name, byte **pic, int *width, int *height ); static byte s_intensitytable[256]; static unsigned char s_gammatable[256]; @@ -1931,2450 +1929,6 @@ int SaveJPGToBuffer( byte *buffer, int quality, //=================================================================== -/* -================= -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); -} - -//=================================================================== - typedef struct { char *ext; diff --git a/code/renderer/tr_image_png.c b/code/renderer/tr_image_png.c new file mode 100644 index 00000000..30a89516 --- /dev/null +++ b/code/renderer/tr_image_png.c @@ -0,0 +1,2466 @@ +/* +=========================================================================== +ioquake3 png decoder +Copyright (C) 2007,2008 Joerg Dietrich + +This program 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. + +This program 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 this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +=========================================================================== +*/ + +#include "tr_local.h" + +#include "../qcommon/puff.h" + +/* +================= +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 + */ + +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); +} diff --git a/code/renderer/tr_image_png.h b/code/renderer/tr_image_png.h new file mode 100644 index 00000000..a313e680 --- /dev/null +++ b/code/renderer/tr_image_png.h @@ -0,0 +1 @@ +void LoadPNG( const char *name, byte **pic, int *width, int *height );