#include "Core/Asura.h"

#ifdef STANDALONE_TIM2_CONVERTER
//redirect the text messages to OutputWindow instead
#include "stdafx.h"
#ifndef TIM2_SILENT_MODE
#include "Tim2Converter.h"
#include "OutputWindow.h"
#endif
#define Tools_Level_Export OutputWindow
#else
#include "Load&Save/LevelExport/Tools_Level_Export.h"
#endif 

#include "Core/Asura_TextureFlags.h"
#include "Core/Asura_MaterialFlags.h"
#include "Core/Asura_Maths.h"
#include "Core/Memory/Asura_Mem.h"
#include "Core/Asura_HashTree.h"

#include "Debugging/Asura_Assert.h"

#include "ImageProcessing/Tools_Tim2_Swizzler.h"
#include "ImageProcessing/Tools_Tim2_Writer.h"
#include "Load&Save/Tools_TextureConversionData.h"

#include "ImageProcessing/Tools_IPImageEx.h"

#include <float.h>
 
Tools_Tim2_Writer* Tools_Tim2_Writer::ThisWriter = 0;

Tools_Tim2_Writer::Tools_Tim2_Writer()
{
	NumMipMaps = 0;
	AlphaType = ALPHA_NONE;

	m_uPaletteSize = 0;
	Palette = 0;

	PixelFormat = 0;
	PaletteFormat = 0;

	AlphaIsUsedAsNormalAlpha = true;
	TextureFlags = 0;
	m_uMaterialFlags = 0;

	m_uMaximumTextureSize = 0;
}

Tools_Tim2_Writer::Tools_Tim2_Writer(int iWidth,int iHeight,int iPitch,void* pBits,const TextureConversionData& xConversionData)
{
	Initialise(iWidth,iHeight,iPitch,pBits,xConversionData);
}

void Tools_Tim2_Writer::Initialise(int iWidth,int iHeight,int iPitch,void* pBits,const TextureConversionData& xConversionData)
{
	NumMipMaps = 0;
	AlphaType = ALPHA_NONE;

	m_uPaletteSize = 0;
	Palette = 0;

	PixelFormat = xConversionData.m_iPixelFormat;
	PaletteFormat = xConversionData.m_iPaletteFormat;

	AlphaIsUsedAsNormalAlpha = true;
	TextureFlags = xConversionData.m_iTextureFlags;
	m_uMaterialFlags = xConversionData.m_uMaterialFlags;

	Swizzle = xConversionData.m_bSwizzled;
	m_bUseFixedPalette = xConversionData.m_bUseFixedPalette;
	m_bWritePaletteAsRedScaleIndices = xConversionData.m_bWritePaletteAsRedScaleIndices;
	m_bDisableDithering = xConversionData.m_bDisableDithering;

	m_uMaximumTextureSize = xConversionData.m_uMaximumTextureSize;
	

	SetMainImage(iWidth,iHeight,iPitch,pBits);
}

Tools_Tim2_Writer::~Tools_Tim2_Writer()
{
	delete [] Palette;
}


#define WRITE_DATA(_data,_datasize) \
{\
	WriteFile(file,_data,_datasize,&bytesWritten,0);\
	totalWritten+=bytesWritten;\
	if((unsigned)_datasize!=bytesWritten)\
	{\
		CloseHandle(file);\
		return false;\
	}\
}

#define WRITE_ALIGNMENT(align) \
{\
	if(totalWritten%align)\
	{\
		int amount = align - (totalWritten%align);\
		WRITE_DATA(Padding,amount);\
	}\
}
void Tools_Tim2_Writer::SetMainImage(int width,int height,int pitch,void* pBits)
{
	Asura_Assert(!Images[0].PixelData,"");

	//allocate space for the image
	Images[0].Width = width;
	Images[0].Height = height;
	Images[0].PixelData = new unsigned int [width*height];


	AlphaType = ALPHA_NONE;

    //determine alpha type based on texture flags
	if((TextureFlags & AsuraTextureFlag_8BitAlpha) || (m_uMaterialFlags & AsuraMaterialFlag_SelfIlluminate))
	{
		AlphaType = ALPHA_8BIT;
	}
	else if( m_uMaterialFlags & AsuraMaterialFlag_Transparent)
	{
		AlphaType = ALPHA_1BIT;
	}

    const u_int uMaterialsWithSpecialAlpha = AsuraMaterialFlag_SelfIlluminate
                                           | AsuraMaterialFlag_Reflective
                                           | AsuraMaterialFlag_Specular_Soft
                                           | AsuraMaterialFlag_Specular_Sharp;

	if( (m_uMaterialFlags & uMaterialsWithSpecialAlpha) || (TextureFlags & AsuraTextureFlag_DontPremultiplyAlphaInPaletteGeneration) )
	{
		AlphaIsUsedAsNormalAlpha = false;
	}
	
	//find the actual alpha from the file
	ActualAlpha = ALPHA_NONE;
	
	//copy across, checking the alpha channel while we are at it
	unsigned char* lineStart = (unsigned char*)pBits;
	unsigned int* dest = Images[0].PixelData;


	for(int y=0;y<height;y++,lineStart+=pitch)
	{
		unsigned int* source = (unsigned int*) lineStart;
		for(int x=0;x<width;x++,source++,dest++)
		{
			if(TextureFlags & AsuraTextureFlag_IgnoreAlphaChannel)
			{
				//set alpha channel to fully opaque as we copy
				*dest=(*source)|0xff000000;
			}
			else
			{
				int alpha = (*source >> 24);
				if(alpha!=255)
				{
					if(alpha>0)
					{
						//got values other than 0 or 255.. so call it 8 bit alpha
						ActualAlpha  = ALPHA_8BIT;
					}
					else if(ActualAlpha  == ALPHA_NONE)
					{
						ActualAlpha  = ALPHA_1BIT;
					}
				}
				*dest=*source;
			}
		}
	}



	//fill in expected width/heights for the mip maps

	for(int i=1;i<TOOLS_TIM2_MAX_MIPMAPS_TO_GENERATE;i++)
	{
		if(Images[i-1].Width%2 || Images[i-1].Height%2)
		{
			Images[i].Width = 0;
			Images[i].Height = 0;
		}
		else
		{
			Images[i].Width = Images[i-1].Width/2;
			Images[i].Height = Images[i-1].Height/2;
		}
	}
	NumMipMaps = 1;
}

