///////////////////////////////////////////////////////////////////////////////
/// @file deBitmapLoad.cpp
///
/// @brief bitmap loading code
///
/// @author Assassin, Hootie, paradoxnj
///
/// This file is the intellectual property of Novus Delta, LLC.. Usage of the
/// contents of this file is subject to the Destiny3D Member License which
/// can be found at http://www.destiny3d.com.  Any other usage is prohibited.
///
/// This file is distributed "AS IS" without warranty of any kind.  Novus
/// Delta, LLC. does not guarantee the fitness of the contents of this file
/// for any particular purpose.
///
/// Copyright (C) 2001-2003 Novus Delta, LLC. All Rights Reserved.
///
/// <hr>
///                                 Change History
/// <hr>
///
/// @date Jan 2002
/// @author Assassin
/// @remarks Creation
///
///////////////////////////////////////////////////////////////////////////////

#include "deFileSystem.hpp"
#include "deBitmap_priv.hpp"
#include "deByteOrder.hpp"
#include "deFunctors.hpp"

#pragma todo(work out byte ordering for various things in here)
#pragma todo(decide whether memory should reflect file or register representation)

#include "libpng/png.h"

// some values and structs included here for loading files
#define deDDPF_RGB					0x00000040l
#define deDDPF_ALPHAPIXELS			0x00000001l
#define deDDSD_MIPMAPCOUNT			0x00020000l
#define deDDSD_LINEARSIZE			0x00080000l
#define deMAKEFOURCC(ch0, ch1, ch2, ch3)                            \
            ((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) |       \
            ((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24 ))
#pragma pack (push, 2)
struct deBitmapFileHeader
{
	u16		bfType;
	u32		bfSize;
	u16		bfReserved1;
	u16		bfReserved2;
	u32		bfOffBits;
};
struct deBitmapInfoHeader
{
	u32     biSize;
	s32     biWidth;
	s32     biHeight;
	u16     biPlanes;
	u16     biBitCount;
	u32     biCompression;
	u32     biSizeImage;
	s32     biXPelsPerMeter;
	s32     biYPelsPerMeter;
	u32     biClrUsed;
	u32     biClrImportant;
};
struct deDDPixelFormat
{
    u32		dwSize;			// size of structure
    u32		dwFlags;		// pixel format flags
    u32		dwFourCC;		// (FOURCC code)
    union
    {
		u32	dwRGBBitCount;		// how many bits per pixel
//		u32	dwYUVBitCount;		// how many bits per pixel
//		u32	dwZBufferBitDepth;	// how many bits for z buffers
//		u32	dwAlphaBitDepth;	// how many bits for alpha channels
    };
    union
    {
		u32	dwRBitMask;		// mask for red bit
//		u32	dwYBitMask;		// mask for Y bits
    };
    union
    {
		u32	dwGBitMask;		// mask for green bits
//		u32	dwUBitMask;		// mask for U bits
    };
    union
    {
		u32	dwBBitMask;		// mask for blue bits
//		u32	dwVBitMask;		// mask for V bits
    };
    union
    {
		u32	dwRGBAlphaBitMask;	// mask for alpha channel
//		u32	dwYUVAlphaBitMask;	// mask for alpha channel
//		u32	dwRGBZBitMask;		// mask for Z channel
//		u32	dwYUVZBitMask;		// mask for Z channel
    };
};
struct deDDSurfaceDesc2
{
	u32 dwSize; //Size of structure. This member must be set to 124. 
	u32 dwFlags; //Flags to indicate valid fields. Always include DDSD_CAPS, DDSD_PIXELFORMAT, DDSD_WIDTH, DDSD_HEIGHT. 
	u32 dwHeight; //Height of the main image in pixels 
	u32 dwWidth; //Width of the main image in pixels 
	u32 dwPitchOrLinearSize; //For uncompressed formats, this is the number of bytes per scan line (DWORD> aligned) for the main image. dwFlags should include DDSD_PITCH in this case. For compressed formats, this is the total number of bytes for the main image. dwFlags should be include DDSD_LINEARSIZE in this case. 
	u32 dwDepth; //For volume textures, this is the depth of the volume. dwFlags should include DDSD_DEPTH in this case. 
	u32 dwMipMapCount; //For items with mipmap levels, this is the total number of levels in the mipmap chain of the main image. dwFlags should include DDSD_MIPMAPCOUNT in this case. 
	u32 dwReserved1[11]; 
	deDDPixelFormat ddpfPixelFormat; //32-byte value that specifies the pixel format structure. 
	u32 ddsCaps[4]; //16-byte value that specifies the capabilities structure. 
	u32 dwReserved2; 
};
#pragma pack (pop)

#pragma pack (push, 1)
struct deTGAHeader
{
	u8	IDFieldLength;
	u8	ColorMapType;
	u8	ImageType;
	u16	CMapOrigin;
	u16	CMapLength;
	u8	CMapEntrySize;
	u16	OriginX;
	u16	OriginY;
	u16	Width;
	u16	Height;
	u8	BitDepth;
	u8	ImageDesc;
};
struct PCXHEADER
{
	char Manufacturer;	// Always 10 -- 
	char Version; // Version information
				 // 0 = Ver2.5, 2 = Ver2.8 with palette, 3 = Ver2.8 without palette,
				// 4 = PC Paintbrush for Windows, 5 = Ver 3.0
	
	char Encoding; // 1 = Run Length Encoding
	
	char BitsPerPixel; // Number of bits that represent a pixel
	
	short xMin, yMin, xMax, yMax; // Image Dimensions
	
	short HorDPI; // Horizontal Resolution of pixels in dots per inch
	short VerDPI; // Vertical Resolution of pixels in dots per inch
	
	char Colormap[48]; // Color palette setting
	
	char Reserved1; // Should be set to zero
	char nPlanes; // Number of color planes
	short BytesPerLine; // Number of bytes to allocate per scanline
						// Must be an even number
	
	short PaletteInfo; // How to interpet palette

	short HorScreenSize; // Horizontal screen size in pixels
	short VerScreenSize; // Vertical screen size in pixels

	char Filler[54]; // An addtional 54 bytes that must be set to 0 -- 
					 // Total size of a .pcx header is 128 bytes
};
#pragma pack (pop)

deBoolean deBitmap::LoadBMPFile(IdeFile *file)
{
	deBitmapFileHeader	bfh;
	deBitmapInfoHeader	bih;
	unsigned char		*data;
	deARGB				*Palette;

	DE_ASSERT(sizeof(deBitmapFileHeader) == 14 && sizeof(deBitmapInfoHeader) == 40);

	if (!file)
		return deFALSE;

	file->Read((void*)&bfh, sizeof(deBitmapFileHeader));
	if (deByteOrderLE16(bfh.bfType) != 0x4D42) // 'BM'
	{
		return deFALSE;
	}

	file->Read((void*)&bih, sizeof(deBitmapInfoHeader));
	this->SetWidth(deByteOrderLE32(bih.biWidth));
	this->SetHeight(abs(deByteOrderLE32(bih.biHeight)));
	this->SetBitDepth(deByteOrderLE16(bih.biBitCount));
	//size gets set upon updating the width/height/bitdepth
	//this->SetImageSize(bih.biWidth*bih.biHeight*bih.biBitCount/8);

	if (this->GetBitDepth() == 8)
	{
		DWORD DataSize = 4*(256);
		Palette=(deARGB*)malloc(DataSize);
		file->Read((void*)Palette, DataSize);
		if (DE3D_CPU_BIG_ENDIAN)
		{
			for (DataSize = 0; DataSize < 256; ++DataSize)
			{
				// convert from the little-endian file data
				Palette[DataSize] = deByteOrderLE32(Palette[DataSize]);
			}
		}
	}
	else
		Palette = NULL;

	file->SetPosition(deByteOrderLE32(bfh.bfOffBits), deFile_Seek_Start);
	data = new unsigned char[m_ImageSize];
	if (!data)
	{
		return deFALSE;
	}

	{
		int n;
		int BytesPerRow = m_Width*m_ByteDepth;
		if (BytesPerRow & 0x0003)
		{
			BytesPerRow |= 0x0003;
			BytesPerRow++;
		}
		BYTE* Linebuffer = new BYTE[BytesPerRow];
		
		if (deByteOrderLE32(bih.biHeight) > 0)
		{
			for (n = m_Height-1; n >= 0; n--)
			{
				file->Read(Linebuffer,BytesPerRow);
				memcpy(data+m_Width*m_ByteDepth*n, Linebuffer, m_Width*m_ByteDepth);
			}
		}
		else
		{
/*
			for (n=0; n < m_Height; ++n)
			{
				file->Read(Linebuffer,BytesPerRow);
				memcpy(data+m_Width*m_ByteDepth*n, Linebuffer, m_Width*m_ByteDepth);
			}
*/
			file->Read(data,m_ImageSize);
		}
		
		delete[] Linebuffer;
	}
//=================
	if (this->GetBitDepth() == 32)
	{
		DWORD			MaxSize = this->GetImageSize() + this->GetImageSize()/3;
		DWORD			Temp;
		unsigned char*	Buffer;
		DWORD *			NewBuffer;
		Buffer = new unsigned char[MaxSize];
		NewBuffer = (DWORD *)Buffer;
		for(Temp = 0; Temp < this->GetImageSize(); Temp+=4) 
		{
			if (DE3D_CPU_LITTLE_ENDIAN)
				*NewBuffer = ((DWORD)(data[Temp]) <<  0) | ((DWORD)(data[Temp+1]) <<  8) | ((DWORD)(data[Temp+2]) << 16) | ((DWORD)(data[Temp+3]) << 24); 
			else
				*NewBuffer = ((DWORD)(data[Temp]) << 24) | ((DWORD)(data[Temp+1]) << 16) | ((DWORD)(data[Temp+2]) <<  8) | ((DWORD)(data[Temp+3]) <<  0); 
			NewBuffer++; 
		}
		delete[] data;
		this->SetFormat(IdeDriver::BPP_X8R8G8B8);
		this->SetBitDepth(32);
		this->SetImageSize(MaxSize);
		this->SetData(Buffer, 0, 0);
	}
	else if (this->GetBitDepth() == 24)
	{
		DWORD			MaxSize = this->GetImageSize() + this->GetImageSize()/3;
		DWORD			Temp;
		unsigned char*	Buffer;
		DWORD *			NewBuffer;
		Buffer = new unsigned char[MaxSize];
		NewBuffer = (DWORD *)Buffer;
		for(Temp = 0; Temp < this->GetImageSize(); Temp+=3) 
		{
			if (DE3D_CPU_LITTLE_ENDIAN)
				*NewBuffer = 0xFF000000 | ((DWORD)(data[Temp]) <<  0) | ((DWORD)(data[Temp+1]) << 8) | ((DWORD)(data[Temp+2]) << 16); 
			else
				*NewBuffer = 0xFF000000 | ((DWORD)(data[Temp]) << 16) | ((DWORD)(data[Temp+1]) << 8) | ((DWORD)(data[Temp+2]) <<  0); 
			NewBuffer++; 
		}
		delete[] data;
		this->SetFormat(IdeDriver::BPP_X8R8G8B8);
		this->SetBitDepth(32);
		this->SetImageSize(MaxSize);
		this->SetData(Buffer, 0, 0);
	}
	else if(this->GetBitDepth() == 8)
	{
		DWORD			MaxSize = this->GetWidth()*this->GetHeight()*4;
		DWORD			Temp;
		unsigned char*	Buffer;
		DWORD *			NewBuffer;
		deARGB			CurrentColor;
		Buffer = new unsigned char[MaxSize];
		NewBuffer = (DWORD *)Buffer;
		for(Temp = 0; Temp < this->GetImageSize(); Temp++) 
		{
			CurrentColor = Palette[data[Temp]]; 
			*NewBuffer = 0xFF000000 | (CurrentColor & 0x00FFFFFF);
			NewBuffer++; 
		}
		delete[] data;
		this->SetFormat(IdeDriver::BPP_X8R8G8B8);
		this->SetBitDepth(32);
		this->SetImageSize(MaxSize);
		this->SetData(Buffer, 0, 0);
	}
//=================

	if (Palette)
		free(Palette);

	return deTRUE;
}

#pragma todo(make TGA loading work better - more formats such as RLE compression)
deBoolean deBitmap::LoadTGAFile(IdeFile *file)
{
	unsigned char	*file_data;
	long			image_size;
	int				byte_depth;
	int				alpha_bits;
	int				i;

	DE_ASSERT(sizeof(deTGAHeader) == 18);

	if (!file)
		return deFALSE;

	deTGAHeader FileHeader;
	if (sizeof(deTGAHeader) != file->Read(&FileHeader, sizeof(deTGAHeader)))
		return deFALSE;
	// convert from little-endian
	FileHeader.Width = deByteOrderLE16(FileHeader.Width);
	FileHeader.Height = deByteOrderLE16(FileHeader.Height);
	FileHeader.OriginX = deByteOrderLE16(FileHeader.OriginX);
	FileHeader.OriginY = deByteOrderLE16(FileHeader.OriginY);
	FileHeader.CMapOrigin = deByteOrderLE16(FileHeader.CMapOrigin);
	FileHeader.CMapLength = deByteOrderLE16(FileHeader.CMapLength);

//#pragma todo (Change file reading code to read a TGA Structure so only 1 read for header info)
	/*
	//2 bytes of garbage data
	file->Read((void*)&IDFieldLength, sizeof(unsigned char));
	file->Read((void*)&ColorMapType, sizeof(unsigned char));

	//Image Type Code.  Should be either 2 or 3 for what we need.
	file->Read((void*)&inchar, sizeof(unsigned char));
	if (inchar != 2 && inchar != 3)
	{
		return deFALSE;
	}

	//9 bytes of data we don't need
	file->Read((void*)&inlong, sizeof(long int));		// color map origin and length
	file->Read((void*)&inchar, sizeof(unsigned char));	// color map entry size
	file->Read((void*)&inlong, sizeof(long int));		// XY Origin - ignored

	//Image dimensions
	file->Read((void*)&inlong, sizeof(short int));
	this->SetWidth((int)inlong);
	
	file->Read((void*)&inlong, sizeof(short int));
	this->SetHeight((int)inlong);

	//Bit depth
	file->Read((void*)&inchar, sizeof(unsigned char));
	this->SetBitDepth((int)inchar);

	//1 byte of data that we don't need
	file->Read((void*)&inchar, sizeof(unsigned char));
	alpha_bits = (inchar & 0x07);
	*/

	this->SetBitDepth(FileHeader.BitDepth);
	this->SetWidth(FileHeader.Width);
	this->SetHeight(FileHeader.Height);
	alpha_bits = (FileHeader.ImageDesc & 0x07);

	// skip over the (often omitted) ID field
	if (FileHeader.IDFieldLength > 0)
		file->SetPosition(FileHeader.IDFieldLength, deFile_Seek_Current);

	//Calculate the image size
	byte_depth = this->GetByteDepth();
	image_size = this->GetWidth() * this->GetHeight() * byte_depth;

	this->SetImageSize(image_size);

	//Read in the image data
	file_data = new unsigned char[image_size];
	if (FileHeader.ImageDesc & 0x20)
	{
		file->Read(file_data, image_size);
	}
	else
	{
		// upside-down
		int BytesPerRow = FileHeader.Width * byte_depth;
		for (i = FileHeader.Height-1; i >= 0; --i)
		{
			file->Read(file_data+i*BytesPerRow, BytesPerRow);
		}
	}

	if (byte_depth == 1)
	{
#pragma todo (test 8-bit TGA)
		// no need to convert the data :)
		this->SetFormat(IdeDriver::BPP_L8);
	}
	else if (byte_depth == 2)
	{
#pragma todo (test 16-bit TGA)
		if (DE3D_CPU_BIG_ENDIAN)
		{
			char ch;
			for (i = 0; i < image_size; i += 2)
			{
				ch = file_data[i+1];
				file_data[i+1] = file_data[i];
				file_data[i] = ch;
			}
		}
		if (alpha_bits)
			this->SetFormat(IdeDriver::BPP_A1R5G5B5);
		else
			this->SetFormat(IdeDriver::BPP_X1R5G5B5);
	}
	else if (byte_depth == 3)
	{
#pragma todo (test 24-bit TGA)
		// convert to 32-bit, since no video cards support 24-bit
		this->SetImageSize(m_Width * m_Height * 4);
		DWORD	*newbuf = new DWORD[m_Width * m_Height];
		DWORD	*pLongData = newbuf;
		BYTE	*pData;
		pData = file_data;
		image_size /= 3;
		for (i = 0; i < image_size; i++)
		{
			if (DE3D_CPU_LITTLE_ENDIAN)
				*pLongData = (0xff000000) | (pData[2] << 16) | (pData[1] << 8) | (pData[0]);
			else
				*pLongData = (0xff000000) | (pData[0] << 16) | (pData[1] << 8) | (pData[2]);
			pLongData++;
			pData += 3;
		}
		delete[] file_data;
		file_data = (BYTE*)newbuf;
		this->SetFormat(IdeDriver::BPP_X8R8G8B8);
	}
	else if (byte_depth == 4)
	{
#pragma todo (test 32-bit TGA)
		if (DE3D_CPU_BIG_ENDIAN)
		{
			u32* pData = (u32*)file_data;
			for (i = 0; i < image_size/4; ++i)
			{
				*pData = deByteOrderLE32(*pData);
			}
		}
		//if (alpha_bits)
			this->SetFormat(IdeDriver::BPP_A8R8G8B8);
		//else
		//	this->SetFormat(IdeDriver::BPP_X8R8G8B8);
	}

	this->SetData(file_data, 0, 0);

	return deTRUE;
}

deBoolean deBitmap::LoadPCXFile(IdeFile *file)
{
	struct RGBTRI { BYTE rgbRed,rgbGreen,rgbBlue; };
	BYTE			c;
	RGBTRI			Palette[256];
	PCXHEADER		header;
	int				total_bytes;

	if (!file)
		return deFALSE;

	//Read in the header
	file->Read((void*)&header, sizeof(PCXHEADER));
	
	//Some sanity checks
	if ((int)header.Manufacturer != 10)
	{
		return deFALSE;
	}

	this->SetWidth(header.xMax - header.xMin + 1);
	this->SetHeight(header.yMax - header.yMin + 1);
	this->SetBitDepth(header.nPlanes * 8);
	header.BitsPerPixel = (char)(header.BitsPerPixel * header.nPlanes);
	//this->SetImageSize(this->GetHeight() * this->GetWidth());

	int					i, j, k;

	total_bytes = (int)header.nPlanes * header.BytesPerLine;
	BYTE *pixelData = new BYTE[this->GetImageSize()];

	file->SetPosition(128, deFile_Seek_Start);
	BYTE *scanLine = new BYTE[total_bytes];
	for (i = 0; i < this->GetHeight(); i++)
	{
		BYTE			byte_value, byte_count;

		for (j = 0; j < total_bytes; j++)
		{
			file->Read((void*)&c, sizeof(BYTE));

			//The top 2 bits are set
			if ((c & 0xC0) == 0xC0)
			{
				byte_count = (unsigned char)(c & 0x3F);

				file->Read((void*)&byte_value, sizeof(BYTE));
			}
			else
			{
				byte_count = 1;
				byte_value = c;
			}

			k = 0;
			do
			{
				scanLine[j] = byte_value;
				k++; j++;
			}
			while(k < byte_count && j < total_bytes);
			j--;
			//for (k = 0; k < byte_count && j < total_bytes; k++, j++)
		}
		if (header.nPlanes == 1)
			memcpy(&(pixelData[header.BytesPerLine*i]), &(scanLine[0]), header.BytesPerLine);
		else
		{
			int index = i * this->GetWidth() * 3;
			for (j = 0; j < this->GetWidth(); j++)
			{
				pixelData[index + 3*j + 2] = scanLine[j];
				pixelData[index + 3*j + 1] = scanLine[j + header.BytesPerLine];
				pixelData[index + 3*j + 0] = scanLine[j + header.BytesPerLine * 2];
			}
		}
	}

	//Read in the palette if there is one
	if (header.Version == 5 && this->GetBitDepth() == 8)
	{
		file->SetPosition(-769, deFile_Seek_End);
		file->Read((void*)&c, sizeof(BYTE));
		//if (c == 12)
		{
			DWORD DataSize = sizeof(RGBTRI)*(1<<this->GetBitDepth());
			file->Read((void*)Palette, DataSize);
		}
	}
	
	if (this->GetBitDepth() == 24)
	{
		DWORD MaxSize = this->GetImageSize() + this->GetImageSize()/3;
		DWORD		Temp;
		unsigned char *		Buffer;
		DWORD *		NewBuffer;
		Buffer = new unsigned char[MaxSize];
		NewBuffer = (DWORD *)Buffer;
		for(Temp = 0; Temp < this->GetImageSize(); Temp+=3) 
		{
			*NewBuffer = 0xFF000000 | ((DWORD)(pixelData[Temp]) << 0) | ((DWORD)(pixelData[Temp+1]) << 8) | ((DWORD)(pixelData[Temp+2]) << 16);
			NewBuffer++; 
		}
		this->SetFormat(IdeDriver::BPP_X8R8G8B8);
		this->SetBitDepth(32);
		this->SetImageSize(MaxSize);
		this->SetData(Buffer, 0, 0);
	}
	else if(this->GetBitDepth() == 8)
	{
		DWORD		MaxSize = this->GetWidth()*this->GetHeight()*4;
		DWORD		Temp;
		unsigned char *		Buffer;
		DWORD *		NewBuffer;
		RGBTRI				CurrentColor;
		Buffer = new unsigned char[MaxSize];
		NewBuffer = (DWORD *)Buffer;
		for(Temp = 0; Temp < this->GetImageSize(); Temp++) 
		{
			CurrentColor = Palette[pixelData[Temp]]; 
			*NewBuffer = 0xFF000000 | ((DWORD)(CurrentColor.rgbRed) << 16) | ((DWORD)(CurrentColor.rgbGreen) << 8) | ((DWORD)(CurrentColor.rgbBlue) << 0); 
			NewBuffer++; 
		}
		this->SetFormat(IdeDriver::BPP_X8R8G8B8);
		this->SetBitDepth(32);
		this->SetImageSize(MaxSize);
		this->SetData(Buffer, 0, 0);
	}
//=================
	delete[] pixelData;
	delete[] scanLine;

	return deTRUE;
}

void de3d_png_read_data(png_structp png_ptr, png_bytep data, png_size_t length)
{
	IdeFile * pFS;

	pFS = (IdeFile*)(png_ptr->io_ptr);

	if (pFS == NULL)
		png_error(png_ptr, "Read Error");

	png_size_t check;

	check = pFS->Read(data, length);

	if (check != length)
		png_error(png_ptr, "Read Error");
}

deBoolean deBitmap::LoadPNGFile(IdeFile *file)
{
	png_structp png_read_ptr;
	png_infop png_info_ptr;
	png_infop png_end_info;
	png_bytep *row_pointers;
	int itemp, rowbytes;

	png_read_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!png_read_ptr)
        return (deFALSE);    
	
	png_info_ptr = png_create_info_struct(png_read_ptr);
    if (!png_info_ptr)
    {
        png_destroy_read_struct(&png_read_ptr,
			(png_infopp)NULL, (png_infopp)NULL);
        return (deFALSE);
    }
	
	png_end_info = png_create_info_struct(png_read_ptr);
    if (!png_end_info)
    {
        png_destroy_read_struct(&png_read_ptr, &png_info_ptr,
			(png_infopp)NULL);
        return (deFALSE);
    }

#pragma warning (disable : 4611) // "setjmp is non-portable"
	if (0 != setjmp(png_jmpbuf(png_read_ptr)))
    {
		png_destroy_read_struct(&png_read_ptr, &png_info_ptr, &png_end_info);
		return deFALSE;
    }
#pragma warning (default : 4611)
	
	png_set_read_fn(png_read_ptr, file, de3d_png_read_data);

	png_read_png(png_read_ptr, png_info_ptr, 
		PNG_TRANSFORM_SWAP_ALPHA | PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING, 
		NULL);
	
	itemp = png_get_image_width(png_read_ptr, png_info_ptr);
	m_Width = itemp;

	itemp = png_get_image_height(png_read_ptr, png_info_ptr);
	m_Height = itemp;

	itemp = png_get_bit_depth(png_read_ptr, png_info_ptr);
	SetBitDepth(itemp);

	png_colorp palette;
	int num_palette = 0;
	png_get_PLTE(png_read_ptr, png_info_ptr, &palette, &num_palette);
	
	rowbytes = png_get_rowbytes(png_read_ptr, png_info_ptr);
	
	row_pointers = png_get_rows(png_read_ptr, png_info_ptr);

	SetNumFaces(1);

	u8* &pData = m_Data[0];
	if (num_palette == 0)
	{
		int image_size = rowbytes * m_Height;
		pData = new BYTE[image_size];
		for (itemp = 0; itemp < m_Height; ++itemp)
		{
			memcpy(pData+(itemp*rowbytes), row_pointers[itemp], rowbytes);
		}
		itemp = png_get_channels(png_read_ptr, png_info_ptr);
		if (itemp == 1)
		{
			SetFormat(IdeDriver::BPP_L8);
		}
		else if (itemp == 3)
		{
			SetBitDepth(4);
			this->SetImageSize(m_Width * m_Height * 4);
			BYTE * newbuf = new BYTE[m_ImageSize];
			int i, j;
			image_size -= 3;
			for (i = 0, j = 0; i < image_size; j+=4, i+=3)
			{
				if (DE3D_CPU_LITTLE_ENDIAN)
				{
					newbuf[j  ] = pData[i+2];
					newbuf[j+1] = pData[i+1];
					newbuf[j+2] = pData[i  ];
					newbuf[j+3] = 255;
				}
				else
				{
					newbuf[j  ] = 255;
					newbuf[j+1] = pData[i  ];
					newbuf[j+2] = pData[i+1];
					newbuf[j+3] = pData[i+2];
				}
			}
			delete[] pData;
			pData = newbuf;
			SetFormat(IdeDriver::BPP_X8R8G8B8);
		}
		else if (itemp == 4)
		{
#pragma todo (test endian conversion here) // might need to convert on big-endian instead
			if (DE3D_CPU_LITTLE_ENDIAN)
			{
				int i, j;
				for (i = 0, j = 0; i < image_size; j+=4, i+=3)
				{
					deSwap(pData[i+0], pData[i+3]);
					deSwap(pData[i+1], pData[i+2]);
				}
			}
			SetFormat(IdeDriver::BPP_A8R8G8B8);
		}
	}
	else
	{
		pData = new BYTE[m_Width * m_Height * 4];
		rowbytes *= 4;
		int x;
		for (itemp = 0; itemp < m_Height; ++itemp)
		{
			for (x = 0; x < m_Width; ++x)
			{
				if (DE3D_CPU_LITTLE_ENDIAN)
				{
					pData[itemp*rowbytes + x*4 + 3] = 0xff;
					pData[itemp*rowbytes + x*4 + 2] = palette[row_pointers[itemp][x]].red;
					pData[itemp*rowbytes + x*4 + 1] = palette[row_pointers[itemp][x]].green;
					pData[itemp*rowbytes + x*4 + 0] = palette[row_pointers[itemp][x]].blue;
				}
				else
				{
					pData[itemp*rowbytes + x*4 + 0] = 0xff;
					pData[itemp*rowbytes + x*4 + 1] = palette[row_pointers[itemp][x]].red;
					pData[itemp*rowbytes + x*4 + 2] = palette[row_pointers[itemp][x]].green;
					pData[itemp*rowbytes + x*4 + 3] = palette[row_pointers[itemp][x]].blue;
				}
			}
		}
		SetFormat(IdeDriver::BPP_X8R8G8B8);
	}
	
	png_destroy_read_struct(&png_read_ptr, &png_info_ptr, &png_end_info);
	
	return deTRUE;
}

