/*
 * Wolfenstein: Enemy Territory GPL Source Code
 * Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
 *
 * ET: Legacy
 * Copyright (C) 2012-2024 ET:Legacy team <mail@etlegacy.com>
 *
 * This file is part of ET: Legacy - http://www.etlegacy.com
 *
 * ET: Legacy 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 3 of the License, or
 * (at your option) any later version.
 *
 * ET: Legacy 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 ET: Legacy. If not, see <http://www.gnu.org/licenses/>.
 *
 * In addition, Wolfenstein: Enemy Territory GPL Source Code is also
 * subject to certain additional terms. You should have received a copy
 * of these additional terms immediately following the terms and conditions
 * of the GNU General Public License which accompanied the source code.
 * If not, please request a copy in writing from id Software at the address below.
 *
 * id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
 */
/**
 * @file tr_image_png.c
 * @brief PNG LOADING
 */

#include "tr_common.h"
#include "../qcommon/puff.h"

/**
 * @def INT_MAX
 * @brief We could limit the png size to a lower value here
 */
#ifndef INT_MAX
#define INT_MAX 0x1fffffff
#endif

/**
 * @def Q3IMAGE_BYTESPERPIXEL
 * @brief 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)

/**
 * @struct PNG_ChunkHeader
 * @brief 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)

/**
 * @def MAKE_CHUNKTYPE
 * @brief 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')

/**
 * @struct PNG_Chunk_IHDR
 * @brief 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)

/**
 * @def PNG_CompressionMethod_0
 * @brief Only one valid CompressionMethod is standardized.
 */
#define PNG_CompressionMethod_0 (0)

/**
 * @def PNG_FilterMethod_0
 * @brief 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)

/**
 * @def PNG_Adam7_NumPasses
 * @brief The Adam7 interlace method uses 7 passes.
 */
#define PNG_Adam7_NumPasses (7)

/*
 * @struct PNG_ZlibHeader
 * @brief The compressed data starts with a header ...
 * @note Unused
struct PNG_ZlibHeader
{
    uint8_t CompressionMethod;
    uint8_t Flags;
};
*/

#define PNG_ZlibHeader_Size (2)

/**
 * @def PNG_ZlibCheckValue_Size
 * @brief ... and is followed by a check value
 */
#define PNG_ZlibCheckValue_Size (4)

/*
 *  Some support functions for buffered files follow.
 */

/**
 * @struct BufferedFile
 * @brief Buffered file representation
 */
struct BufferedFile
{
	byte *Buffer;
	int Length;
	byte *Ptr;
	int BytesLeft;
};

/**
 * @brief Read a file into a buffer.
 * @param[in] name
 * @return
 */
static struct BufferedFile *ReadBufferedFile(imageData_t *data)
{
	struct BufferedFile *BF;

	//  input verification
	if (!data)
	{
		return NULL;
	}

	//  Allocate control struct.
	BF = ri.Z_Malloc(sizeof(struct BufferedFile));
	if (!BF)
	{
		return NULL;
	}

	//  Initialize the structs components.
	BF->Length    = 0;
	BF->Buffer    = NULL;
	BF->Ptr       = NULL;
	BF->BytesLeft = 0;

	BF->Length = (int)data->size;
	BF->Buffer = data->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);
}

/**
 * @brief Close a buffered file.
 * @param[in] BF
 */
static void CloseBufferedFile(struct BufferedFile *BF)
{
	if (BF)
	{
		ri.Free(BF);
	}
}

/**
 * @brief Get a pointer to the requested bytes.
 * @param[in,out] BF
 * @param[in] Length
 * @return
 */
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);
}

/**
 * @brief Rewind the buffer.
 * @param[in,out] BF
 * @param[in] Offset
 * @return
 */
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;
}

/**
 * @brief Skip some bytes.
 * @param[in,out] BF
 * @param[in] Offset
 * @return
 */
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;
}

/**
 * @brief Find a chunk
 * @param[in] BF
 * @param[in] ChunkType
 * @return
 */
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;
}