void Tools_Tim2_Writer::Sharpen()
{
	/*
	Sharpen Filter is 
	  0   -.25    0
	-.25    2   -.25
	  0   -.25    0

	Sharpen More Filter is
	-.25  -.25  -.25
	-.25    5   -.25
	-.25  -.25  -.25
	*/
	
	float fSharp = 0.25f;
	for(int iImage=0;iImage<NumMipMaps;iImage++)
	{
		Tools_Tim2_ImageData* pxImage = &Images[iImage];

		//generate buffer for sharpened image
		unsigned int* puDest = new unsigned int[pxImage->Width*pxImage->Height];
		unsigned int* puSrc	= pxImage->PixelData;
		
		for(int y=0;y<pxImage->Height;y++)
		{
			for(int x=0;x<pxImage->Width;x++)
			{
				u_int uCol = puSrc[y*pxImage->Width+x];
				int iMainA = (uCol >> 24) & 0xff;
				int iMainR = (uCol >> 16) & 0xff;
				int iMainG = (uCol >> 8) & 0xff;
				int iMainB = (uCol >> 0) & 0xff;

				float fTotalA = (float)iMainA;
				float fTotalR = (float)iMainR;
				float fTotalG = (float)iMainG;
				float fTotalB = (float)iMainB;

				for(int y2=y-1;y2<y+2;y2++)
				{
					if(y2<0 || y2>=pxImage->Height) continue;
					for(int x2=x-1;x2<x+2;x2++)
					{
						if(x2<0 || x2>=pxImage->Width) continue;
						{
							if(!(TextureFlags & AsuraTextureFlag_SharpenMore))
							{
								if(x!=x2 && y!=y2) continue;
							}
							
							uCol = puSrc[y2*pxImage->Width+x2];
							int iA = (uCol >> 24) & 0xff;
							int iR = (uCol >> 16) & 0xff;
							int iG = (uCol >> 8) & 0xff;
							int iB = (uCol >> 0) & 0xff;

							fTotalA += (iMainA-iA)*fSharp;
							fTotalR += (iMainR-iR)*fSharp;
							fTotalG += (iMainG-iG)*fSharp;
							fTotalB += (iMainB-iB)*fSharp;
						}
					}
				}

				//convert colour components back to ints , and clamp
				int iTotalA = Asura_Maths::ClampToRange(Asura_Maths::FloatToInt_Near(fTotalA),0,255);
				int iTotalR = Asura_Maths::ClampToRange(Asura_Maths::FloatToInt_Near(fTotalR),0,255);
				int iTotalG = Asura_Maths::ClampToRange(Asura_Maths::FloatToInt_Near(fTotalG),0,255);
				int iTotalB = Asura_Maths::ClampToRange(Asura_Maths::FloatToInt_Near(fTotalB),0,255);

				puDest[y*pxImage->Width+x] = (iTotalA<<24)|(iTotalR<<16)|(iTotalG<<8)|iTotalB;
			}
		}
		//replace the pixel data with the sharpened version
		delete [] pxImage->PixelData;
		pxImage->PixelData = puDest;
				
	}
}

void Tools_Tim2_Writer::ScaleImage(int iWidth,int iHeight)
{
	if(!Images[0].PixelData)
	{
		Asura_Assert(0,"Main image data needs to be set first");
		return;
	}
	if(iWidth == Images[0].Width && iHeight == Images[0].Height) return;


	Tools_IPImageEx xOriginalImage;
	xOriginalImage.CreateNew(Images[0].Width,Images[0].Height,0);
	xOriginalImage.CreateAlphaChannel();

	unsigned int* puSource = Images[0].PixelData;
	for(int h=0;h<Images[0].Height;h++)
	{
		IPColour* pxRGBDest = xOriginalImage.GetColourRow(h);
		IPAlphaValue* puAlphaDest = xOriginalImage.GetAlphaRow(h);
		for(int w=0;w<Images[0].Width;w++)
		{
			*puAlphaDest = (*puSource >> 24) & 0xff;
			pxRGBDest->r = (unsigned char)((*puSource >> 16) & 0xff);
			pxRGBDest->g = (unsigned char)((*puSource >> 8) & 0xff);
			pxRGBDest->b = (unsigned char)((*puSource >> 0) & 0xff);

			puAlphaDest++;
			pxRGBDest++;
			puSource++;
		}
	}

	Tools_IPImageEx xScaledImage;
	xScaledImage.CreateByScaling(xOriginalImage,(unsigned)iWidth,(unsigned)iHeight);

	delete [] Images[0].PixelData;
	Images[0].PixelData = new unsigned int[iWidth*iHeight];
	Images[0].Width = iWidth;
	Images[0].Height = iHeight;


	unsigned int* puDest = Images[0].PixelData;
	for(int h=0;h<Images[0].Height;h++)
	{
		const IPColour* pxRGBSource = xScaledImage.GetColourRow(h);
		const IPAlphaValue* puAlphaSource = xScaledImage.GetAlphaRow(h);
		for(int w=0;w<Images[0].Width;w++)
		{
			unsigned int uA = *puAlphaSource;
			unsigned int uR = pxRGBSource->r;
			unsigned int uG = pxRGBSource->g;
			unsigned int uB = pxRGBSource->b;

			*puDest++ = (uA<<24)|(uR<<16)|(uG<<8)|uB;

			pxRGBSource++;
			puAlphaSource++;
		}
	}

	for(int i=1;i<TOOLS_TIM2_MAX_MIPMAPS_TO_GENERATE;i++)
	{
		if(Images[i-1].Width%2 || Images[i-1].Height%2)
		{
			Images[i].Width = 0;
			Images[i].Height = 0;
		}
		else
		{
			Images[i].Width = Images[i-1].Width/2;
			Images[i].Height = Images[i-1].Height/2;
		}
	}



}

/*
void Tools_Tim2_Writer::SetOutputPixelFormat(int pixelFormat,int paletteFormat)
{
	PixelFormat = pixelFormat;
	PaletteFormat = paletteFormat;

}

void Tools_Tim2_Writer::SetOutputPixelFormatByAlpha(Tools_Tim2_Writer::ALPHA_TYPES alpha)
{
	AlphaType = alpha;
	PixelFormat = 0;
	PaletteFormat = 0;
}
*/