IdeDriver::BPP GetDDSFormat(deDDPixelFormat & ddpf)
{
	if (!(deByteOrderLE32(ddpf.dwFlags) & deDDPF_RGB))
		return IdeDriver::BPP_UNKNOWN;
	if (   deByteOrderLE32(ddpf.dwRBitMask) == 0x00ff0000
		&& deByteOrderLE32(ddpf.dwGBitMask) == 0x0000ff00 
		&& deByteOrderLE32(ddpf.dwBBitMask) == 0x000000ff)
	{
		if (deByteOrderLE32(ddpf.dwRGBBitCount) == 32)
		{
			if (deByteOrderLE32(ddpf.dwFlags) & deDDPF_ALPHAPIXELS)
				return IdeDriver::BPP_A8R8G8B8;
			else
				return IdeDriver::BPP_X8R8G8B8;
		}
		else
		{
			return IdeDriver::BPP_R8G8B8;
		}
	}
	if (   deByteOrderLE32(ddpf.dwRBitMask) == 0x0f00
		&& deByteOrderLE32(ddpf.dwGBitMask) == 0x00f0
		&& deByteOrderLE32(ddpf.dwBBitMask) == 0x000f)
	{
		if (deByteOrderLE32(ddpf.dwFlags) & deDDPF_ALPHAPIXELS)
			return IdeDriver::BPP_A4R4G4B4;
		else
			return IdeDriver::BPP_X4R4G4B4;
	}
	if (   deByteOrderLE32(ddpf.dwRBitMask) == 0x7c00
		&& deByteOrderLE32(ddpf.dwGBitMask) == 0x03e0
		&& deByteOrderLE32(ddpf.dwBBitMask) == 0x001f)
	{
		if (deByteOrderLE32(ddpf.dwFlags) & deDDPF_ALPHAPIXELS)
			return IdeDriver::BPP_A1R5G5B5;
		else
			return IdeDriver::BPP_X1R5G5B5;
	}
	if (   deByteOrderLE32(ddpf.dwRBitMask) == 0xf800
		&& deByteOrderLE32(ddpf.dwGBitMask) == 0x07e0
		&& deByteOrderLE32(ddpf.dwBBitMask) == 0x001f)
	{
		return IdeDriver::BPP_R5G6B5;
	}
	return IdeDriver::BPP_UNKNOWN;
}