/**
 * @brief Decompress all IDATs
 * @param[in] BF
 * @param[out] Buffer
 * @return
 */
static uint32_t DecompressIDATs(struct BufferedFile *BF, uint8_t **Buffer)
{
	uint8_t *DecompressedData;

	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 0;
	}

	// some zeroing
	DecompressedData = NULL;
	*Buffer          = DecompressedData;

	CompressedData       = NULL;
	CompressedDataLength = 0;

	BytesToRewind = 0;

	// Find the first IDAT chunk.
	if (!FindChunk(BF, PNG_ChunkType_IDAT))
	{
		return 0;
	}

	// 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 0;
		}

		// 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 0;
			}

			BytesToRewind        += Length + PNG_ChunkCRC_Size;
			CompressedDataLength += Length;
		}
	}

	BufferedFileRewind(BF, BytesToRewind);

	CompressedData = ri.Z_Malloc(CompressedDataLength);
	if (!CompressedData)
	{
		return 0;
	}

	CompressedDataPtr = CompressedData;

	// Collect the compressed Data
	while (qtrue)
	{
		// Read chunk header
		CH = BufferedFileRead(BF, PNG_ChunkHeader_Size);
		if (!CH)
		{
			ri.Free(CompressedData);

			return 0;
		}

		// 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 0;
			}

			if (!BufferedFileSkip(BF, PNG_ChunkCRC_Size))
			{
				ri.Free(CompressedData);

				return 0;
			}

			Com_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 0;
	}

	// Allocate the buffer for the uncompressed data.
	DecompressedData = ri.Z_Malloc(puffDestLen);
	if (!DecompressedData)
	{
		ri.Free(CompressedData);

		return 0;
	}

	// 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 0;
	}

	// Set the output of this function.
	*Buffer = DecompressedData;

	return(puffDestLen);
}

/**
 * @brief The Paeth predictor
 * @param[in] a
 * @param[in] b
 * @param[in] c
 * @return
 */
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;

	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);
}

/**
 * @brief Reverse the filters.
 * @param[in,out] DecompressedData
 * @param[in] ImageHeight
 * @param[in] BytesPerScanline
 * @param[in] BytesPerPixel
 * @return
 */
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;
}

/**
 * @brief Convert a raw input pixel to Quake 3 RGA format.
 * @param[in] IHDR
 * @param[out] OutPtr
 * @param[in] DecompPtr
 * @param[in] HasTransparentColour
 * @param[in] TransparentColour
 * @param[in] OutPal
 * @return
 */
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;
}

/**
 * @brief Decode a non-interlaced image.
 * @param[in] IHDR
 * @param[out] OutBuffer
 * @param[in] DecompressedData
 * @param[in] DecompressedDataLength
 * @param[in] HasTransparentColour
 * @param[in] TransparentColour
 * @param[out] OutPal
 * @return
 */
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;
}

/**
 * @brief Decode an interlaced image.
 * @param[in] IHDR
 * @param[out] OutBuffer
 * @param[in] DecompressedData
 * @param[in] DecompressedDataLength
 * @param[in] HasTransparentColour
 * @param[in] TransparentColour
 * @param[out] OutPal
 * @return
 */
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;
}

/**
 * @brief The PNG loader
 * @param[in] name
 * @param[out] pic
 * @param[out] width
 * @param[out] height
 * @param alphaByte is not yet implemented
 */