bool Tools_Tim2_Writer::WriteOutFile(const char* filename,int numMipLevelsToGenerate,int numMipLevelsToDrop,bool bRescaleAlpha)
{
	//open the file for writing
	unsigned long bytesWritten;
	HANDLE file = CreateFile(filename,GENERIC_WRITE,0,0,CREATE_ALWAYS,0,0);
	if(file == INVALID_HANDLE_VALUE)
	{
		return false;
	}
	
	
	TIM2_FILEHEADER timFileHeader;
	TIM2_PICTUREHEADER timPictureHeader;
	TIM2_MIPMAPHEADER timMipMapHeader;
	AsuraTim2UserData timUserData;

	memset(&timFileHeader,0,sizeof(timFileHeader));
	memset(&timPictureHeader,0,sizeof(timPictureHeader));
	memset(&timMipMapHeader,0,sizeof(timMipMapHeader));
	memset(&timUserData,0,sizeof(timUserData));


	//fill in the file header
	timFileHeader.FileId[0] = 'T';	
	timFileHeader.FileId[1] = 'I';	
	timFileHeader.FileId[2] = 'M';	
	timFileHeader.FileId[3] = '2';	
	timFileHeader.FormatVersion = 0x04;
	timFileHeader.FormatId = TIM2_ALIGN_128;
	timFileHeader.Pictures = 1;


	int AlignBytes = (timFileHeader.FormatId == TIM2_ALIGN_16) ? 16 : 128;

	if(PixelFormat == TIM2_IDTEX4)
	{
#ifndef TIM2_SILENT_MODE
		//Warn if trying to use a 16 colour palette on an image that started with more 
		//than that number of colours. RAR 12/1/2005
		Asura_HashTree xColourHash;
		unsigned int* pxCol = Images[0].PixelData;
		for(int i=0;i<Images[0].Height;i++)
		{
			for(int j=0;j<Images[0].Width;j++,pxCol++)
			{
				//add colour to hash tree
				int iJunkIndex;
				xColourHash.FindNodeIndexByHash((Asura_Hash_ID)*pxCol,true,iJunkIndex);
			}
		}

		u_int uOriginalColourCount = xColourHash.GetNumberOfNodes();

		if(uOriginalColourCount>16)
		{
			Tools_Level_Export::AddMessage(Tools_Level_Export::MT_WARNING,"Image flagged for 16 colour palette has %d colours in original.",uOriginalColourCount);
		}
#endif
	}


	//decide on the image type
	if(PixelFormat==0)
	{
		#if 0
		//format is determined based on the name of the file
		if(ActualAlpha!=AlphaType)
		{
#ifndef TIM2_SILENT_MODE
			char* alphaStrings[]={"No Alpha","1 Bit Alpha","8 Bit Alpha"};
			Tools_Level_Export::AddMessage(Tools_Level_Export::MT_WARNING,"Alpha in file doesn't match filename. Expected %s , Actual %s",alphaStrings[AlphaType],alphaStrings[ActualAlpha]);
#endif
		}
		#endif
		
		if(AlphaType == ALPHA_8BIT)
		{
			timPictureHeader.ImageType = TIM2_IDTEX8;
			timPictureHeader.ClutColors = 256;
			timPictureHeader.ClutType = TIM2_RGB32;
		}
		else
		{
			timPictureHeader.ImageType = TIM2_IDTEX8;
			timPictureHeader.ClutColors = 256;
			timPictureHeader.ClutType = TIM2_RGB16;
		}
	}
	else
	{
		timPictureHeader.ImageType = (unsigned char)PixelFormat;
		timPictureHeader.ClutType = (unsigned char)PaletteFormat;
		switch(PixelFormat)
		{
			case TIM2_IDTEX4 :
				timPictureHeader.ClutColors = 16;
				break;
			case TIM2_IDTEX8 :
				timPictureHeader.ClutColors = 256;
				break;
			default :
				//(not palettised , but setting PaletteFormat , for checking alpha type below)
				PaletteFormat = PixelFormat;
				timPictureHeader.ClutColors = 0;
				break;
		}
		
		if(PaletteFormat==0)
		{
			//Pixel format was defined , but palette format is being determined by the alpha type
			//in the image.
			if(AlphaType == ALPHA_8BIT)
			{
				timPictureHeader.ClutType = TIM2_RGB32;
			}
			else
			{
				timPictureHeader.ClutType = TIM2_RGB16;
			}
			
		}
		else
		{
			Asura_Assert(PaletteFormat>= TIM2_RGB16 && PaletteFormat<= TIM2_RGB32,"");

			switch(PaletteFormat)
			{
				case TIM2_RGB16 :
					AlphaType = ALPHA_1BIT;
					break;
				case TIM2_RGB24 :
					AlphaType = ALPHA_NONE;
					break;
				case TIM2_RGB32 :
					AlphaType = ALPHA_8BIT;
					break;
			}
		}
	}
	if(m_bUseFixedPalette)
	{
		//force settings to match palette
		u_int uPaletteSize;
		const u_int* puPalette;
		TextureConversionDataBase::GetFixedPalette(uPaletteSize,puPalette);

		if(!uPaletteSize)
		{
			Asura_Assert(0,"Fixed palette hasn't been set");
			CloseHandle(file);
			DeleteFile(filename);
			return false;
		}

		if(uPaletteSize <= 16)
		{
			timPictureHeader.ImageType = TIM2_IDTEX4;
			timPictureHeader.ClutColors = 16;
		}
		else
		{
			timPictureHeader.ImageType = TIM2_IDTEX8;
			timPictureHeader.ClutColors = 256;
		}
		if(AlphaType == ALPHA_8BIT)
		{
			timPictureHeader.ClutType = TIM2_RGB32;
		}
		else
		{
			timPictureHeader.ClutType = TIM2_RGB16;
		}
	}
	if(m_bWritePaletteAsRedScaleIndices)
	{
		//clut type must be 32 bit for this option. RAR 26/10/2006
		timPictureHeader.ClutType = TIM2_RGB32;

	}


	if(timPictureHeader.ImageType != TIM2_IDTEX8)
	{
		//ignore the swizzled option , since it only applies to the palettised version
		//and also doesn't work for 16 colour palettes RAR 12/1/2005
		Swizzle = false;
	}
	
	if(m_uMaximumTextureSize)
	{
		//May need to drop some extra mip levels to get beneath the specified size
		u_int uSize = Asura_Maths::Max(Images[0].Width,Images[0].Height);
		uSize >>= numMipLevelsToDrop;
		while(uSize > m_uMaximumTextureSize)
		{
			uSize >>= 1;
			numMipLevelsToDrop++;
		}
	}
	
	//generate the mip maps
	#if 0
	if( !GenerateMipMaps(numMipLevelsToGenerate+1, numMipLevelsToDrop, timPictureHeader.ImageType) )
	{
		CloseHandle(file);
		DeleteFile(filename);
		return false;
	}
	if(TextureFlags & (AsuraTextureFlag_Sharpen | AsuraTextureFlag_SharpenMore))
	{
		Sharpen();
	}
	#else
	bool bMipMapOkay = true;
	if(TextureFlags & (AsuraTextureFlag_Sharpen | AsuraTextureFlag_SharpenMore))
	{
		//drop mip map levels , to generate a new main image
		bMipMapOkay &= GenerateMipMaps(1,numMipLevelsToDrop,timPictureHeader.ImageType);
		//sharpen that image
		Sharpen();
		//and generate the real mip maps
		bMipMapOkay &= GenerateMipMaps(numMipLevelsToGenerate+1, 0, timPictureHeader.ImageType);
	}
	else
	{
		bMipMapOkay &= GenerateMipMaps(numMipLevelsToGenerate+1, numMipLevelsToDrop, timPictureHeader.ImageType);
	}
	if( !bMipMapOkay )
	{
		CloseHandle(file);
		DeleteFile(filename);
		return false;
	}
	#endif
	if(timPictureHeader.ClutColors)
	{
		if(!GeneratePalette(timPictureHeader.ClutColors,timPictureHeader.ClutType))
		{
			CloseHandle(file);
			DeleteFile(filename);
			return false;
		}
	}


	timPictureHeader.MipMapTextures = (unsigned char)NumMipMaps;
	timPictureHeader.ImageWidth = (unsigned short)Images[0].Width;
	timPictureHeader.ImageHeight = (unsigned short)Images[0].Height;

	#ifdef TOOLS_TIM2_WRITEBMPS
	//save out bitmaps of each of the mipmap levels for testing
	WriteBitmaps(filename);
	#endif
	
	//convert all the pixel data , and work out sizes for the texture data in the tim file
	ConvertPixelData(timPictureHeader.ImageType,bRescaleAlpha);

	//can now fill in the mip map header , and ImageSize
	for(int i=0;i<NumMipMaps;i++)
	{
		timMipMapHeader.Size[i] = Images[i].TimDataSize;
		timPictureHeader.ImageSize += Images[i].TimDataSize;
	}
	
	//align image size
	timPictureHeader.ImageSize = (timPictureHeader.ImageSize+AlignBytes-1)&~(AlignBytes-1);

	//calculate clut size
	if(timPictureHeader.ClutType)
	{
		timPictureHeader.ClutColors = (unsigned short)((timPictureHeader.ClutColors+31)&~31);
		switch(timPictureHeader.ClutType & 0x3f)
		{
			case TIM2_RGB16 :
				timPictureHeader.ClutSize = timPictureHeader.ClutColors*2;
				break;
			case TIM2_RGB24 :
				timPictureHeader.ClutSize = timPictureHeader.ClutColors*3;
				break;
			case TIM2_RGB32 :
				timPictureHeader.ClutSize = timPictureHeader.ClutColors*4;
				break;
			default :
				Asura_Assert(0,"Unexpected clut type");
		}
		//align
		timPictureHeader.ClutSize = (timPictureHeader.ClutSize+AlignBytes-1)&~(AlignBytes-1);
	}

	//calculate the HeaderSize
	timPictureHeader.HeaderSize = (unsigned __int16)(sizeof(timPictureHeader) + GetMipMapHeaderSize(NumMipMaps) + sizeof(timUserData));
	timPictureHeader.HeaderSize = (unsigned __int16)((timPictureHeader.HeaderSize+AlignBytes-1)&~(AlignBytes-1));
	
	timPictureHeader.TotalSize = timPictureHeader.ClutSize+timPictureHeader.ImageSize+timPictureHeader.HeaderSize;
	Asura_Assert(timPictureHeader.TotalSize%AlignBytes == 0,"");//the individual components should be aligned , so total is aligned

	//fill in user data
	timUserData.Id = ASURA_TIM2_USERDATA_ID;
	timUserData.Swizzled = Swizzle ? 1 : 0;
	
	
	//start writing out the file
	unsigned totalWritten = 0;
	unsigned char Padding[128] = {0,};

	

	//file header
	WRITE_DATA(&timFileHeader,sizeof(timFileHeader));
	WRITE_ALIGNMENT(AlignBytes);
	
	//if swizzleing , fiddle the values in the picture header
	unsigned __int8 tempImageType = timPictureHeader.ImageType;
	unsigned __int16 tempImageWidth = timPictureHeader.ImageWidth;
	unsigned __int16 tempImageHeight = timPictureHeader.ImageHeight;
	if(Swizzle)
	{
		timPictureHeader.ImageType = TIM2_RGB32;
		timPictureHeader.ImageWidth = (unsigned __int16)(timPictureHeader.ImageWidth/((tempImageType==TIM2_IDTEX4) ? 4 : 2));
		timPictureHeader.ImageHeight = (unsigned __int16)(timPictureHeader.ImageHeight/2);
	}
	
	//picture header
	WRITE_DATA(&timPictureHeader,sizeof(timPictureHeader));
	//WRITE_ALIGNMENT(AlignBytes);

	//restore values in picture header
	timPictureHeader.ImageType = tempImageType;
	timPictureHeader.ImageWidth = tempImageWidth;
	timPictureHeader.ImageHeight = tempImageHeight;


	//mip map header
	if(GetMipMapHeaderSize(NumMipMaps))
	{
		WRITE_DATA(&timMipMapHeader,GetMipMapHeaderSize(NumMipMaps));
	}
	//user data
	WRITE_DATA(&timUserData,sizeof(timUserData));
	

	WRITE_ALIGNMENT(AlignBytes);

	//image data
	for(int i=0;i<NumMipMaps;i++)
	{
		Tools_Tim2_ImageData* image = &Images[i];
		WRITE_DATA(image->PixelData,image->TimDataSize);
		WRITE_ALIGNMENT(16);
		
	}
	WRITE_ALIGNMENT(AlignBytes);

	//clut data
	if(m_uPaletteSize)
	{
		unsigned char* ClutData = new unsigned char[timPictureHeader.ClutSize];
		memset(ClutData,0,timPictureHeader.ClutSize);
		Tim2_RGB16_Pixel* ClutData16 = (Tim2_RGB16_Pixel*)ClutData;
		Tim2_RGB24_Pixel* ClutData24 = (Tim2_RGB24_Pixel*)ClutData;
		Tim2_RGB32_Pixel* ClutData32 = (Tim2_RGB32_Pixel*)ClutData;

		//dubious names for keeping track of progress...
		int block = 0;//which 16x2 block we are on to
		int section = 0;//left or right 8x2 section
		int section_row = 0;//current row in 8x2 section
		int section_col = 0;//current col in 8x2 section
		for(u_int i=0;i<m_uPaletteSize;i++)
		{
			int index = i;
			if(m_uPaletteSize>16)
			{
				index = 32*block+8*section+section_row*16+section_col;
				section_col++;
				if(section_col==8)
				{
					//reached end of 8x1 row
					section_col=0;
					section_row++;
				}		
				if(section_row==2)
				{
					//reached end of 8x2 section
					section_row=0;
					section++;
				}
				if(section==2)
				{
					//end of 16x2 block
					block++;
					section=0;
				}
			}
			
			switch(timPictureHeader.ClutType & 0x3f)
			{
				case TIM2_RGB16 :
					ClutData16[index].a = ((Palette[i]>>24)&0xff)>>7;
					ClutData16[index].r = ((Palette[i]>>16)&0xff)>>3;
					ClutData16[index].g = ((Palette[i]>>8)&0xff)>>3;
					ClutData16[index].b = ((Palette[i]>>0)&0xff)>>3;
					break;
				case TIM2_RGB24 :
					ClutData24[index].r = (unsigned char)((Palette[i]>>16)&0xff);
					ClutData24[index].g = (unsigned char)((Palette[i]>>8)&0xff);
					ClutData24[index].b = (unsigned char)((Palette[i]>>0)&0xff);
					break;
				case TIM2_RGB32 :
					ClutData32[index].a = ((Palette[i]>>24)&0xff);
					if ( bRescaleAlpha )
					{
						ClutData32[index].a = ((int)ClutData32[index].a+1)/2;//alpha needs to be from 0 to 128
					}
					ClutData32[index].r = ((Palette[i]>>16)&0xff);
					ClutData32[index].g = ((Palette[i]>>8)&0xff);
					ClutData32[index].b = ((Palette[i]>>0)&0xff);
					break;
			}
		}
		WRITE_DATA(ClutData,timPictureHeader.ClutSize);
		delete [] ClutData;



	}
	CloseHandle(file);

	return true;
}

