/*
**	Command & Conquer Generals Zero Hour(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

// FILE: ImagePacker.cpp //////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
//                                                                          
//                       Westwood Studios Pacific.                          
//                                                                          
//                       Confidential Information                           
//                Copyright (C) 2001 - All Rights Reserved                  
//                                                                          
//-----------------------------------------------------------------------------
//
// Project:    ImagePacker
//
// File name:  ImagePacker.cpp
//
// Created:    Colin Day, August 2001
//
// Desc:       Entry point for the image packer.  This program takes
//						 separate image files and combines them into a single 
//						 image as close as possible so that we can conserve texture
//						 memory
//
//-----------------------------------------------------------------------------
///////////////////////////////////////////////////////////////////////////////

// SYSTEM INCLUDES ////////////////////////////////////////////////////////////
#include <stdio.h>
#include <io.h>
#include <assert.h>

// USER INCLUDES //////////////////////////////////////////////////////////////
#include "Common/Debug.h"
#include "WWLib/Targa.h"
#include "Resource.h"
#include "ImagePacker.h"
#include "WinMain.h"
#include "WindowProc.h"

// DEFINES ////////////////////////////////////////////////////////////////////
char *gAppPrefix = "ip_";	// So IP can have a different debug log file name if we need it.

// PRIVATE TYPES //////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// PRIVATE DATA ///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
ImagePacker *TheImagePacker = NULL;

// PUBLIC DATA ////////////////////////////////////////////////////////////////

// PRIVATE PROTOTYPES /////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// PRIVATE FUNCTIONS //////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

// ImagePacker::createNewTexturePage ==========================================
/** Create a new texture page and add to the list */
//=============================================================================
TexturePage *ImagePacker::createNewTexturePage( void )
{
	TexturePage *page;

	// allocate new page
	page = new TexturePage( getTargetWidth(), getTargetHeight() );
	if( page == NULL )
	{

		DEBUG_ASSERTCRASH( page, ("Unable to allocate new texture page.\n") );
		return NULL;

	}  // end if

	// link page to list
	page->m_prev = NULL;
	page->m_next = m_pageList;
	if( m_pageList )
		m_pageList->m_prev = page;
	m_pageList = page;

	// add the tail pointer if this is the first page
	if( m_pageTail == NULL )
		m_pageTail = page;

	// we got a new page now
	m_pageCount++;

	// set page id as the current page count
	page->setID( m_pageCount );

	return page;

}  // end createNewTexturePage

// ImagePacker::validateImages ================================================
/** Check all the images in the image list, if any of them cannot be
	* processed we will flag them as so.  If we have some images that can't
	* be processed, we will warn the user of these images and ask them
	* whether or not to proceed.
	*
	* Returns TRUE to proceed
	* Returns FALSE to cancel build
	*/
//=============================================================================
Bool ImagePacker::validateImages( void )
{
	UnsignedInt i;
	ImageInfo *image;
	Bool errors = FALSE;
	Bool proceed = TRUE;

	// loop through all images
	for( i = 0; i < m_imageCount; i++ )
	{

		// get this image
		image = m_imageList[ i ];

		// sanity
		if( image == NULL )
		{

			DEBUG_ASSERTCRASH( image, ("Image in imagelist is NULL") );
			continue;  // should never happen

		}  // end if

		//
		// if this image is too big to fit in the target page size as a whole
		// then there is nothing we can do about it
		//
		if( image->m_size.x > getTargetWidth() ||
				image->m_size.y > getTargetHeight() )
		{

			errors = TRUE;
			BitSet( image->m_status, ImageInfo::TOOBIG );
			BitSet( image->m_status, ImageInfo::CANTPROCESS );

		}  // end if

		//
		// if this image is not the right format we can't process it, at 
		// present we only understand 32 and 24 bit images
		//
		if( image->m_colorDepth != 32 && image->m_colorDepth != 24 )
		{
		
			errors = TRUE;
			BitSet( image->m_status, ImageInfo::INVALIDCOLORDEPTH );
			BitSet( image->m_status, ImageInfo::CANTPROCESS );
			
		}  // end if

	}  // end for i

	//
	// if we have errors, build a list and show them to the user
	//
	if( errors == TRUE )
	{

		proceed = DialogBox( ApplicationHInstance, 
												 (LPCTSTR)IMAGE_ERRORS,
												 TheImagePacker->getWindowHandle(), 
												 (DLGPROC)ImageErrorProc );

	}  // end if

	return proceed;

}  // end validateImages

