1250 lines
34 KiB
C++

/*
** 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