diff options
-rw-r--r-- | tools/palex.py | 179 |
1 files changed, 129 insertions, 50 deletions
diff --git a/tools/palex.py b/tools/palex.py index 6cff14dbda..8a2408105e 100644 --- a/tools/palex.py +++ b/tools/palex.py @@ -2,24 +2,49 @@ # Amiga AGI game palette extractor. # Extracts palette from an Amiga AGI game's executable file. # Initial version written during summer of 2007 by Buddha^. -# Unoptimized. -import struct, sys, os.path +# Somewhat optimized. Adding the preliminary palette test helped speed a lot. +# FIXME: Doesn't report anything about not found files when some files are found. +# An example: palex.py SQ2 PQ1 (When file SQ2 exists but PQ1 doesn't) +import struct, sys, os.path, glob # Constants -maxComponentValue = 0x0F +maxComponentValue = 0xF colorsPerPalette = 16 componentsPerColor = 3 bytesPerComponent = 2 -bytesPerColor = componentsPerColor * bytesPerComponent -paletteComponentCount = colorsPerPalette * componentsPerColor -paletteByteCount = paletteComponentCount * bytesPerComponent -black = tuple([0 for x in range(componentsPerColor)]) -white = tuple([maxComponentValue for x in range(componentsPerColor)]) +bytesPerColor = componentsPerColor * bytesPerComponent +componentsPerPalette = colorsPerPalette * componentsPerColor +bytesPerPalette = componentsPerPalette * bytesPerComponent +encodedBlack = '\x00' * bytesPerColor +encodedWhite = (('\x00' * (bytesPerComponent - 1)) + ("%c" % maxComponentValue)) * componentsPerColor +decodedBlack = tuple([0 for x in range(componentsPerColor)]) +decodedWhite = tuple([maxComponentValue for x in range(componentsPerColor)]) +blackColorNum = 0 +whiteColorNum = colorsPerPalette - 1 +encodedBlackStart = blackColorNum * bytesPerColor +encodedBlackEnd = encodedBlackStart + bytesPerColor +encodedWhiteStart = whiteColorNum * bytesPerColor +encodedWhiteEnd = encodedWhiteStart + bytesPerColor componentPrintFormat = "0x%1X" +arraynamePrefix = "amigaPalette" -def is12BitColor(color): - """Is color 12-bit (i.e. 4 bits per color component)?""" - return color == filter(lambda component: 0 <= component <= maxComponentValue, color) +def isColor12Bit(color): + """Is the color 12-bit (i.e. 4 bits per color component)?""" + for component in color: + if not (0 <= component <= maxComponentValue): + return False + return True + +def printCommentLineList(lines): + """Prints list of lines inside a comment""" + if len(lines) > 0: + if len(lines) == 1: + print "// " + lines[0] + else: + print "/**" + for line in lines: + print " * " + line + print " */" def printColor(color, tabulate = True, printLastComma = True, newLine = True): """Prints color with optional start tabulation, comma in the end and a newline""" @@ -36,10 +61,15 @@ def printColor(color, tabulate = True, printLastComma = True, newLine = True): else: print result, -def printPalette(palette, arrayName): +def printPalette(palette, filename, arrayname): """Prints out palette as a C-style array""" - print "/** A 16-color, 12-bit RGB palette from an Amiga AGI game. */" - print "static const unsigned char " + arrayName + "[] = {" + # Print comments about the palette + comments = ["A 16-color, 12-bit RGB palette from an Amiga AGI game."] + comments.append("Extracted from file " + os.path.basename(filename)) + printCommentLineList(comments) + + # Print the palette as a C-style array + print "static const unsigned char " + arrayname + "[] = {" for color in palette[:-1]: printColor(color) printColor(palette[-1], printLastComma = False) @@ -51,13 +81,13 @@ def isAmigaPalette(palette): if len(palette) != colorsPerPalette: return False - # First palette color must be black, last color white - if palette[0] != black or palette[-1] != white: + # First palette color must be black and last palette color must be black + if palette[whiteColorNum] != decodedWhite or palette[blackColorNum] != decodedBlack: return False # All colors must be 12-bit (i.e. 4 bits per color component) for color in palette: - if not is12BitColor(color): + if not isColor12Bit(color): return False # All colors must be unique @@ -66,41 +96,90 @@ def isAmigaPalette(palette): return True -# The main program starts here -if len(sys.argv) <= 2 or sys.argv[1] == "-h" or sys.argv[1] == "--help": - quit("Usage: " + os.path.basename(sys.argv[0]) + " FILE ARRAYNAME\n" \ - " Searches FILE for an Amiga AGI game palette\n" \ - " and prints out any found candidates as C-array style arrays with the name ARRAYNAME."); - -# Open file and read it into memory -file = open(sys.argv[1], 'rb') -data = file.read() -foundPalettes = [] -palette = [black for x in range(colorsPerPalette)] -# Search through the whole file -for searchPosition in range(len(data) - paletteByteCount + 1): - # Parse possible palette from byte data - for colorNum in range(colorsPerPalette): - paletteStart = searchPosition + colorNum * bytesPerColor - paletteEnd = paletteStart + bytesPerColor - # Parse color components as unsigned 16-bit big endian integers - color = struct.unpack('>' + 'H' * componentsPerColor, data[paletteStart:paletteEnd]); - palette[colorNum] = color; +def preliminaryPaletteTest(data, pos): + """Preliminary test for a palette (For speed's sake).""" + # Test that palette's last color is white + if data[pos + encodedWhiteStart : pos + encodedWhiteEnd] != encodedWhite: + return False + # Test that palette's first color is black + if data[pos + encodedBlackStart : pos + encodedBlackEnd] != encodedBlack: + return False + return True - # Save good candidates to a list - if isAmigaPalette(palette): - foundPalettes.append(tuple(palette)); +def searchForAmigaPalettes(filename): + """Search file for Amiga AGI game palettes and return any found unique palettes""" + try: + file = None + foundPalettes = [] + # Open file and read it into memory + file = open(filename, 'rb') + data = file.read() + palette = [decodedBlack for x in range(colorsPerPalette)] + # Search through the whole file + for searchPosition in range(len(data) - bytesPerPalette + 1): + if preliminaryPaletteTest(data, searchPosition): + # Parse possible palette from byte data + for colorNum in range(colorsPerPalette): + colorStart = searchPosition + colorNum * bytesPerColor + colorEnd = colorStart + bytesPerColor + # Parse color components as unsigned 16-bit big endian integers + color = struct.unpack('>' + 'H' * componentsPerColor, data[colorStart:colorEnd]) + palette[colorNum] = color + # Save good candidates to a list + if isAmigaPalette(palette): + foundPalettes.append(tuple(palette)) + # Close source file and return unique found palettes + file.close() + return set(foundPalettes) + except IOError: + if file != None: + file.close() + return None -# Close file -file.close() +# The main program starts here +if len(sys.argv) < 2 or sys.argv[1] == "-h" or sys.argv[1] == "--help": + quit("Usage: " + os.path.basename(sys.argv[0]) + " FILE [[FILE] ... [FILE]]\n" \ + " Searches all FILE parameters for Amiga AGI game palettes\n" \ + " and prints out any found candidates as C-style arrays\n" \ + " with sequentially numbered names (" + arraynamePrefix + "1, " + arraynamePrefix + "2 etc).\n" \ + " Supports wildcards.") -# Remove duplicates from found palettes list -uniqueFoundPalettes = set(foundPalettes) +# Get the list of filenames (Works with wildcards too) +filenameList = [] +for parameter in sys.argv[1:]: + filenameList.extend(glob.glob(parameter)) -# Print out the found unique palettes -if len(uniqueFoundPalettes) > 0: - for palette in uniqueFoundPalettes: - printPalette(palette, sys.argv[2]) +# Go through all the files and search for palettes +totalPalettesCount = 0 +if len(filenameList) > 0: + negativeFiles = [] + errorFiles = [] + for filename in filenameList: + foundPalettes = searchForAmigaPalettes(filename) + if foundPalettes == None: + errorFiles.append(filename) + elif len(foundPalettes) == 0: + negativeFiles.append(filename) + else: + # Print out the found palettes + for palette in foundPalettes: + # Print palettes with sequentially numbered array names + totalPalettesCount = totalPalettesCount + 1 + printPalette(palette, filename, arraynamePrefix + str(totalPalettesCount)) + print "" # Print an extra newline to separate things + # Print comment about files we couldn't find any palettes in + if len(negativeFiles) > 0: + comments = [] + comments.append("Couldn't find any palettes in the following files:") + comments.extend(negativeFiles) + printCommentLineList(comments) + print "" # Print an extra newline to separate things + # Print comment about errors handling files + if len(errorFiles) > 0: + comments = [] + comments.append("Error handling the following files:") + comments.extend(errorFiles) + printCommentLineList(comments) + print "" # Print an extra newline to separate things else: - print "Didn't find any Amiga AGI game palettes in file " + sys.argv[1] - print "Heuristic isn't perfect though, so caveat emptor" + printCommentLineList(["No files found"]) |