// ImagePacker::packImages ====================================================
/** Pack all the images in the image list, starting from the top and
	* working from there */
//=============================================================================
Bool ImagePacker::packImages( void )
{
	UnsignedInt i;
	TexturePage *page = NULL;
	ImageInfo *image = NULL;

	//
	// first sanity check all images loaded, if there are images that cannot
	// be processed the user will be given a list of these and asked wether
	// or not to proceed
	//
	Bool proceed;
	proceed = validateImages();
	if( proceed == FALSE )
	{

		statusMessage( "Build Cancelled By User." );
		return FALSE;

	}  // end if

	// loop through all images
	for( i = 0; i < m_imageCount; i++ )
	{

		// update status
		sprintf( m_statusBuffer, "Fitting Image %d of %d.", i, m_imageCount );
		statusMessage( m_statusBuffer );
		 
		// get this image out of the list
		image = m_imageList[ i ];

		// ignore images that we cannot process
		if( BitTest( image->m_status, ImageInfo::CANTPROCESS) )
			continue;

		// try to put image on each page
		for( page = m_pageTail; page; page = page->m_prev )
		{

			if( page->addImage( image ) == TRUE )
				break;  // page added, stop trying to add into pages

		}  // end for page

		// if image was not able to go on any existing page create a new page for it
		if( page == NULL )
		{

			page = createNewTexturePage();
			if( page == NULL )
				return FALSE;

			// try to add the image to this page
			if( page->addImage( image ) == FALSE )
			{
				char buffer[ _MAX_PATH ];

				sprintf( buffer, "Unable to add image '%s' to a brand new page!\n", image->m_path );
				DEBUG_ASSERTCRASH( 0, (buffer) );
				MessageBox( NULL, buffer, "Internal Error", MB_OK | MB_ICONERROR );
				return FALSE;

			}  // end if

		}  // end if

	}  // end for i

	return TRUE;  // success

}  // end packImages

// ImagePacker::writeFinalTextures ============================================
/** Generate and write the final textures to the output directory
	* of the packed images along with a definition file for which images
	* are where on the page */
//=============================================================================
void ImagePacker::writeFinalTextures( void )
{
	TexturePage *page;
	Bool errors = FALSE;
	char buffer[ 128 ];

	//
	// go through each page, let's start from the end of the list since
	// that's where we packed first, but it doesn't matter
	//
	for( page = m_pageTail; page; page = page->m_prev )
	{

		// update status message
		sprintf( buffer, "Generating texture #%d of %d.", 
						 page->getID(), m_pageCount );
		statusMessage( buffer );

		// generate the final texture for this page
		if( page->generateTexture() == FALSE )
		{
			
			errors = TRUE;
			continue;  // could not generate this page, but try to continue

		}  // end if

		//
		// write this page out to a file using the filename given by
		// the user and the texture page ID to keep it unique
		//
		if( page->writeFile( m_outputFile ) == FALSE )
		{

			errors = TRUE;
			continue;  // could not write page, but try to go on

		}  // end if

	}  // end for page

	// check for any errors and notify the user
	if( errors == TRUE )
	{

		DialogBox( ApplicationHInstance, 
							 (LPCTSTR)PAGE_ERRORS,
							 TheImagePacker->getWindowHandle(), 
							 (DLGPROC)PageErrorProc );

	}  // end if

}  // end writeFinalTextures

// sortImageCompare ===========================================================
/** Compare function for qsort
	* -1 item1 less than item2 
	*	0 item1 identical to item2 
	*	1 item1 greater than item2
	*/
//=============================================================================
static Int sortImageCompare( const void *aa, const void *bb )
{
	const ImageInfo **a = (const ImageInfo **)aa;
	const ImageInfo **b = (const ImageInfo **)bb;

	if( (*a)->m_area < (*b)->m_area )
		return 1;
	else if( (*a)->m_area > (*b)->m_area )
		return -1;
	else
		return 0;

}  // end sortImageCompare

// ImagePacker::sortImageList =================================================
/** Sort the image list */
//=============================================================================
void ImagePacker::sortImageList( void )
{

	// sort all images so that largest area ones are first
	qsort( (void *)m_imageList, m_imageCount, sizeof( ImageInfo *), sortImageCompare );

}  // end sortImageList