bool Tools_Tim2_Writer::ReadHeadersFromBuffer( const char* pcBuffer, u_int uBufferSize, TIM2_FILEHEADER* pxFileHdr, TIM2_PICTUREHEADER* pxPicHdr, AsuraTim2UserData* pxUserData /*= 0*/)
{
	Asura_Assert(pcBuffer,"");
	Asura_Assert(pxFileHdr,"");
	Asura_Assert(pxPicHdr,"");
	
	u_int uBytesLeft = uBufferSize;

	//get file header
	if(sizeof(TIM2_FILEHEADER) > uBytesLeft) return false;
	*pxFileHdr = *(const TIM2_FILEHEADER*)pcBuffer;
	pcBuffer += sizeof(TIM2_FILEHEADER);
	uBytesLeft -= sizeof(TIM2_FILEHEADER);

	//make sure this is a tim2 file
	if(*(int*)pxFileHdr->FileId != ASURA_TIM2_FILE_ID)
	{
		return false;
	}



    const u_int uAlign = ( TIM2_ALIGN_16 == pxFileHdr->FormatId ? 16 : 128 );
    if( sizeof(TIM2_FILEHEADER) & (uAlign - 1) )
    {
		u_int uSkip = uAlign - (sizeof(TIM2_FILEHEADER) & (uAlign - 1));
		if(uSkip > uBytesLeft) return false;

		pcBuffer += uSkip;
		uBytesLeft -= uSkip;
		
    }

	//get picture header
	if(sizeof(TIM2_FILEHEADER) > uBytesLeft) return false;
	*pxPicHdr = *(const TIM2_PICTUREHEADER*)pcBuffer;
	pcBuffer += sizeof(TIM2_PICTUREHEADER);
	uBytesLeft -= sizeof(TIM2_PICTUREHEADER);


	//asura tim2 user data requested?
	if(pxUserData)
	{
		pxUserData->Id = 0;//default to invalid

		//Is the header large enough for the user data to exist at the end?
		int iMipMapHeaderSize = Tools_Tim2_Writer::GetMipMapHeaderSize(pxPicHdr->MipMapTextures);
		if((sizeof(TIM2_PICTUREHEADER) + sizeof(AsuraTim2UserData) + iMipMapHeaderSize)<=pxPicHdr->HeaderSize)
		{
			//skip past the mip map header
			if(iMipMapHeaderSize > (int) uBytesLeft) return false;
			pcBuffer += iMipMapHeaderSize;
			uBytesLeft -= iMipMapHeaderSize;


			//and read the user data

			if(sizeof(AsuraTim2UserData) > uBytesLeft) return false;
			*pxUserData = *(const AsuraTim2UserData*)pcBuffer;
			pcBuffer += sizeof(AsuraTim2UserData);
			uBytesLeft -= sizeof(AsuraTim2UserData);

		}
	}
	return true;
}