qboolean R_LoadPNG(imageData_t *data, byte **pic, int *width, int *height, byte alphaByte)
{
	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 (!(data && pic))
	{
		return qfalse;
	}

	// Zero out return values.
	*pic = NULL;

	if (width)
	{
		*width = 0;
	}

	if (height)
	{
		*height = 0;
	}

	// Read the file.
	ThePNG = ReadBufferedFile(data);
	if (!ThePNG)
	{
		return qfalse;
	}

	// Read the siganture of the file.
	Signature = BufferedFileRead(ThePNG, PNG_Signature_Size);
	if (!Signature)
	{
		CloseBufferedFile(ThePNG);

		return qfalse;
	}

	// Is it a PNG?
	if (memcmp(Signature, PNG_Signature, PNG_Signature_Size))
	{
		CloseBufferedFile(ThePNG);

		return qfalse;
	}

	// Read the first chunk-header.
	CH = BufferedFileRead(ThePNG, PNG_ChunkHeader_Size);
	if (!CH)
	{
		CloseBufferedFile(ThePNG);

		return qfalse;
	}

	// 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 qfalse;
	}

	// Read the IHDR.
	IHDR = BufferedFileRead(ThePNG, PNG_Chunk_IHDR_Size);
	if (!IHDR)
	{
		CloseBufferedFile(ThePNG);

		return qfalse;
	}

	// Read the CRC for IHDR
	CRC = BufferedFileRead(ThePNG, PNG_ChunkCRC_Size);
	if (!CRC)
	{
		CloseBufferedFile(ThePNG);

		return qfalse;
	}

	// 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);

		Ren_Print(S_COLOR_YELLOW "%s: invalid image size\n", data->name);

		return qfalse;
	}

	// 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 qfalse;
	}

	// Check if InterlaceMethod is valid.
	if (!((IHDR->InterlaceMethod == PNG_InterlaceMethod_NonInterlaced)  || (IHDR->InterlaceMethod == PNG_InterlaceMethod_Interlaced)))
	{
		CloseBufferedFile(ThePNG);

		return qfalse;
	}

	// 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 qfalse;
		}

		// Read the chunk-header.
		CH = BufferedFileRead(ThePNG, PNG_ChunkHeader_Size);
		if (!CH)
		{
			CloseBufferedFile(ThePNG);

			return qfalse;
		}

		// 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 qfalse;
		}

		// Check if Length is divisible by 3
		if (ChunkHeaderLength % 3)
		{
			CloseBufferedFile(ThePNG);

			return qfalse;
		}

		// Read the raw palette data
		InPal = BufferedFileRead(ThePNG, ChunkHeaderLength);
		if (!InPal)
		{
			CloseBufferedFile(ThePNG);

			return qfalse;
		}

		// Read the CRC for the palette
		CRC = BufferedFileRead(ThePNG, PNG_ChunkCRC_Size);
		if (!CRC)
		{
			CloseBufferedFile(ThePNG);

			return qfalse;
		}

		// 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 qfalse;
		}

		//  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 qfalse;
		}

		//  Read the transparency information.
		Trans = BufferedFileRead(ThePNG, ChunkHeaderLength);
		if (!Trans)
		{
			CloseBufferedFile(ThePNG);

			return qfalse;
		}

		//  Read the CRC.
		CRC = BufferedFileRead(ThePNG, PNG_ChunkCRC_Size);
		if (!CRC)
		{
			CloseBufferedFile(ThePNG);

			return qfalse;
		}

		// Only for Grey, True and Indexed ColourType should tRNS exist.
		switch (IHDR->ColourType)
		{
		case PNG_ColourType_Grey:
		{
			if (ChunkHeaderLength != 2)
			{
				CloseBufferedFile(ThePNG);

				return qfalse;
			}

			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 qfalse;
			}

			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 qfalse;
			}

			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 qfalse;
		}
		}
	}

	// Rewind to the start of the file.
	if (!BufferedFileRewind(ThePNG, -1))
	{
		CloseBufferedFile(ThePNG);

		return qfalse;
	}

	//  Skip the signature
	if (!BufferedFileSkip(ThePNG, PNG_Signature_Size))
	{
		CloseBufferedFile(ThePNG);

		return qfalse;
	}

	//  Decompress all IDAT chunks
	DecompressedDataLength = DecompressIDATs(ThePNG, &DecompressedData);
	if (!(DecompressedDataLength && DecompressedData))
	{
		CloseBufferedFile(ThePNG);

		return qfalse;
	}

	//  Allocate output buffer.
	OutBuffer = R_GetImageBuffer(IHDR_Width * IHDR_Height * Q3IMAGE_BYTESPERPIXEL, BUFFER_IMAGE, data->name);
	if (!OutBuffer)
	{
		ri.Free(DecompressedData);
		CloseBufferedFile(ThePNG);

		return qfalse;
	}

	// 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 qfalse;
		}

		break;
	}

	case PNG_InterlaceMethod_Interlaced:
	{
		if (!DecodeImageInterlaced(IHDR, OutBuffer, DecompressedData, DecompressedDataLength, HasTransparentColour, TransparentColour, OutPal))
		{
			ri.Free(OutBuffer);
			ri.Free(DecompressedData);
			CloseBufferedFile(ThePNG);

			return qfalse;
		}

		break;
	}

	default:
	{
		ri.Free(OutBuffer);
		ri.Free(DecompressedData);
		CloseBufferedFile(ThePNG);

		return qfalse;
	}
	}

	//  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);
	return qtrue;
}