// ImagePacker::addImagesInDirectory ==========================================
/** Add all the images in the specified directory */
//=============================================================================
void ImagePacker::addImagesInDirectory( char *dir )
{

	// sanity
	if( dir == NULL )
		return;

	char currDir[ _MAX_PATH ];
	char filePath[ _MAX_PATH ];
	WIN32_FIND_DATA item;  // search item
	HANDLE hFile;  // handle for search resources
	Int len;

	// save the current directory
	GetCurrentDirectory( _MAX_PATH, currDir );

	// change into the directory
	SetCurrentDirectory( dir );

	// go through each item in the output directory		
	hFile = FindFirstFile( "*", &item);
	if( hFile != INVALID_HANDLE_VALUE )
	{  

		// if this is a file count it
		if( !(item.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
				strcmp( item.cFileName, "." ) && 
				strcmp( item.cFileName, ".." ) )
		{

			len = strlen( item.cFileName );
			if( len > 4 && 
					item.cFileName[ len - 4 ] == '.' &&
					(item.cFileName[ len - 3 ] == 't' || item.cFileName[ len - 3 ] == 'T') &&
					(item.cFileName[ len - 2 ] == 'g' || item.cFileName[ len - 2 ] == 'G') &&
					(item.cFileName[ len - 1 ] == 'a' || item.cFileName[ len - 1 ] == 'A') )
			{

				sprintf( filePath, "%s%s", dir, item.cFileName );
				addImage( filePath );

			}  // end if

		}  // end if

		// find the rest of the files
		while( FindNextFile( hFile, &item ) != 0 )
		{

			// if this is a file count it
			if( !(item.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
					strcmp( item.cFileName, "." ) && 
					strcmp( item.cFileName, ".." ) )
			{

				len = strlen( item.cFileName );
				if( len > 4 && 
						item.cFileName[ len - 4 ] == '.' &&
						(item.cFileName[ len - 3 ] == 't' || item.cFileName[ len - 3 ] == 'T') &&
						(item.cFileName[ len - 2 ] == 'g' || item.cFileName[ len - 2 ] == 'G') &&
						(item.cFileName[ len - 1 ] == 'a' || item.cFileName[ len - 1 ] == 'A') )
				{

					sprintf( filePath, "%s%s", dir, item.cFileName );
					addImage( filePath );

				}  // end if

			}  // end if

		}  // end while

		// close search
		FindClose( hFile );

	}  //end if, items found

	// restore our current directory
	SetCurrentDirectory( currDir );

}  // end addImagesInDirectory

// ImagePacker::checkOutputDirectory ==========================================
/** Verify that there are no files in the output directory ... if there
	* are give the user the option to delete them, cancel the operation,
	* or proceed with possibly overwriting any files there
	*
	* Returns TRUE to proceed with the process, FALSE if the user wants
	* to cancel the process
	*/
//=============================================================================
Bool ImagePacker::checkOutputDirectory( void )
{
	WIN32_FIND_DATA item;  // search item
	HANDLE hFile;  // handle for search resources
	Int fileCount = 0;
	char currDir[ _MAX_PATH ];
	
	// get the working directory
	GetCurrentDirectory( _MAX_PATH, currDir );

	// create the output directory if it does not exist
	CreateDirectory( m_outputDirectory, NULL );

	// change into the output directory
	SetCurrentDirectory( m_outputDirectory );

	// go through each item in the output directory		
	hFile = FindFirstFile( "*", &item);
	if( hFile != INVALID_HANDLE_VALUE )
	{  

		// if this is a file count it
		if( !(item.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
				 strcmp( item.cFileName, "." ) && 
				 strcmp( item.cFileName, ".." ) )
			fileCount++;

		// find the rest of the files
		while( FindNextFile( hFile, &item ) != 0 )
		{

			// if this is a file count it
			if( !(item.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
					 strcmp( item.cFileName, "." ) && 
					 strcmp( item.cFileName, ".." ) )
				fileCount++;

		}  // end while

		// close search
		FindClose( hFile );

	}  //end if, items found

	// switch back to the current directory
	SetCurrentDirectory( currDir );

	if( fileCount != 0 )
	{
		char buffer[ 256 ];
		Int response;

		sprintf( buffer, "The output directory (%s) must be empty before proceeding.  Delete '%d' files and continue with build process?",
						 m_outputDirectory, fileCount );
		response = MessageBox( NULL, buffer, 
													 "Delete files to continue?", 
													 MB_YESNO | MB_ICONWARNING );

		// if they said no, do not delete the files and abort the pack process
		if( response == IDNO )
			return FALSE;

		//
		// they said yes, delete all the files in the output directory
		//

		// change into the output directory
		SetCurrentDirectory( m_outputDirectory );
		
		// go through each item in the output directory		
		hFile = FindFirstFile( "*", &item);
		if( hFile != INVALID_HANDLE_VALUE )
		{  

			// if this is a file count it
			if( !(item.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
					 strcmp( item.cFileName, "." ) && 
					 strcmp( item.cFileName, ".." ) )
				DeleteFile( item.cFileName );

			// find the rest of the files
			while( FindNextFile( hFile, &item ) != 0 )
			{

				// if this is a file count it
				if( !(item.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
						 strcmp( item.cFileName, "." ) && 
						 strcmp( item.cFileName, ".." ) )
					DeleteFile( item.cFileName );

			}  // end while

			// close search
			FindClose( hFile );

		}  //end if, items found

		// switch back to the current directory
		SetCurrentDirectory( currDir );

	}  // end if

	return TRUE;  // proceed

}  // end checkOutputDirectory

// ImagePacker::resetPageList =================================================
/** Clear the page list */
//=============================================================================
void ImagePacker::resetPageList( void )
{
	TexturePage *next;

	while( m_pageList )
	{

		next = m_pageList->m_next;
		delete m_pageList;
		m_pageList = next;

	}  // end while

	m_pageTail = NULL;
	m_pageCount = 0;
	m_targetPreviewPage = 1;

}  // end resetPageList

// ImagePacker::resetImageDirectoryList =======================================
/** Clear the image directory list */
//=============================================================================
void ImagePacker::resetImageDirectoryList( void )
{
	ImageDirectory *next;

	while( m_dirList )
	{

		next = m_dirList->m_next;
		delete m_dirList;
		m_dirList = next;
			
	}  // end while

	m_dirCount = 0;
	m_imagesInDirs = 0;

}  // end resetImageDirectoryList

// ImagePacker::resetImageList ================================================
/** Clear the image list */
//=============================================================================
void ImagePacker::resetImageList( void )
{

	if( m_imageList )
		delete [] m_imageList;
	m_imageList = NULL;
	m_imageCount = 0;

}  // end resetImageList

// ImagePacker::addDirectory ==================================================
/** Add the directory to the directory list, do not add it if it is already
	* in the directory list.  We want to have that sanity check so that
	* we can be assured that each image being added to the image list from
	* each directory will be unique and we therefore don't have to do
	* any further checking for duplicates */
//=============================================================================
void ImagePacker::addDirectory( char *path, Bool subDirs )
{
	char currDir[ _MAX_PATH ];
	WIN32_FIND_DATA item;  // search item
	HANDLE hFile;  // handle for search resources

	// santiy
	if( path == NULL )
		return;

	// check to see if path is already in list
	ImageDirectory *dir;
	for( dir = m_dirList; dir; dir = dir->m_next )
		if( stricmp( dir->m_path, path ) == 0 )
			return;  // already in list

	// save our current directory
	GetCurrentDirectory( _MAX_PATH, currDir );

	// set our directory to this one
	if( SetCurrentDirectory( path ) == 0 )
		return;  // directory does not exist

	// image is not in list, make a new entry and link to the list
	dir = new ImageDirectory;
	if( dir == NULL )
	{
		
		MessageBox( NULL, "Unable to allocate image directory", "Error", 
								MB_OK | MB_ICONERROR );
		return;

	}  // end if

	// allocate space for the path
	Int len = strlen( path );
	dir->m_path = new char[ len + 1 ];
	strcpy( dir->m_path, path );
	if( dir->m_path == NULL )
	{

		MessageBox( NULL, "Unable to allocate path for directory", "Error",
								MB_OK | MB_ICONERROR );
		delete dir;
		return;

	}  // end if

	// tie to list
	dir->m_prev = NULL;
	dir->m_next = m_dirList;
	if( m_dirList )
		m_dirList->m_prev = dir;
	m_dirList = dir;
	
	// increase our directory count
	m_dirCount++;

	// update status
	sprintf( m_statusBuffer, "Folder Added: %d.", m_dirCount );
	statusMessage( m_statusBuffer );

	// count how many image files are in this directory
	hFile = FindFirstFile( "*", &item);
	if( hFile != INVALID_HANDLE_VALUE )
	{  

		// if this is a file count it
		if( !(item.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
				strcmp( item.cFileName, "." ) && 
				strcmp( item.cFileName, ".." ) )
		{

			len = strlen( item.cFileName );
			if( len > 4 && 
					item.cFileName[ len - 4 ] == '.' &&
					(item.cFileName[ len - 3 ] == 't' || item.cFileName[ len - 3 ] == 'T') &&
					(item.cFileName[ len - 2 ] == 'g' || item.cFileName[ len - 2 ] == 'G') &&
					(item.cFileName[ len - 1 ] == 'a' || item.cFileName[ len - 1 ] == 'A') )
				dir->m_imageCount++;

		}  // end if

		// find the rest of the files
		while( FindNextFile( hFile, &item ) != 0 )
		{

			// if this is a file count it
			if( !(item.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
					strcmp( item.cFileName, "." ) && 
					strcmp( item.cFileName, ".." ) )
			{

				len = strlen( item.cFileName );
				if( len > 4 && 
						item.cFileName[ len - 4 ] == '.' &&
						(item.cFileName[ len - 3 ] == 't' || item.cFileName[ len - 3 ] == 'T') &&
						(item.cFileName[ len - 2 ] == 'g' || item.cFileName[ len - 2 ] == 'G') &&
						(item.cFileName[ len - 1 ] == 'a' || item.cFileName[ len - 1 ] == 'A') )
					dir->m_imageCount++;

			}  // end if

		}  // end while

		// close search
		FindClose( hFile );

	}  //end if, items found

	// add the image count of this directory to the total image count
	m_imagesInDirs += dir->m_imageCount;

	// if we are adding subdirectories add them all
	if( subDirs )
	{
		char subDir[ _MAX_PATH ];

		// go through each item in the output directory		
		hFile = FindFirstFile( "*", &item);
		if( hFile != INVALID_HANDLE_VALUE )
		{  

			// if this is a file count it
			if( (item.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
					strcmp( item.cFileName, "." ) && 
					strcmp( item.cFileName, ".." ) )
			{

				sprintf( subDir, "%s%s\\", path, item.cFileName );
				addDirectory( subDir, subDirs );

			}  // end if

			// find the rest of the files
			while( FindNextFile( hFile, &item ) != 0 )
			{

				// if this is a file count it
				if( (item.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
						strcmp( item.cFileName, "." ) && 
						strcmp( item.cFileName, ".." ) )
				{

					sprintf( subDir, "%s%s\\", path, item.cFileName );
					addDirectory( subDir, subDirs );

				}  // end if

			}  // end while

			// close search
			FindClose( hFile );

		}  //end if, items found

	}  // end if

	// restore our current directory
	SetCurrentDirectory( currDir );

}  // end addDirectory

// ImagePacker::addImage ======================================================
/** Add the image to the image list */
//=============================================================================
void ImagePacker::addImage( char *path )
{

	// sanity
	if( path == NULL )
		return;

	// allocate a new entry
	ImageInfo *info = new ImageInfo;
	if( info == NULL )
	{

		MessageBox( NULL, "Unable to allocate image info", "Error",
								MB_OK | MB_ICONERROR );
		return;

	}  // end if

	// allocate space for the path
	Int len = strlen( path );
	info->m_path = new char[ len + 1 ];
	strcpy( info->m_path, path );
	if( info->m_path == NULL )
	{

		MessageBox( NULL, "Unable to allcoate image path info", "Error",
								MB_OK | MB_ICONERROR );
		delete info;
		return;

	}  // end if

	// load just the header information from the targa
	m_targa->Load( info->m_path, 0, TRUE );

	// get the data we need out of the targa header
	info->m_colorDepth = m_targa->Header.PixelDepth;
	info->m_size.x = m_targa->Header.Width;
	info->m_size.y = m_targa->Header.Height;
	info->m_area = info->m_size.x * info->m_size.y;

	// save the filename only without path
	Int i;
	char *c;
	for( i = len - 1; i >= 0; i-- )
	{

		if( path[ i ] == '\\' )
		{
			c = &path[ i + 1 ];
			break;
		}

	}  // end for i

	Int nameLen = strlen( c );
	info->m_filenameOnly = new char[ nameLen + 1 ];
	strcpy( info->m_filenameOnly, c );

	info->m_filenameOnlyNoExt = new char[ nameLen - 4 + 1 ];
	strncpy( info->m_filenameOnlyNoExt, c, nameLen - 4 );
	info->m_filenameOnlyNoExt[ nameLen - 4 ] = '\0';

	// assign to array
	m_imageList[ m_imageCount++ ] = info;

	// update status
	sprintf( m_statusBuffer, "Loading Image %d of %d.", 
					 m_imageCount, m_imagesInDirs );
	statusMessage( m_statusBuffer );

}  // end addImage

// ImagePacker::generateINIFile ===============================================
/** Generate the INI image file definition for the final packed images */
//=============================================================================
Bool ImagePacker::generateINIFile( void )
{
	FILE *fp;
	char filename[ _MAX_PATH ];

	// construct filename we'll use
	sprintf( filename, "%s%s.INI", m_outputDirectory, m_outputFile );

	// open the file
	fp = fopen( filename, "w" );
	if( fp == NULL )
	{
		char buffer[ _MAX_PATH + 64 ];

		sprintf( buffer, "Cannot open INI file '%s' for writing.", filename );
		MessageBox( NULL, buffer, "Error Opening File", MB_OK | MB_ICONERROR );
		return FALSE;

	}  // end if

	// print header for file
	fprintf( fp, "; ------------------------------------------------------------\n" );
	fprintf( fp, "; Do NOT edit by hand, ImagePacker.exe auto generated INI file\n" );
	fprintf( fp, "; ------------------------------------------------------------\n\n" );

	//
	// loop through all the pages so that we write image definitions that
	// are on the same page close together in the file, note we're
	// going backwards through the page list because page 1 is at the 
	// tail and I want them to print out in number order, but it
	// doesn't really matter
	//
	TexturePage *page;
	ImageInfo *image;
	for( page = m_pageTail; page; page = page->m_prev )
	{

		// ignore texture pages that generated errors
		if( BitTest( page->m_status, TexturePage::PAGE_ERROR ) )
			continue;

		// go through each image on this page
		for( image = page->getFirstImage(); 
				 image; 
				 image = image->m_nextPageImage )
		{

			//
			// write the item definition, note when we output the texture coords
			// we add on to the right and bottom to include that pixel in the
			// texture calculations ... need to do this since we are using a zero
			// based region for the "filled regions" in the image packer
			//
			fprintf( fp, "MappedImage %s\n", image->m_filenameOnlyNoExt );
			fprintf( fp, "  Texture = %s_%03d.tga\n", m_outputFile, page->getID() );
			fprintf( fp, "  TextureWidth = %d\n", page->getWidth() );
			fprintf( fp, "  TextureHeight = %d\n", page->getHeight() );
			fprintf( fp, "  Coords = Left:%d Top:%d Right:%d Bottom:%d\n",
							 image->m_pagePos.lo.x, image->m_pagePos.lo.y,
							 image->m_pagePos.hi.x + 1, image->m_pagePos.hi.y + 1 );
			fprintf( fp, "  Status = %s\n", 
							 BitTest( image->m_status, ImageInfo::ROTATED90C ) ? 
												"ROTATED_90_CLOCKWISE" : "NONE" );
			fprintf( fp, "End\n\n" );

		}  // end for image

	}  // end for page

	// close the file
	fclose( fp );

	return TRUE;  // success

}  // end generateINIFile

///////////////////////////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

// ImagePacker::getSettingsFromDialog =========================================
/** Given the current state of the option dialog passed in, get all the
	* settings we need for the image packer from the GUI and validate them */
//=============================================================================
Bool ImagePacker::getSettingsFromDialog( HWND dialog )
{
	Int i;

	// sanity
	if( dialog == NULL )
		return FALSE;

	// if we are using a user target image size, it must be a power of 2
	if( IsDlgButtonChecked( dialog, RADIO_TARGET_OTHER ) )
	{
		UnsignedInt size, val;
		Int bitCount = 0;

		size = GetDlgItemInt( dialog, EDIT_WIDTH, NULL, FALSE );
		for( val = size; val; val >>= 1 )
			if( BitTest( val, 0x1 ) )
				bitCount++;

		//
		// if bit count was not 1, this is not a power of 2 ... it also
		// guards us from entering a size of zero :)
		//
		if( bitCount != 1 )
		{

			MessageBox( NULL, "The target image size must be a power of 2.",
									"Must Be Power Of 2", MB_OK | MB_ICONERROR );
			return FALSE;

		}  // end if

		// set the size for the image packer
		setTargetSize( size, size );

	}  // end if
	else if( IsDlgButtonChecked( dialog, RADIO_128X128 ) )
		setTargetSize( 128, 128 );
	else if( IsDlgButtonChecked( dialog, RADIO_256X256 ) )
		setTargetSize( 256, 256 );
	else if( IsDlgButtonChecked( dialog, RADIO_512X512 ) )
		setTargetSize( 512, 512 );
	else
	{

		MessageBox( NULL, "Internal Error. Target Size Unknown.",
								"Error", MB_OK | MB_ICONERROR );
		return FALSE;

	}  // end else
	
	// get alpha option
	Bool outputAlpha = FALSE;
	if( IsDlgButtonChecked( dialog, CHECK_ALPHA ) == BST_CHECKED )
		outputAlpha = TRUE;
	TheImagePacker->setOutputAlpha( outputAlpha );

	// get create INI option
	Bool createINI = FALSE;
	if( IsDlgButtonChecked( dialog, CHECK_INI ) == BST_CHECKED )
		createINI = TRUE;
	TheImagePacker->setINICreate( createINI );

	// get preview with image option
	Bool useBitmap = FALSE;
	if( IsDlgButtonChecked( dialog, CHECK_BITMAP_PREVIEW ) == BST_CHECKED )
		useBitmap = TRUE;
	TheImagePacker->setUseTexturePreview( useBitmap );

	// get option to compress final textures
	Bool compress = FALSE;
	if( IsDlgButtonChecked( dialog, CHECK_COMPRESS ) == BST_CHECKED )
		compress = TRUE;
	TheImagePacker->setCompressTextures( compress );

	// get options for the gap options
	TheImagePacker->clearGapMethod( ImagePacker::GAP_METHOD_EXTEND_RGB );
	if( IsDlgButtonChecked( dialog, CHECK_GAP_EXTEND_RGB ) == BST_CHECKED )
		TheImagePacker->setGapMethod( ImagePacker::GAP_METHOD_EXTEND_RGB );
	TheImagePacker->clearGapMethod( ImagePacker::GAP_METHOD_GUTTER );
	if( IsDlgButtonChecked( dialog, CHECK_GAP_GUTTER ) == BST_CHECKED )
		TheImagePacker->setGapMethod( ImagePacker::GAP_METHOD_GUTTER );

	// get gutter size whether we are using that option or not
	Int gutter = GetDlgItemInt( dialog, EDIT_GUTTER, NULL, FALSE );
	if( gutter < 0 )
		gutter = 0;
	setGutter( gutter );
						
	// save the filename to output
	GetDlgItemText( dialog, EDIT_FILENAME, m_outputFile, MAX_OUTPUT_FILE_LEN - 1 );
	
	// check for illegal characters in the output name
	Int len = strlen( m_outputFile );
	for( i = 0; i < len; i++ )
	{
		char *illegal = "/\\:*?<>|";
		Int illegalLen = strlen( illegal );
		
		for( Int j = 0; j < illegalLen; j++ )
		{

			if( m_outputFile[ i ] == illegal[ j ] )
			{
				char buffer[ 256 ];

				sprintf( buffer, "Output filename '%s' contains one or more of the following illegal characters:\n\n%s", 
								 m_outputFile, illegal );
				MessageBox( NULL, buffer, "Illegal Filename", MB_OK | MB_ICONERROR );
				return FALSE;

			}  // end if

		}  // end for j

	}  // end for i

	// get the work on sub-folders option
	m_useSubFolders = IsDlgButtonChecked( dialog, CHECK_USE_SUB_FOLDERS );

	// clear our list of image directories
	resetImageDirectoryList();

	// set a status message
	statusMessage( "Gathering Directory Information, Please Wait ..." );

	// add all the image directories specified in the folder listbox
	Int count = SendDlgItemMessage( dialog, LIST_FOLDERS, LB_GETCOUNT, 0, 0 );
	char buffer[ _MAX_PATH ];
	for( i = 0; i < count; i++ )
	{

		// get text from the listbox
		SendDlgItemMessage( dialog, LIST_FOLDERS,		
												LB_GETTEXT, i, (LPARAM)buffer );

		// add the directory
		addDirectory( buffer, m_useSubFolders );

	}  // end for i

	// all done
	return TRUE;

}  // end getSettingsFromDialog

// ImagePacker::ImagePacker ===================================================
/** */
//=============================================================================
ImagePacker::ImagePacker( void )
{

	m_hWnd = NULL;
	m_targetSize.x = DEFAULT_TARGET_SIZE;
	m_targetSize.y = DEFAULT_TARGET_SIZE;
	m_useSubFolders = TRUE;
	strcpy( m_outputFile, "" );
	strcpy( m_outputDirectory, "" );
	m_dirList = NULL;
	m_dirCount = 0;
	m_imagesInDirs = 0;
	m_imageList = NULL;
	m_imageCount = 0;
	strcpy( m_statusBuffer, "" );
	m_pageList = NULL;
	m_pageTail = NULL;
	m_pageCount = 0;
	m_gapMethod = GAP_METHOD_EXTEND_RGB;
	m_gutterSize = 1;
	m_outputAlpha = TRUE;
	m_createINI = TRUE;

	m_targetPreviewPage = 1;
	m_hWndPreview = NULL;
	m_showTextureInPreview = FALSE;

	m_targa = NULL;
	m_compressTextures = FALSE;

}  // end ImagePacker

// ImagePacker::~ImagePacker ==================================================
/** */
//=============================================================================
ImagePacker::~ImagePacker( void )
{

	// delete our lists
	resetImageDirectoryList();
	resetImageList();
	resetPageList();

	// delete our targa header loader
	if( m_targa )
		delete m_targa;

}  // end ~ImagePacker

// ImagePacker::init ==========================================================
/** Initialize the image packer system */
//=============================================================================
Bool ImagePacker::init( void )
{

	// allocate a targa to read the headers for the images
	m_targa = new Targa;
	if( m_targa == NULL )
	{
		
		DEBUG_ASSERTCRASH( m_targa, ("Unable to allocate targa header during init\n") );
		MessageBox( NULL, "ImagePacker can't init, unable to create targa",
								"Internal Error", MB_OK | MB_ICONERROR );
		return FALSE;

	}  // end if

	return TRUE;

}  // end init

// ImagePacker::statusMessage =================================================
/** Status message for the program */
//=============================================================================
void ImagePacker::statusMessage( char *message )
{

	SetDlgItemText( getWindowHandle(), STATIC_STATUS, message );

}  // end statusMessage

// ImagePacker::process =======================================================
/** Run the packing process */
//=============================================================================
Bool ImagePacker::process( void )
{

	// build the output directory based on the base name of the output images
	char currDir[ _MAX_PATH ];
	GetCurrentDirectory( _MAX_PATH, currDir );
	sprintf( m_outputDirectory, "%s\\ImagePackerOutput\\", currDir );
	CreateDirectory( m_outputDirectory, NULL );

	// subdir of output directory based on output image name
	strcat( m_outputDirectory, m_outputFile );
	strcat( m_outputDirectory, "\\" );

	//
	// check for existing images in the output directory ... if we have
	// some then ask the user if they want to delete them or proceed with
	// a possible overwrite
	//
	if( checkOutputDirectory() == FALSE )
	{

		statusMessage( "Build Process Cancelled." );
		return FALSE;

	}  // end if

	// reset the contents of our image list and existing textures
	resetImageList();
	resetPageList();

	// set a status message
	statusMessage( "Gathering Image Information, Please Wait ..." );

	// allocate an array to hold all the images
	m_imageList = new ImageInfo *[ m_imagesInDirs ];

	// load our image list with all the art files from the specified directories
	ImageDirectory *dir;
	for( dir = m_dirList; dir; dir = dir->m_next )
		addImagesInDirectory( dir->m_path );

	// sort the images with the largest biggest images at the top of the list
	sortImageList();

	// pack all images
	if( packImages() )
	{

		// generate the actual final textures and write them out to the file
		writeFinalTextures();

		// generate the INI definition file if requested
		if( createINIFile() == TRUE )
			generateINIFile();

		// update preview window
		UpdatePreviewWindow();

		// all done
		sprintf( m_statusBuffer, "Image Packing Complete: '%d' Texture Pages Generated from '%d' Images in '%d' Folder(s)",
						 m_pageCount, m_imageCount, m_dirCount );
		statusMessage( m_statusBuffer );

	}  // end if

	return TRUE;

}  // end process

