/* =========================================================================== 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" // we could limit the png size to a lower value here #ifndef INT_MAX #define INT_MAX 0x1fffffff #endif /* ================= 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; union { byte *b; void *v; } buffer; /* * 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, &buffer.v); BF->Buffer = buffer.b; /* * 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, unsigned 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, unsigned Offset) { unsigned BytesRead; /* * input verification */ if(!BF) { return(qfalse); } /* * special trick to rewind to the beginning of the buffer */ if(Offset == (unsigned)-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, unsigned 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 */ if(!(DecompressedData && BytesPerPixel)) { return(qfalse); } /* * ImageHeight and BytesPerScanline can be zero in small interlaced images. */ if((!ImageHeight) || (!BytesPerScanline)) { return(qtrue); } /* * 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[2])) { 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 * but only when the pass has a width bigger than zero */ if(BytesPerScanline[a]) { 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 R_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)) || IHDR_Width > INT_MAX / Q3IMAGE_BYTESPERPIXEL / IHDR_Height) { CloseBufferedFile(ThePNG); Com_Printf(S_COLOR_YELLOW "%s: invalid image size\n", name); 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); }