bool Tools_Tim2_Writer::ReadHeadersFromFile( const char* szFilename, TIM2_FILEHEADER* pxFileHdr, TIM2_PICTUREHEADER* pxPicHdr, AsuraTim2UserData* pxUserData /*= 0*/)
{
    HANDLE uFileHandle = CreateFile( szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL );
    if( INVALID_HANDLE_VALUE == uFileHandle )
    {
        return false;
    }

    DWORD uBytesRead;

    if( !ReadFile(uFileHandle, pxFileHdr, sizeof(TIM2_FILEHEADER), &uBytesRead, NULL) || sizeof(TIM2_FILEHEADER) != uBytesRead )
    {
        CloseHandle( uFileHandle );
        return false;
    }

    const u_int uAlign = ( TIM2_ALIGN_16 == pxFileHdr->FormatId ? 16 : 128 );
    if( sizeof(TIM2_FILEHEADER) & (uAlign - 1) )
    {
        if( -1 == SetFilePointer(uFileHandle, uAlign - (sizeof(TIM2_FILEHEADER) & (uAlign - 1)), NULL, FILE_CURRENT) )
        {
            CloseHandle( uFileHandle );
            return false;
        }
    }

    if( !ReadFile(uFileHandle, pxPicHdr, sizeof(TIM2_PICTUREHEADER), &uBytesRead, NULL) || sizeof(TIM2_PICTUREHEADER) != uBytesRead )
    {
        CloseHandle( uFileHandle );
        return false;
    }

	//asura tim2 user data requested?
	if(pxUserData)
	{
		pxUserData->Id = 0;//default to invalid

		//Is the header large enough for the user data to exist at the end?
		int iMipMapHeaderSize = Tools_Tim2_Writer::GetMipMapHeaderSize(pxPicHdr->MipMapTextures);
		if((sizeof(TIM2_PICTUREHEADER) + sizeof(AsuraTim2UserData) + iMipMapHeaderSize)<=pxPicHdr->HeaderSize)
		{
			//skip past the mip map header
			if(-1 == SetFilePointer(uFileHandle,iMipMapHeaderSize, NULL, FILE_CURRENT))
			{
	    	    CloseHandle( uFileHandle );
	    	    return false;
			}

			//and read the user data
	    	if( !ReadFile(uFileHandle, pxUserData, sizeof(AsuraTim2UserData), &uBytesRead, NULL) || sizeof(AsuraTim2UserData) != uBytesRead )
	    	{
	    	    CloseHandle( uFileHandle );
	    	    return false;
	    	}
		}

	}

    CloseHandle( uFileHandle );
    return true;
}

bool Tools_Tim2_Writer::GetDataSizeFromExistingFile( const char* szFilename, u_int* puDataSize )
{
    TIM2_FILEHEADER    xFileHdr;
    TIM2_PICTUREHEADER xPicHdr;
    
    if( !ReadHeadersFromFile(szFilename, &xFileHdr, &xPicHdr) ) { return false; }

    *puDataSize = xPicHdr.ImageSize;
    if( xPicHdr.ClutType )
    {
        *puDataSize += xPicHdr.ClutSize;
    }

    return true;
}

bool Tools_Tim2_Writer::GetImgProportionsFromExistingFile( const char* szFilename, u_int* puWidth, u_int* puHeight )
{
    TIM2_FILEHEADER    xFileHdr;
    TIM2_PICTUREHEADER xPicHdr;
	AsuraTim2UserData xUserData;

    if( !ReadHeadersFromFile(szFilename, &xFileHdr, &xPicHdr, &xUserData) ) { return false; }

    u_int uW = xPicHdr.ImageWidth;
    u_int uH = xPicHdr.ImageHeight;

    if( xUserData.IsValid() && xUserData.Swizzled )
    {
        // The change in dimensions depends on the original format. See §8.6
        // of the GS User's Manual (v5 or v6.)
        uW <<= ( 256 == xPicHdr.ClutColors ? 1 : 2 );
        uH <<= 1;
    }

    *puWidth  = uW;
    *puHeight = uH;
    
    return true;
}