#pragma todo(clean up LoadDDSFile and make mip-mapping work properly)
deBoolean deBitmap::LoadDDSFile(IdeFile *file)
{
	//////////////////////////////////////
	// start reading the file
	// from Microsoft's mssdk D3DIM example "Compress"

	deDDSurfaceDesc2	ddsd;
    u32					dwMagic;
	s32					FileSize;
	s32					MipCount;
	BOOL				bMipTexture;
	BOOL				bDXTC;

	DE_ASSERT(sizeof(deDDSurfaceDesc2) == 124);

    // Read magic number
	file->Read(&dwMagic, sizeof(u32));

    if( deByteOrderLE32(dwMagic) != deMAKEFOURCC('D','D','S',' ') )
    {
		return deFALSE;
    }
	
    // Read the surface description
	file->Read(&ddsd, sizeof(deDDSurfaceDesc2));
	FileSize = file->GetSize();
	FileSize -= sizeof(u32) + sizeof(deDDSurfaceDesc2);

    // Clear unwanted flags
	// Can't do this!!!  surface not re-created here
	//    ddsd.dwFlags &= (~DDSD_PITCH);
	//    ddsd.dwFlags &= (~DDSD_LINEARSIZE);


	// Is it DXTC ?
	// I sure hope pixelformat is valid!
//	DecodePixelFormat( m_strFormat, &( ddsd.ddpfPixelFormat ) );
	deDDPixelFormat & ddpf = (ddsd.ddpfPixelFormat);
	bDXTC = deTRUE;
	switch(deByteOrderLE32(ddpf.dwFourCC))
	{
	case 0:
		// This dds texture isn't compressed so write out ARGB format
		m_Format = GetDDSFormat(ddpf);
		bDXTC = deFALSE;
		break;
		
	case deMAKEFOURCC('D','X','T','1'):
		//strcpy( strPixelFormat, "DXT1" );
		m_Format = IdeDriver::BPP_DXT1;
		break;
		
	case deMAKEFOURCC('D','X','T','2'):
		//strcpy( strPixelFormat, "DXT2" );
		m_Format = IdeDriver::BPP_DXT2;
		break;
		
	case deMAKEFOURCC('D','X','T','3'):
		//strcpy( strPixelFormat, "DXT3" );
		m_Format = IdeDriver::BPP_DXT3;
		break;
		
	case deMAKEFOURCC('D','X','T','4'):
		//strcpy( strPixelFormat, "DXT4" );
		m_Format = IdeDriver::BPP_DXT4;
		break;
		
	case deMAKEFOURCC('D','X','T','5'):
		//strcpy( strPixelFormat, "DXT5" );
		m_Format = IdeDriver::BPP_DXT5;
		break;
	default:
		//strcpy( strPixelFormat, "Format Unknown");
		m_Format = IdeDriver::BPP_UNKNOWN;
		bDXTC = deFALSE;
		break;
	}

	if(m_Format == IdeDriver::BPP_UNKNOWN)
	{
		DEBUGRETURN(deFALSE, "Unrecognized .DDS format, expected DXTC\n");
	}

    // Does texture have mipmaps?
	if (deByteOrderLE32(ddsd.dwFlags) & deDDSD_MIPMAPCOUNT)
		MipCount = deByteOrderLE32(ddsd.dwMipMapCount)-1;
	else
		MipCount = 0;
	bMipTexture = ( MipCount > 1 ) ? deTRUE : deFALSE;
	
	m_Height = deByteOrderLE32(ddsd.dwHeight);
	m_Width  = deByteOrderLE32(ddsd.dwWidth);

	SetNumFaces(1);
	u8* &pData = m_Data[0];
	// Read in all the mip-levels at once

#pragma todo (test both these cases with different data)
	if (bDXTC)
	{
		if( deByteOrderLE32(ddsd.dwFlags) & deDDSD_LINEARSIZE )
		{
			//TRACE("dwFlags  has DDSD_LINEARSIZE\n");

			m_ImageSize = deByteOrderLE32(ddsd.dwPitchOrLinearSize);
			pData = new BYTE[m_ImageSize];
			if( pData == NULL )
			{
				DEBUGRETURN(deFALSE, "Can't allocate m_Data on file read!\n");
			}

			file->Read(pData, m_ImageSize);
			m_ByteDepth = m_ImageSize*4 / (m_Height*m_Width);
		}
		else
		{
			//TRACE("dwFlags  file doesn't have linearsize set\n");

			// do some funky computations for the base image size
			m_ImageSize = deByteOrderLE32(ddsd.dwPitchOrLinearSize) * deByteOrderLE32(ddsd.dwHeight);
			if (m_ImageSize == 0)
			{
				m_ImageSize = m_Width * deByteOrderLE32(ddsd.dwHeight) / 2;
			}
			if (m_Format != IdeDriver::BPP_DXT1)
				m_ImageSize *= 2;
			ddsd.dwPitchOrLinearSize = deByteOrderLE32(m_ImageSize*4 / m_Height);
			DWORD dwBytesPerRow = deByteOrderLE32(ddsd.dwWidth) * deByteOrderLE32(ddpf.dwRGBBitCount) / 8;
			if (dwBytesPerRow == 0)
				dwBytesPerRow = deByteOrderLE32(ddsd.dwPitchOrLinearSize);
			m_ByteDepth = m_ImageSize*4 / (m_Height*m_Width);

			int i;
			long MipSize = 0;
			long w,h,size;
			// starting with first mip-level...
			w = m_Width/2;
			h = m_Height/2;
			w = max(w, 1);
			h = max(h, 1);
			// ... cycle through all the sizes to figure out the total data size
			for (i = 0; i < MipCount; ++i)
			{
				// this formula was provided by the dx8 docs
				size = max(1, w / 4) * max(1, h / 4);
				w /= 2;
				h /= 2;
				w = max(w, 1);
				h = max(h, 1);
				if (m_Format == IdeDriver::BPP_DXT1)
					size *= 8;
				else
					size *= 16;
				MipSize += size;
			}
			m_ImageSize += MipSize;

			// create our data at last
			pData = new BYTE[m_ImageSize];
			if( pData == NULL )
			{
				DEBUGRETURN(deFALSE, "Can't allocate m_Data on file read!\n");
			}

			//BYTE* pDest = pData;

			// copy the data into our buffer, one line at a time
			//for( DWORD yp = 0; yp < ddsd.dwHeight; yp++ )
			//{
			//	file->Read( pDest, dwBytesPerRow);
			//	pDest += ddsd.lPitch;
			//}
			file->Read(pData, m_ImageSize);
		}
		m_MipLevels = MipCount;
	}
	else // !bDXTC
	{
		m_ByteDepth = BPPByteDepth(m_Format);
		m_ImageSize = deByteOrderLE32(ddsd.dwPitchOrLinearSize) * deByteOrderLE32(ddsd.dwHeight);
		if (m_ImageSize == 0)
		{
			m_ImageSize = m_Width * m_Height * m_ByteDepth;
		}
		
		pData = new BYTE[m_ImageSize];
		if( pData == NULL )
		{
			DEBUGRETURN(deFALSE, "Can't allocate m_Data on file read!\n");
		}
		file->Read(pData, m_ImageSize);
	}

	// success
	return deTRUE;
}