#ifdef FEATURE_PNG
/**
 * @brief Encode a non-interlaced 8-bit true color image
 * @param[in] filename
 * @param[in] width
 * @param[in] height
 * @param[in] data
 * @param[in] padding
 */
static qboolean EncodeImageNonInterlaced8True(uint32_t IHDR_Width, uint32_t IHDR_Height, byte *InBuffer,
                                              uint32_t InBytesPerPixel, uint32_t Padding, uint8_t *ImageData)
{
	uint32_t BytesPerScanline, BytesPerPixel, PixelsPerByte;
	uint32_t w, h;
	byte     *InPtr;
	uint8_t  *OutPtr;

	// input verification
	if (!InBuffer)
	{
		return(qfalse);
	}

	// information for un-filtering
	// PNG_ColourType_True, PNG_BitDepth_8
	BytesPerPixel = PNG_NumColourComponents_True;
	PixelsPerByte = 1;

	// Calculate the size of one scanline
	BytesPerScanline = (IHDR_Width * BytesPerPixel + (PixelsPerByte - 1)) / PixelsPerByte;

	// Set the working pointers to the beginning of the buffers.
	InPtr  = InBuffer;
	OutPtr = ImageData;

	// Create the output image.
	for (h = IHDR_Height; h > 0; h--)
	{
		InPtr = InBuffer + (BytesPerScanline + Padding) * (h - 1);

		// set FilterType
		OutPtr[0] = PNG_FilterType_None;
		OutPtr++;

		for (w = 0; w < (BytesPerScanline / BytesPerPixel); w++)
		{
			OutPtr[0] = InPtr[0];
			OutPtr[1] = InPtr[1];
			OutPtr[2] = InPtr[2];

			InPtr  += InBytesPerPixel;
			OutPtr += BytesPerPixel;
		}
	}

	return(qtrue);
}

/**
 * @brief Write data to buffer
 * @param[in] buffer
 * @param[in] data
 * @param[in] length
 */
void WriteToBuffer(void **buffer, const void *data, size_t length)
{
	memcpy(*buffer, data, length);
	*(byte **)buffer += length;
}

/**
 * @brief Write PNG chuck header to buffer
 * @param[in] buffer
 * @param[in] data
 * @param[in] length
 */
void WriteChunkHeader(void **buffer, void **crcPtr, PNG_ChunkCRC *CRC, int type, int length)
{
	struct PNG_ChunkHeader CH;

	// Write chuck header
	CH.Type   = BigLong(type);
	CH.Length = BigLong(length);

	WriteToBuffer(buffer, &CH, PNG_ChunkHeader_Size);

	// Init CRC
	*CRC    = ri.zlib_crc32(0, Z_NULL, 0);
	*CRC    = ri.zlib_crc32(*CRC, *(byte **)buffer - 4, 4);
	*crcPtr = *buffer;
}

/**
 * @brief Write CRC to buffer
 * @param[in] buffer
 * @param[in] crcPtr
 * @param[in] CRC
 */