bool Tools_Tim2_Writer::GetAlphaTypeFromExistingFile( const char* szFilename, ALPHA_TYPES* peAlphaType )
{
    TIM2_FILEHEADER    xFileHdr;
    TIM2_PICTUREHEADER xPicHdr;

    if( !ReadHeadersFromFile(szFilename, &xFileHdr, &xPicHdr) ) { return false; }

    u_int uImgType = xPicHdr.ImageType;
    
    // Deal with swizzled textures.
    if( TIM2_RGB32 == uImgType && xPicHdr.ClutColors )
    {
        uImgType = ( 256 == xPicHdr.ClutColors ? TIM2_IDTEX8 : TIM2_IDTEX4 );
    }

    *peAlphaType = ALPHA_NONE;
    switch( uImgType )
    {
        case TIM2_IDTEX4:
        case TIM2_IDTEX8:
			switch( xPicHdr.ClutType & 0x3F )
            {
			    case TIM2_RGB16: *peAlphaType = ALPHA_1BIT; break;
                case TIM2_RGB32: *peAlphaType = ALPHA_8BIT; break;
            }
            break;

        case TIM2_RGB16:
            *peAlphaType = ALPHA_1BIT; break;

        case TIM2_RGB32:
            *peAlphaType = ALPHA_8BIT; break;
    }

    return true;
}

void Tools_Tim2_Writer::DeSwizzleHeaders(TIM2_PICTUREHEADER* pxPicHdr, AsuraTim2UserData* pxUserData)
{
	Asura_Assert(pxPicHdr && pxUserData,"");

	if(!pxUserData->Swizzled) return;

	if(pxPicHdr->ClutColors == 32)//Number of clut colours is always a multiple of 32 , so ends up at 32 even if only 16 colours
	{
		pxPicHdr->ImageWidth <<= 2;
		pxPicHdr->ImageHeight <<= 1;
		pxPicHdr->ImageType = TIM2_IDTEX4;
	}
	else
	{
		Asura_Assert(pxPicHdr->ClutColors==256,"Only expecting 32 or 256 clut colors");
		pxPicHdr->ImageWidth <<= 1;
		pxPicHdr->ImageHeight <<= 1;
		pxPicHdr->ImageType = TIM2_IDTEX8;

	}
}

bool Tools_Tim2_Writer::GenerateMipMaps( int maxImages, int numMipLevelsToDrop, u_int uImgType )
{
	NV_ERROR_CODE eError;
	{
		nvCompressionOptions xOptions;
		xOptions.SetDefaultOptions();
		xOptions.bDitherColor = true;
		xOptions.textureFormat = k8888;

		const u_char* pucPixelData = reinterpret_cast<const u_char*>(Images[0].PixelData);
		ThisWriter = this;

		eError = Asura_PC_CompressDXT(
			pucPixelData,//raw_data
			Images[0].Width,
			Images[0].Height,
			Images[0].Width*4,//pitch
			nvBGRA,	// pixel order
			&xOptions,//compression options
			MipMapCallback,	// mip callback
			NULL);	// subrect (use defaults).
			
		Asura_Assertf(eError == NV_OK, ("Error in Asura_PC_CompressDXT: %s", Asura_PC_CompressDXT_GetErrorString(eError)));
		ThisWriter = 0;
	}

	if(eError != NV_OK)
	{
		return false;
	}

	if(m_uMaterialFlags & AsuraMaterialFlag_SelfIlluminate)
	{
		//alpha for textures with self illumination is done differently
		GenerateAlphaMipMapsForSelfIllumination();
	}
	
	if(NumMipMaps>1)
	{
		//We may need to drop one or more mip map levels
		//Normally it should just be one level (textures are done at twice the resolution
		//that they should be on the PS2). If textures are too large further levels may be dropped
		Asura_Assert(numMipLevelsToDrop<NumMipMaps,"");

		if(numMipLevelsToDrop)
		{
			NumMipMaps-=numMipLevelsToDrop;
			for(int i=0;i<numMipLevelsToDrop;i++)
			{
				delete [] Images[i].PixelData;
			}
			for(int i=0;i<NumMipMaps;i++)
			{
				Images[i] = Images[i+numMipLevelsToDrop];
				Images[i+numMipLevelsToDrop].PixelData = 0;
				Images[i+numMipLevelsToDrop].Width >>= numMipLevelsToDrop;
				Images[i+numMipLevelsToDrop].Height >>= numMipLevelsToDrop;
			}
		}
		if(TextureFlags & AsuraTextureFlag_PS2MoireFix)
		{
			MoireFix();
		}
		
		if(TextureFlags & (AsuraTextureFlag_NoMipMaps|AsuraTextureFlag_PS2DistantTexture))
		{
			//ignore the maxImages setting passed... only want 1 image
			maxImages = 1;
		}

		//In addition , we are only interested in having up to 3 mip maps
		//(or rather 4 : Base image + 3 mips)
		//(or just 1 in some cases)
		while(NumMipMaps>maxImages)
		{
			NumMipMaps--;
			delete [] Images[NumMipMaps].PixelData;
			Images[NumMipMaps].PixelData = 0;
		}

        // Examine the lower mipmap levels. Disregard any that are too small.
        while( NumMipMaps > 1 )
        {
            // Can't have any images less than 8 pixels in dimension since
            // this doesn't work in conjunction with bilinear sampling on the
            // PS2, which we use pretty much universally. With palettised
            // images, this is further restricted if we're swizzling them.
            int iMinDim = 8;
            if( Swizzle )
            {
                iMinDim = ( TIM2_IDTEX8 == uImgType ? 16 : 32 );
            }

            // Stop once we reach an acceptably sized image.
            if( Images[NumMipMaps - 1].Width >= iMinDim && Images[NumMipMaps - 1].Height >= iMinDim ) { break; }

			--NumMipMaps;
			delete[] Images[NumMipMaps].PixelData;
			Images[NumMipMaps].PixelData = NULL;
        }
	}

	return true;
}

void Tools_Tim2_Writer::GenerateAlphaMipMapsForSelfIllumination()
{
	for(int i=1;i<NumMipMaps;i++)
	{
		Tools_Tim2_ImageData* image = &Images[i-1];
		Asura_Assert(image->PixelData,"");
		Tools_Tim2_ImageData* mipImage = &Images[i];
		Asura_Assert(mipImage->PixelData,"");

		unsigned int* pixelData = image->PixelData;
		unsigned int* mipPixel = mipImage->PixelData;
		
		for(int y=0;y<mipImage->Height;y++)
		{
			for(int x=0;x<mipImage->Width;x++,mipPixel++)
			{
				//get the highest alpha value of the 4 pixels in the larger image
				unsigned int a = pixelData[x*2 + (y*2)*image->Width]>>24;
				a = max(a,pixelData[x*2+1 + (y*2)*image->Width]>>24);
				a = max(a,pixelData[x*2 + (y*2+1)*image->Width]>>24);
				a = max(a,pixelData[x*2+1 + (y*2+1)*image->Width]>>24);

				*mipPixel &=~ 0xff000000;
				*mipPixel |= a<<24;
			}
		}
		
	}
}


NV_ERROR_CODE Tools_Tim2_Writer::MipMapCallback(const void* pPixelData, size_t uCount, const MIPMapData* pxMipMapData, void* pUserData)
{
	Asura_Assert(ThisWriter,"");
	u_int uMipLevel = pxMipMapData ? pxMipMapData->mipLevel : 0;
	if(uMipLevel>=TOOLS_TIM2_MAX_MIPMAPS_TO_GENERATE) return NV_OK;//ignore excess mip maps
	if(uMipLevel==0) return NV_OK;// not interested in getting the main image again
	
	Tools_Tim2_ImageData* image = &ThisWriter->Images[uMipLevel];
	Asura_Assert(!image->PixelData,"");
	if(image->Width%4 || image->Height%4) return NV_OK;//ignore mip map levels that aren't multiples of 4
	if(image->Width==0 || image->Height==0) return NV_OK;//0 width/height mip maps seem to be generated on non square images... ignore them

	Asura_Assert((unsigned)(image->Width*image->Height*4)==uCount,"");

	//allocate space for the mip map , and copy across
	image->PixelData = new unsigned int[image->Width*image->Height];
	memcpy(image->PixelData,pPixelData,uCount);

	ThisWriter->NumMipMaps++;

	return NV_OK;
}