deBoolean deBitmap::LoadCMAPFile(IdeFile *file, const char* filename)
{
	if (!file || !filename)
		return deFALSE;

	long	Length;			// length of the buffer
	char*	Buffer = NULL;	// storage for read-in file
	int		Offsets[7];		// index into buffer indicating where each filename starts
	int		i, k, n;		// counters for various things
	bool	newline;		// indicates when we have a newline in the file

	// clear out any data we might currently have
	Cleanup();

	// get the file size
	file->SetPosition(0, deFile_Seek_End);
	Length = file->GetPosition();
	file->SetPosition(0, deFile_Seek_Start);

	// allocate enough memory to read the file and an extra terminating null
	Buffer = new char[Length+1];
	// read the file into the buffer
	if (Length != file->Read(Buffer, Length))
		return deFALSE;
	// put on the terminating null
	Buffer[Length] = '\0';
	Length += 1;

	// initialize the offset values (we have 7 to account for extra lines after the 6th line)
	memset(Offsets, -1, 7*sizeof(int));
	k = 0;
	newline = true;
	for (i = 0; i < Length; ++i)
	{
		if (Buffer[i] == '\n')
		{
			// if we have a newline char, go back and blank out all the whitespace leading up to it
			newline = true;
			for (n = i; n > Offsets[k]; --n)
			{
				if (Buffer[n] == '\n' || Buffer[n] == '\r' || Buffer[n] == ' ' || Buffer[n] == '\t')
					Buffer[n] = '\0';
				else
					break;
			}
			continue;
		}
		if (newline)
		{
			// we're starting a new line, so ignore leading whitespace
			if (Buffer[i] == ' ' || Buffer[i] == '\r' || Buffer[i] == '\t')
				continue;
			// record the start of this line's text
			Offsets[k] = i;
			++k;
			if (k > 6) // bust out if we've read in our 6 lines
				break;
			newline = false;
		}
	}

	bool	ValidFile = false;	// set to true when we get some valid bitmap attributes
	u8*		newdata;			// pointer to each face's bitmap data
	char*	newfilename;		// pointer into Buffer for individual face filenames
	char	filepath[256];		// used to prepend relative path to filename
	IdeResourceBase* rsc = NULL;
	deBitmap* tempbmp = NULL;
	SetNumFaces(6);
	// loop through the 6 faces and load up a file for each one
	for (i = 0; i < 6; ++i)
	{
		newdata = NULL;
		// if we have a valid offset for this face, let's load it up
		if (Offsets[i] >= 0)
		{
			// find the relative path of this file
			// so we can prepend it to the filenames fetched from inside it
			// this means the filenames inside the CMAP file are relative to the CMAP's location
			n = 0;
			for (k = 0; filename[k]; ++k)
			{
				filepath[k] = filename[k];
				// if we find a slash, set the terminating char to be 1 after it
				if (filename[k] == '\\' || filename[k] == '/')
					n = k+1;
				else if (filename[k] == '\0')
					break;
			}
			filepath[n] = '\0';

			// grab the filename for this face
			newfilename = &(Buffer[Offsets[i]]);
			// make sure we're not attempting to load a cubemap onto a cubemap face
			n = strlen(newfilename)-5;
			if (n >= 0 && !strcmpi(newfilename+n, ".cmap"))
			{
				DEBUGMSG2("Cannot load a cubemap to a cubemap face '%s', processing '%s'", newfilename, filename);
				goto bad_load;
			}
			// concatenate the relative path with the filename
			strcat(filepath, newfilename);
			// fetch the resource
			rsc = IdeResourceManager_GetManager()->GetResource(filepath, NULL);
			if (rsc)
			{
				// get the bitmap interface
				tempbmp = (deBitmap*)(rsc->GetRscInterface(deBitmap::s_BitmapRscInterfaceID));
				if (tempbmp)
				{
					if (!ValidFile)
					{
						// if we haven't yet recorded data on the bitmap face, record them now
						ValidFile = true;

						// copy attributes
						m_Format = tempbmp->m_Format;
						m_Width = tempbmp->m_Width;
						m_Height = tempbmp->m_Height;
						// width and height have to be the same... it's a CUBE
						if (m_Width != m_Height)
						{
							DEBUGMSG2("Height and Width do not match in bitmap file '%s', processing '%s'", filepath, filename);
							goto bad_load;
						}
						m_ByteDepth = tempbmp->m_ByteDepth;
						m_ImageSize = tempbmp->m_ImageSize;
						m_RenderTarget = tempbmp->m_RenderTarget;
					}
					else
					{
						// do comparisons to verify all faces are the same format & size
						if (m_Format != tempbmp->m_Format || 
							m_ImageSize != tempbmp->m_ImageSize || 
							m_RenderTarget != tempbmp->m_RenderTarget)
						{
							DEBUGMSG2("Formats do not match in bitmap file '%s', processing '%s'", filepath, filename);
							goto bad_load;
						}
						if (m_Width != tempbmp->m_Width || m_Height != tempbmp->m_Height)
						{
							DEBUGMSG2("Height and Width do not match in bitmap file '%s', processing '%s'", filepath, filename);
							goto bad_load;
						}
					}

					// hijack the data from the other bitmap
					newdata = tempbmp->m_Data[0];

					// set the other bitmap's data to NULL
					tempbmp->m_Data[0] = NULL;
					// release the bitmap, we've got its loaded data
					rsc->Release();
					rsc = NULL;
				}
				else
				{
					DEBUGMSG2("Error with bitmap file '%s', processing '%s'", filepath, filename);
					goto bad_load;
				}
			}
			else
			{
				DEBUGMSG2("Error loading file '%s', processing '%s'", filepath, filename);
				goto bad_load;
			}
		}
		else
		{
			DEBUGMSG1("Error, cube map has too few faces, processing '%s'", filename);
			goto bad_load;
		}
		m_Data[i] = newdata;
	}

	// once we're done processing, we have all 6 faces and can return success
	delete[] Buffer;
	return deTRUE;

bad_load:
	// something went wrong, so clean up and return failure
	if (rsc)
		rsc->Release();
	delete[] Buffer;
	DEBUGRETURN(deFALSE, "LoadCMAPFile: Error loading file");
}

