#include #include #include #include #include #include #include #include #include #include "error.h" #include "getopt.h" #include "palette.h" #include "inifile.h" #include "pcx.h" #include "lbm.h" #include "misc.h" #include "gfx.h" #include "debug4g.h" #include "textio.h" #define kMaxTiles 4096 short tilesizx[kMaxTiles]; short tilesizy[kMaxTiles]; long picanm[kMaxTiles]; struct FNODE { FNODE *next; char name[1]; }; FNODE head = { &head, "" }; FNODE *tail = &head; enum FILE_TYPE { FT_NONE, FT_PAL, FT_PCX, FT_LBM, FT_ART, }; static struct { char *ext; FILE_TYPE fileType; } ftTable[] = { { ".ART", FT_ART }, { ".LBM", FT_LBM }, { ".PAL", FT_PAL }, { ".PCX", FT_PCX }, { ".PCC", FT_PCX }, }; int nWeightR, nWeightG, nWeightB; BOOL ExcludeColor[256]; BYTE RemapColor[256]; int URemapColor[256]; PALETTE srcPal, destPal, thisPal; IniFile paltoolINI("PALTOOL.INI"); char palName[_MAX_PATH]; char bakname[_MAX_PATH], tempName[_MAX_PATH]; BOOL showImages = FALSE; /******************************************************************************* FUNCTION: ShowBanner() DESCRIPTION: Show application banner *******************************************************************************/ void ShowBanner( void ) { tioPrint("ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ"); tioPrint(" Image Remap Tool Version 2.0 Copyright (c) 1995 Q Studios Corporation"); tioPrint("ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ"); } /******************************************************************************* FUNCTION: ShowUsage() DESCRIPTION: Display command-line parameter usage, then exit *******************************************************************************/ void ShowUsage(void) { tioPrint("Syntax: REMAP [options] files"); tioPrint(" /Fxxx Load palette from file [.PAL]"); tioPrint(" /Mx,y Map color x to color y"); tioPrint(" /Oa[-b] Color(s) in dest palette is off limits"); tioPrint(""); tioPrint("Supported file types are: PCX, LBM, ART"); tioPrint(""); exit(0); } /******************************************************************************* FUNCTION: QuitMessage() DESCRIPTION: Display a printf() style message, the exit with code 1 *******************************************************************************/ void QuitMessage(char * fmt, ...) { char msg[80]; va_list argptr; va_start( argptr, fmt ); vsprintf( msg, fmt, argptr ); va_end(argptr); tioPrint(msg); exit(1); } /******************************************************************************* FUNCTION: FindClosestColor() DESCRIPTION: Finds the closest color in RGB using the weights in nWeightR, nWeightG, and nWeightB. RETURNS: Index of the closest color NOTES: Colors marked with a TRUE in ExcludeColor[n] are not considered. *******************************************************************************/ BYTE FindClosestColor( int r, int g, int b ) { int i; int dr, dg, db, dist, matchDist, match; matchDist = 0x7FFFFFFF; for ( i = 0; i < 256; i++ ) { if ( ExcludeColor[i] ) continue; dist = 0; dg = (int)destPal[i].g - g; dist += nWeightG * dg * dg; if ( dist >= matchDist ) continue; dr = (int)destPal[i].r - r; dist += nWeightR * dr * dr; if ( dist >= matchDist ) continue; db = (int)destPal[i].b - b; dist += nWeightB * db * db; if ( dist >= matchDist ) continue; matchDist = dist; match = i; if (dist == 0) break; } return (BYTE)match; } /******************************************************************************* FUNCTION: BuildRemapTable() DESCRIPTION: Create the lookup table to remap from srcPal to destPal *******************************************************************************/ void BuildRemapTable(void) { for (int i = 0; i < 256; i++) { if ( URemapColor[i] < 0 ) RemapColor[i] = FindClosestColor(srcPal[i].r, srcPal[i].g, srcPal[i].b); else RemapColor[i] = (BYTE)URemapColor[i]; } } void RemapBuffer( BYTE *buffer, int length, BYTE *remapTable ); #pragma aux RemapBuffer =\ " xor eax,eax",\ "loop1:",\ " mov al,[edx]",\ " mov al,[ebx+eax]",\ " mov [edx],al",\ " inc edx",\ " dec ecx",\ " jnz loop1",\ parm [edx][ecx][ebx]\ modify [eax edx]\ FILE_TYPE GetFileType( char *filename ) { char ext[_MAX_EXT]; _splitpath(filename, NULL, NULL, NULL, ext); for ( int i = 0; i < LENGTH(ftTable); i++ ) { if ( stricmp(ext, ftTable[i].ext) == 0 ) return ftTable[i].fileType; } return FT_NONE; } void GetNewPalette(void) { int width = 0, height = 0; // not really used, but necessary for ReadXXX call switch( GetFileType(palName) ) { case FT_PAL: if ( !FileLoad(palName, destPal, sizeof(destPal)) ) QuitMessage("Couldn't open palette file '%s'", palName); break; case FT_PCX: { int r = ReadPCX(palName, destPal, &width, &height, NULL); if ( r != PCX_OKAY ) QuitMessage("Error reading palette from %s: code = %d", palName, r); break; } case FT_LBM: { int r = ReadLBM(palName, destPal, &width, &height, NULL); if ( r != LBM_OKAY ) QuitMessage("Error reading palette from %s: code = %d", palName, r); break; } } } void DisplayImage( BYTE *bits, int width, int height ) { int size = width * height; QBITMAP *qbmSource = (QBITMAP *)malloc(sizeof(QBITMAP) + size); dassert(qbmSource != NULL); memcpy(qbmSource->data, bits, size); qbmSource->bitModel = BM_RAW; qbmSource->tcolor = 0; qbmSource->cols = (short)width; qbmSource->rows = (short)height; qbmSource->stride = (short)width; gfxDrawBitmap(qbmSource, 160 - width / 2, 100 - height / 2); free(qbmSource); } void RemapART( char *filename ) { int hInFile, hOutFile; int tileStart, tileEnd; long artversion, numtiles; char artPalName[_MAX_PATH]; strcpy(artPalName, paltoolINI.GetKeyString(NULL, "GamePal", "")); if ( !FileLoad(artPalName, thisPal, sizeof(thisPal)) ) QuitMessage("Couldn't open palette file '%s'", artPalName); if ( memcmp(srcPal, thisPal, sizeof(PALETTE)) != 0 ) { memcpy(srcPal, thisPal, sizeof(PALETTE)); BuildRemapTable(); } hInFile = open(filename, O_BINARY | O_RDWR); if (hInFile == -1) QuitMessage("Error opening %s", filename); tmpnam(tempName); hOutFile = open(tempName, O_CREAT | O_WRONLY | O_BINARY | O_TRUNC, S_IWUSR); if ( hOutFile == -1 ) QuitMessage("Error creating temporary file"); read(hInFile, &artversion, sizeof(artversion)); read(hInFile, &numtiles, sizeof(numtiles)); read(hInFile, &tileStart, sizeof(tileStart)); read(hInFile, &tileEnd, sizeof(tileEnd)); int nTiles = tileEnd - tileStart + 1; read(hInFile, &tilesizx[tileStart], nTiles * sizeof(tilesizx[0])); read(hInFile, &tilesizy[tileStart], nTiles * sizeof(tilesizy[0])); read(hInFile, &picanm[tileStart], nTiles * sizeof(picanm[0])); write(hOutFile, &artversion, sizeof(artversion)); write(hOutFile, &numtiles, sizeof(numtiles)); write(hOutFile, &tileStart, sizeof(tileStart)); write(hOutFile, &tileEnd, sizeof(tileEnd)); write(hOutFile, &tilesizx[tileStart], nTiles * sizeof(tilesizx[0])); write(hOutFile, &tilesizy[tileStart], nTiles * sizeof(tilesizy[0])); write(hOutFile, &picanm[tileStart], nTiles * sizeof(picanm[0])); for (int i = tileStart; tioGauge(i - tileStart, nTiles); i++) { int nSize = tilesizx[i] * tilesizy[i]; if (nSize == 0) continue; BYTE *pBits = (BYTE *)malloc(nSize); dassert(pBits != NULL); read(hInFile, pBits, nSize); RemapBuffer(pBits, nSize, RemapColor); write(hOutFile, pBits, nSize); if ( showImages ) DisplayImage(pBits, tilesizy[i], tilesizx[i]); free(pBits); } close(hInFile); close(hOutFile); // backup the existing sequence strcpy(bakname, filename); ChangeExtension(bakname, ".BAK"); unlink(bakname); rename(filename, bakname); rename(tempName, filename); } void RemapPCX( char *filename ) { int srcWidth = 0, srcHeight = 0; int nResult; nResult = ReadPCX(filename, NULL, &srcWidth, &srcHeight, NULL); if (nResult != 0) QuitMessage("Problem reading PCX file %s: error=%d", filename, nResult); BYTE *pBits = NULL; nResult = ReadPCX(filename, thisPal, &srcWidth, &srcHeight, &pBits); if (nResult != 0) QuitMessage("Problem reading PCX file %s: error=%d", filename, nResult); if ( memcmp(srcPal, thisPal, sizeof(PALETTE)) != 0 ) { memcpy(srcPal, thisPal, sizeof(PALETTE)); BuildRemapTable(); } RemapBuffer(pBits, srcWidth * srcHeight, RemapColor); if ( showImages ) DisplayImage(pBits, srcWidth, srcHeight); tmpnam(tempName); nResult = WritePCX(tempName, destPal, srcWidth, srcHeight, pBits); if (nResult != 0) QuitMessage("Problem writing temporary PCX: error=%d", nResult); // backup the existing sequence strcpy(bakname, filename); ChangeExtension(bakname, ".BAK"); unlink(bakname); rename(filename, bakname); rename(tempName, filename); free(pBits); } void RemapLBM( char *filename ) { int srcWidth = 0, srcHeight = 0; int nResult; nResult = ReadLBM(filename, NULL, &srcWidth, &srcHeight, NULL); if (nResult != 0) QuitMessage("Problem reading LBM file %s: error=%d", filename, nResult); BYTE *pBits = NULL; nResult = ReadLBM(filename, thisPal, &srcWidth, &srcHeight, &pBits); if (nResult != 0) QuitMessage("Problem reading LBM file %s: error=%d", filename, nResult); if ( memcmp(srcPal, thisPal, sizeof(PALETTE)) != 0 ) { memcpy(srcPal, thisPal, sizeof(PALETTE)); BuildRemapTable(); } RemapBuffer(pBits, srcWidth * srcHeight, RemapColor); if ( showImages ) DisplayImage(pBits, srcWidth, srcHeight); free(pBits); } void ProcessFile( char *filename ) { tioPrint("Processing %s", filename); FILE_TYPE fileType = GetFileType(filename); if ( fileType == FT_NONE ) QuitMessage("Don't know how to handle file %s", filename); // determine the size of the source image switch ( fileType ) { case FT_ART: RemapART(filename); break; case FT_LBM: RemapLBM(filename); break; case FT_PCX: RemapPCX(filename); break; } } void Initialize(void) { for (int i = 0; i < 256; i++) { ExcludeColor[i] = FALSE; URemapColor[i] = -1; } } void InsertFilename( char *filename ) { FNODE *n = (FNODE *)malloc(sizeof(FNODE) + strlen(filename)); strcpy(n->name, filename); // insert the node at the tail, so it stays in order n->next = tail->next; tail->next = n; tail = n; } void ProcessArgument(char *s) { char filespec[_MAX_PATH]; char buffer[_MAX_PATH2]; char path[_MAX_PATH]; strcpy(filespec, s); char *drive, *dir; // separate the path from the filespec _splitpath2(s, buffer, &drive, &dir, NULL, NULL); _makepath(path, drive, dir, NULL, NULL); struct find_t fileinfo; unsigned r = _dos_findfirst(s, _A_NORMAL, &fileinfo); if (r != 0) tioPrint("%s not found", s); while ( r == 0 ) { strcpy(filespec, path); strcat(filespec, fileinfo.name); InsertFilename(filespec); r = _dos_findnext( &fileinfo ); } _dos_findclose(&fileinfo); } /*********************************************************************** * Process command line arguments **********************************************************************/ void ParseOptions( void ) { enum { kSwitchHelp, kSwitchFile, kSwitchMap, kSwitchOmit, }; static SWITCH switches[] = { { "?", kSwitchHelp, FALSE }, { "F", kSwitchFile, TRUE }, { "M", kSwitchMap, TRUE }, { "O", kSwitchOmit, TRUE }, { NULL, 0, FALSE }, }; char buffer[256]; int r; while ( (r = GetOptions(switches)) != GO_EOF ) { switch (r) { case GO_INVALID: sprintf(buffer, "Invalid argument: %s", OptArgument); ThrowError(buffer, ES_ERROR); break; case GO_FULL: ProcessArgument(OptArgument); break; case kSwitchHelp: ShowUsage(); break; case kSwitchFile: strcpy(palName, OptArgument); AddExtension(palName, ".PAL"); break; case kSwitchMap: { int nFrom, nTo; if ( sscanf(OptArgument, "%i,%i", &nFrom, &nTo) != 2 ) QuitMessage("Syntax error: %s", OptArgument); URemapColor[nFrom] = nTo; break; } case kSwitchOmit: { int nFrom, nTo; switch ( sscanf(OptArgument, "%i-%i", &nFrom, &nTo) ) { case 1: ExcludeColor[nFrom] = TRUE; break; case 2: while (nFrom <= nTo) ExcludeColor[nFrom++] = TRUE; break; default: QuitMessage("Syntax error: %s", OptArgument); } break; } } } } void main( int argc /*, char *argv[]*/) { tioInit(0); ShowBanner(); if (argc < 2) ShowUsage(); nWeightR = paltoolINI.GetKeyInt(NULL, "WeightR", 1); nWeightG = paltoolINI.GetKeyInt(NULL, "WeightG", 1); nWeightB = paltoolINI.GetKeyInt(NULL, "WeightB", 1); Initialize(); ParseOptions(); if ( head.next == &head ) QuitMessage("No source file specified"); if ( palName[0] == '\0' ) QuitMessage("You must specify a new palette."); GetNewPalette(); int oldMode = gGetMode(); if (!gFindMode(320, 200, 256, CHAIN256)) QuitMessage("Helix driver not found"); if ( showImages ) { Video.SetMode(); gfxSetClip(0, 0, 320, 200); gSetDACRange(0, 256, destPal); } // process the file list for (FNODE *n = head.next; n != &head; n = n->next) ProcessFile(n->name); if ( showImages ) gSetMode(oldMode); }