void Tools_Tim2_Writer::MoireFix()
{
	//replace the first mip map with a scaled up version of the second mip map
	if(NumMipMaps<3) return;

	Tools_Tim2_ImageData* image = &Images[1];
	Tools_Tim2_ImageData* mipImage = &Images[2];

	Asura_Assert(image->PixelData,"");
	Asura_Assert(mipImage->PixelData,"");

	unsigned int* mainPixel1 = image->PixelData;
	unsigned int* mainPixel2 = image->PixelData + image->Width;
	unsigned int* mipPixel = mipImage->PixelData;
	
	for(int y=0;y<mipImage->Height;y++)
	{
		for(int x=0;x<mipImage->Width;x++,mipPixel++)
		{
			*mainPixel1++ = *mipPixel;
			*mainPixel1++ = *mipPixel;
			*mainPixel2++ = *mipPixel;
			*mainPixel2++ = *mipPixel;
		}
		//skip to the next pair of lines on the main image
		mainPixel1 += image->Width;
		mainPixel2 += image->Width;
	}
}



#include "PaletteGenerationDll.h"


static PALETTE_GENERATION_PTR GetPaletteGenerationFunctionFromDll()
{
	static PALETTE_GENERATION_PTR PaletteGeneration = 0;
	static struct RuntimeDll
	{
		RuntimeDll(){hDLL = 0;}
		~RuntimeDll(){if(hDLL) FreeLibrary(hDLL);}
		HMODULE hDLL;
	} dll;
		
	if(!dll.hDLL)
	{
		//try loading the dll
		dll.hDLL = LoadLibrary("PaletteGeneration.dll");

		if (dll.hDLL == NULL)
		{
#ifndef TIM2_SILENT_MODE
			Tools_Level_Export::AddMessage(Tools_Level_Export::MT_ERROR,"Failed to load PaletteGeneration.dll");
#endif
			return 0;
		}

		//grab the function
		PaletteGeneration = (PALETTE_GENERATION_PTR)GetProcAddress(dll.hDLL,"PaletteGeneration");
	}
	
	return PaletteGeneration;
	
	
}


bool Tools_Tim2_Writer::GeneratePalette(int size,int clutType)
{
	//allocate space for the final palette
	m_uPaletteSize = size;
	Palette = new int[size];
	memset(Palette,0,m_uPaletteSize*sizeof(int));


	//fill in the image data to be passed
	PaletteGenerationImage PGImageData[TOOLS_TIM2_MAX_MIPMAPS_TO_GENERATE];
	for(int i=0;i<NumMipMaps;i++)
	{
		PGImageData[i].Width = Images[i].Width;
		PGImageData[i].Height = Images[i].Height;
		PGImageData[i].PixelData = Images[i].PixelData;
	}

	//fill in the rest of the paramaters
	PaletteGenerationParam PGParam;
	PGParam.NumImages = NumMipMaps;
	PGParam.Images = &PGImageData[0];
	switch(clutType)
	{
		case TIM2_RGB16 :
			PGParam.Precision = PGPrecision_16;
			break;
		case TIM2_RGB24 :
			PGParam.Precision = PGPrecision_24;
			break;
		case TIM2_RGB32 :
			PGParam.Precision = PGPrecision_32;
			break;
	}
	PGParam.Alpha = min(AlphaType,ActualAlpha);
	PGParam.AlphaUsedAsNormalAlpha = AlphaIsUsedAsNormalAlpha;
	PGParam.m_bUseExistingPalette = false;
	PGParam.m_bDisableDithering = m_bDisableDithering;

	if(m_bUseFixedPalette)
	{
		PGParam.m_bUseExistingPalette = true;

		//fill in the fixed palette
		const u_int* puPalette;
		TextureConversionDataBase::GetFixedPalette(m_uPaletteSize,puPalette);
		Asura_Assert(m_uPaletteSize,"");
		Asura_Assert(m_uPaletteSize <= (u_int)size,"");

		Asura_Mem::Copy(Palette,puPalette,sizeof(u_int)*m_uPaletteSize);


	}

	PGParam.PaletteSize = m_uPaletteSize;
	PGParam.Palette = Palette;

	//get the function from the dll
	PALETTE_GENERATION_PTR PaletteGeneration = GetPaletteGenerationFunctionFromDll();
	if(!PaletteGeneration)
	{
#ifndef TIM2_SILENT_MODE
		Tools_Level_Export::AddMessage(Tools_Level_Export::MT_ERROR,"Failed to find palette generation function");
#endif
		return false;
	}

	//and call it
	PaletteGeneration(&PGParam);

	if(m_bWritePaletteAsRedScaleIndices)
	{
		//Palette gets obliterated with this option , and replaced by index values in the red components
		for(u_int i=0;i<m_uPaletteSize;i++)
		{
			Palette[i] = i<<16;
		}
	}

	return true;
}

static u_int ComputeTBW( int iWidth, bool bPalettised )
{
    Asura_Assert( 0 <= iWidth && iWidth <= 1024, "Invalid texture width." );

    u_int uTBW = (iWidth + 63) >> 6;

    if( bPalettised && (uTBW & 1) ) { ++uTBW; }

    return uTBW;
}