void WriteCRC(void **buffer, void **crcPtr, PNG_ChunkCRC CRC)
{
	int size = (intptr_t)*buffer - (intptr_t)*crcPtr;

	// Update CRC
	if (size > 0)
	{
		CRC = ri.zlib_crc32(CRC, *crcPtr, size);
	}

	// Write CRC
	CRC = BigLong(CRC);
	WriteToBuffer(buffer, &CRC, PNG_ChunkCRC_Size);
}

/**
 * @brief The PNG saver
 * @param[in] filename
 * @param[in] width
 * @param[in] height
 * @param[in] data
 * @param[in] padding
 */
void RE_SavePNG(const char *filename, int width, int height, byte *data, int padding)
{
	void                  *pngData;
	size_t                pngSize;
	void                  *buffer;
	struct PNG_Chunk_IHDR IHDR;
	PNG_ChunkCRC          CRC;
	void                  *crcPtr;

	uint8_t  *imageData;
	uint32_t imageLength;

	Bytef  *compressedData = NULL;
	uLongf compressedDataLength;

	// Create the png image data from buffer
	imageLength = (width * PNG_NumColourComponents_True + 1) * height;
	imageData   = ri.Hunk_AllocateTempMemory(imageLength);

	EncodeImageNonInterlaced8True(width, height, data, 3, padding, imageData);

	// Compress the png image data
	compressedDataLength = imageLength * 1.01f + 12;
	compressedData       = ri.Hunk_AllocateTempMemory(compressedDataLength);

	if (ri.zlib_compress(compressedData, &compressedDataLength, imageData, imageLength) != Z_OK)
	{
		ri.Hunk_FreeTempMemory(compressedData);
		ri.Hunk_FreeTempMemory(imageData);
		Ren_Warning("RE_SavePNG: Failed to compress image data.\n");
		return;
	}

	// Calculate the size of the image
	pngSize = PNG_Signature_Size + PNG_ChunkHeader_Size + PNG_Chunk_IHDR_Size + PNG_ChunkCRC_Size;

	pngSize += PNG_ChunkHeader_Size + compressedDataLength + PNG_ChunkCRC_Size
	           + PNG_ChunkHeader_Size + PNG_ChunkCRC_Size;

	// Allocate memory to hold the full png image data
	buffer = pngData = ri.Hunk_AllocateTempMemory(pngSize);

	// Setup CRC
	CRC    = ri.zlib_crc32(0, Z_NULL, 0);
	crcPtr = (byte *)buffer + PNG_Signature_Size + 4;

	// Header
	WriteToBuffer(&buffer, PNG_Signature, PNG_Signature_Size);

	WriteChunkHeader(&buffer, &crcPtr, &CRC, PNG_ChunkType_IHDR, PNG_Chunk_IHDR_Size);
	IHDR.Width             = BigLong(width);
	IHDR.Height            = BigLong(height);
	IHDR.BitDepth          = PNG_BitDepth_8;
	IHDR.ColourType        = PNG_ColourType_True;
	IHDR.CompressionMethod = PNG_CompressionMethod_0;
	IHDR.FilterMethod      = PNG_FilterMethod_0;
	IHDR.InterlaceMethod   = PNG_InterlaceMethod_NonInterlaced;
	WriteToBuffer(&buffer, &IHDR, PNG_Chunk_IHDR_Size);
	WriteCRC(&buffer, &crcPtr, CRC);

	// IDAT, Image Data
	WriteChunkHeader(&buffer, &crcPtr, &CRC, PNG_ChunkType_IDAT, compressedDataLength);
	WriteToBuffer(&buffer, compressedData, compressedDataLength);
	WriteCRC(&buffer, &crcPtr, CRC);

	// IEND, Image End
	WriteChunkHeader(&buffer, &crcPtr, &CRC, PNG_ChunkType_IEND, 0);
	WriteCRC(&buffer, &crcPtr, CRC);

	// Write the image to file
	ri.FS_WriteFile(filename, pngData, pngSize);

	// Free memory
	ri.Hunk_FreeTempMemory(pngData);
	ri.Hunk_FreeTempMemory(compressedData);
	ri.Hunk_FreeTempMemory(imageData);
}

#endif