/// only saves to a BMP file
deBoolean deBitmap::SaveToFile(IdeFile *file)
{
	if (!file)
		DEBUGRETURN(deFALSE, "deBitmap::SaveToFile: file was NULL");

	u32 ImageSize = m_Width * m_Height * 3;
	u16 BitDepth = 24;

	u8* FileData = new u8[ImageSize];
	if (!FileData)
	{
		return deFALSE;
	}
	
	int x,y;
	const u8* pMyData = m_Data[0];
	if (m_Format == IdeDriver::BPP_A8R8G8B8) // 32-bit format with alpha info
	{
#pragma todo (needs endian conversion)
		// we can save and load .bmp files with alpha information, which makes them 32-bit
		// this format is not very compatible with other image programs and may not load correctly
		// MS Paint should load it if there are any problems
		// the other formats below all save into 24-bit .bmp

		// resize
		ImageSize = m_Width * m_Height * 4;
		BitDepth = 32;

		delete[] FileData;
		FileData = new u8[ImageSize];
		if (!FileData)
		{
			return deFALSE;
		}

		u32* pFData = (u32*)FileData;
		u32* pData;
		for (y = 0; y < m_Height; ++y)
		{
			pData = (u32*)(pMyData) + (m_Height-y-1)*m_Width;
			for (x = 0; x < m_Width; ++x)
			{
				*pFData = *pData;

				pFData++;
				pData++;
			}
		}
	}
	else if (m_Format == IdeDriver::BPP_X8R8G8B8)
	{
		// need to trim off the extra byte from each pixel to make it 24-bit
#pragma todo (needs testing & endian conversion)
		u8 * pFData = FileData;
		u32* pData;
		for (y = 0; y < m_Height; ++y)
		{
			pData = (u32*)(pMyData) + (m_Height-y-1)*m_Width;
			for (x = 0; x < m_Width; ++x)
			{
				pFData[0] = u8( ((*pData)      ) & 0xff );
				pFData[1] = u8( ((*pData) >>  8) & 0xff );
				pFData[2] = u8( ((*pData) >> 16) & 0xff );
				
				pFData += 3;
				pData++;
			}
		}
	}
	else if (m_Format == IdeDriver::BPP_R8G8B8)
	{
		// can go straight from memory to file
#pragma todo (needs testing & endian conversion)
		u8 * pFData = FileData;
		const u8 * pData;
		// first Height-1 rows
		for (y = 0; y < m_Height-1; ++y)
		{
			pData = pMyData + (m_Height-y-1)*m_Width*3;
			for (x = 0; x < m_Width; ++x)
			{
				*((u32*)pFData) = *((u32*)pData);
				
				pFData += 3;
				pData += 3;
			}
		}
		// last row, Width-1 pixels
		pData = pMyData + (m_Height-y-1)*m_Width*3;
		for (x = 0; x < m_Width-1; ++x)
		{
			*((u32*)pFData) = *((u32*)pData);
			
			pFData += 3;
			pData += 3;
		}
		// last pixel
		pFData[0] = u8( ((*pData)      ) & 0xff);
		pFData[1] = u8( ((*pData) >>  8) & 0xff);
		pFData[2] = u8( ((*pData) >> 16) & 0xff);
	}
	else
	{
		// non-direct format conversion, must sample the color at each pixel
#pragma todo (needs testing & endian conversion)
		deARGB color;
		u8 * pFData = FileData;
		for (y = m_Height-1; y >= 0; --y)
		{
			for (x = 0; x < m_Width; ++x)
			{
				color = GetColorAt(x, y);
				pFData[0] = u8( ((color)      ) & 0xff );
				pFData[1] = u8( ((color) >>  8) & 0xff );
				pFData[2] = u8( ((color) >> 16) & 0xff );

				pFData += 3;
			}
		}
	}

	// info header for bitmap data
	deBitmapInfoHeader bih;
	bih.biSize = deByteOrderLE32(sizeof(bih));
    bih.biWidth = deByteOrderLE32(m_Width);
    bih.biHeight = deByteOrderLE32(m_Height);
    bih.biPlanes = deByteOrderLE16(1);
    bih.biBitCount = deByteOrderLE16(BitDepth);
    bih.biCompression = deByteOrderLE32(0); //BI_RGB;
    bih.biSizeImage = deByteOrderLE32(ImageSize);
    bih.biXPelsPerMeter = deByteOrderLE32(3780);
    bih.biYPelsPerMeter = deByteOrderLE32(3780);
    bih.biClrUsed = deByteOrderLE32(0);
    bih.biClrImportant = deByteOrderLE32(0);
	
	// file header for bitmap file
	deBitmapFileHeader bfh;
    *((char*)(&bfh.bfType)    ) = 'B';
    *((char*)(&bfh.bfType) + 1) = 'M';
    bfh.bfSize = deByteOrderLE32(sizeof(bfh) + sizeof(bih) + ImageSize);
    bfh.bfReserved1 = bfh.bfReserved2 = 0;
    bfh.bfOffBits = deByteOrderLE32(sizeof(bfh) + sizeof(bih));
	
	// write file header
	if (sizeof(bfh) != file->Write(&bfh, sizeof(bfh)))
	{
		file->Release();
		return deFALSE;
	}

	// write info header
	if (sizeof(bih) != file->Write(&bih, sizeof(bih)))
	{
		file->Release();
		return deFALSE;
	}

	// and write the image data itself
	if (ImageSize != (u32)file->Write(FileData, ImageSize))
	{
		file->Release();
		delete[] FileData;
		return deFALSE;
	}

	// delete our temporary buffer
	delete[] FileData;
	// successful save
	return deTRUE;
}