void Tools_Tim2_Writer::ConvertPixelData(int imageType, bool bRescaleAlpha)
{
	for(int i=0;i<NumMipMaps;i++)
	{
		Tools_Tim2_ImageData* image = &Images[i];
		switch(imageType)
		{
			case TIM2_RGB32 :
			{
				unsigned int* source = image->PixelData;
				Tim2_RGB32_Pixel* dest = (Tim2_RGB32_Pixel*)image->PixelData;
				for(int j=image->Width*image->Height;j;j--)
				{
					int colour = *source++;
					dest->a = (colour>>24)&0xff;
					if ( bRescaleAlpha )
					{
						dest->a = ((int)dest->a+1)/2;//alpha needs to be from 0 to 128
					}
					dest->r = (colour>>16)&0xff;
					dest->g = (colour>>8)&0xff;
					dest->b = (colour)&0xff;
					dest++;
				}
				image->TimDataSize = image->Width*image->Height*4;
				break;
			}
			case TIM2_RGB24 :
			{
				unsigned int* source = image->PixelData;
				Tim2_RGB24_Pixel* dest = (Tim2_RGB24_Pixel*)image->PixelData;
				for(int j=image->Width*image->Height;j;j--)
				{
					int colour = *source++;
					dest->r = (unsigned char)((colour>>16)&0xff);
					dest->g = (unsigned char)((colour>>8)&0xff);
					dest->b = (unsigned char)((colour)&0xff);
					dest++;
				}
				image->TimDataSize = image->Width*image->Height*3;
				break;
			}
			case TIM2_RGB16 :
			{
				unsigned int* source = image->PixelData;
				Tim2_RGB16_Pixel* dest = (Tim2_RGB16_Pixel*)image->PixelData;
				for(int j=image->Width*image->Height;j;j--)
				{
					int colour = *source++;
					dest->a = ((colour>>24)&0xff)>127 ? 1 : 0;
					dest->r = ((colour>>16)&0xff)>>3;
					dest->g = ((colour>>8)&0xff)>>3;
					dest->b = ((colour)&0xff)>>3;
					dest++;
				}
				image->TimDataSize = image->Width*image->Height*2;
				break;
			}
			case TIM2_IDTEX8 :
			{
				//image data has already been palettised , but needs to be packed down into chars
				unsigned int* source = image->PixelData;
				unsigned char* dest = (unsigned char*)image->PixelData;
				for(int j=image->Width*image->Height;j;j--)
				{
					*dest++ = (unsigned char)*source++;
				}
				image->TimDataSize = image->Width*image->Height;

				if(Swizzle)
                {
                    u_int uW = image->Width;
                    u_int uH = image->Height;

                    // Check they're a multiple of the block size.
                    if( !(uW & 15) && !(uH & 15) )
                    {
                        Tools_Tim2_Swizzler::writeTexPSMT8( 0, ComputeTBW(uW, true), 0, 0, uW, uH, image->PixelData );

                        uW >>= 1;
                        uH >>= 1;
                        Tools_Tim2_Swizzler::readTexPSMCT32( 0, ComputeTBW(uW, false), 0, 0, uW, uH, image->PixelData );
                    }
				}
				
				break;
			}
			case TIM2_IDTEX4 :
			{
				//image data has already been palettised , but needs to be packed down into chars
				unsigned int* source = image->PixelData;
				unsigned char* dest = (unsigned char*)image->PixelData;
				for(int j=image->Width*image->Height;j;j-=2)
				{
					*dest = (unsigned char)*source++; 
					*dest++ |= ((unsigned char)*source++<<4);
				}
				image->TimDataSize = (image->Width*image->Height)/2;

				if(Swizzle)
				{
                    u_int uW = image->Width;
                    u_int uH = image->Height;

                    // Check they're a multiple of the block size.
                    if( !(uW & 31) && !(uH & 15) )
                    {
                        Tools_Tim2_Swizzler::writeTexPSMT4( 0, ComputeTBW(uW, true), 0, 0, uW, uH, image->PixelData );

                        uW = uW >> 2;
                        uH = uH >> 1;
                        Tools_Tim2_Swizzler::readTexPSMCT32( 0, ComputeTBW(uW, false), 0, 0, uW, uH, image->PixelData );
                    }
				}

				break;
			}

			default :
				Asura_Assert(0,"This type not supported yet");

		}
	}


}

int Tools_Tim2_Writer::GetMipMapHeaderSize(int iNumMipMaps)
{
	if (iNumMipMaps<2) return 0;
	int size = 2*sizeof(unsigned __int64);
	size+=iNumMipMaps*sizeof(unsigned __int32);
	//aligned to 16 bytes

	size = (size+15)&~15;
	return size;
}


#ifdef TOOLS_TIM2_WRITEBMPS
#include "Rendering/Asura_Bmp.h"

void Tools_Tim2_Writer::WriteBitmaps(const char* pszTimName)
{
	unsigned long uBytesWritten;

	for(int iImage=0;iImage<NumMipMaps;iImage++)
	{
		//generate filename
		char szFilename[MAX_PATH];
		strcpy(szFilename,pszTimName);

		//remove extension , and add identifier for mip map level ,and .bmp extension
		char* pcDot = strrchr(szFilename,'.');
		if(pcDot) *pcDot = 0;

		char* pszEndOfName = szFilename + strlen(szFilename);
		sprintf(pszEndOfName,"_Mip%d.bmp",iImage);

		Tools_Tim2_ImageData* pxImage = &Images[iImage];

		//open file for writing
		HANDLE hFile = CreateFile(szFilename,GENERIC_WRITE,0,0,CREATE_ALWAYS,0,0);
		if(hFile == INVALID_HANDLE_VALUE)
		{
			Asura_Assert(0,"Failed to save Bmp");
			continue;
		}

		//fill in header
		BMPHEADER2 xBmp;

		xBmp.Header.Type      = 'B'+'M'*256;
		xBmp.Header.Reserved1 = 0;
		xBmp.Header.Reserved2 = 0;
		xBmp.Header.Offset    = 14+40+0;

	

		xBmp.WinInfo.Size          = 40;
		xBmp.WinInfo.Width         = pxImage->Width;
		xBmp.WinInfo.Height        = pxImage->Height;
		xBmp.WinInfo.Planes        = 1;
		xBmp.WinInfo.BitCount      = 24;
		xBmp.WinInfo.Compression   = 0;
		xBmp.WinInfo.SizeImage     = pxImage->Width*pxImage->Height*3;
		xBmp.WinInfo.XPelsPerMeter = xBmp.WinInfo.Width;
		xBmp.WinInfo.YPelsPerMeter = xBmp.WinInfo.Height;
		xBmp.WinInfo.ClrUsed       = 0;
		xBmp.WinInfo.ClrImportant  = 0;

		xBmp.Header.FileSize  = xBmp.WinInfo.SizeImage + xBmp.Header.Offset + 8;

		WriteFile(hFile,&xBmp.Header.Type,2,&uBytesWritten,0);
		WriteFile(hFile,&xBmp.Header.FileSize,4,&uBytesWritten,0);
		WriteFile(hFile,&xBmp.Header.Reserved1,2,&uBytesWritten,0);
		WriteFile(hFile,&xBmp.Header.Reserved2,2,&uBytesWritten,0);
		WriteFile(hFile,&xBmp.Header.Offset,4,&uBytesWritten,0);

		WriteFile(hFile,&xBmp.WinInfo,sizeof(xBmp.WinInfo),&uBytesWritten,0);

		//allocate buffer for bmp data
		unsigned char* pucBuffer = new unsigned char[xBmp.WinInfo.SizeImage];
		unsigned char* pucBufferPos = pucBuffer;

		for(int i=pxImage->Height-1;i>=0;i--)
		{
			unsigned int* puSource = &pxImage->PixelData[i*pxImage->Width];
			
			for(int j=0;j<pxImage->Width;j++,puSource++)
			{
				unsigned int uColour = *puSource;
				if(Palette)
				{
					uColour = Palette[uColour];
				}
				*pucBufferPos++ = (unsigned char)(uColour & 0xff);
				*pucBufferPos++ = (unsigned char)((uColour>>8) & 0xff);
				*pucBufferPos++ = (unsigned char)((uColour>>16) & 0xff);
			}
		}

		WriteFile(hFile,pucBuffer,xBmp.WinInfo.SizeImage,&uBytesWritten,0);
		delete [] pucBuffer;

		CloseHandle(hFile);
	}
}
#endif
