Timings seem to be _really_ right this time.
[scummvm-innocent.git] / tools / agi-palex.py
blob1e0fa9d54ff391923555b705bcb81362f0f2eb42
1 #!/usr/bin/python
2 # Amiga AGI game palette extractor.
3 # Extracts palette from an Amiga AGI game's executable file.
4 # Initial version written during summer of 2007 by Buddha^.
5 # Somewhat optimized. Adding the preliminary palette test helped speed a lot.
6 # FIXME: Doesn't report anything about not found files when some files are found.
7 # An example: palex.py SQ2 PQ1 (When file SQ2 exists but PQ1 doesn't)
8 import struct, sys, os.path, glob
10 # Constants
11 maxComponentValue = 0xF
12 colorsPerPalette = 16
13 componentsPerColor = 3
14 bytesPerComponent = 2
15 bytesPerColor = componentsPerColor * bytesPerComponent
16 componentsPerPalette = colorsPerPalette * componentsPerColor
17 bytesPerPalette = componentsPerPalette * bytesPerComponent
18 encodedBlack = '\x00' * bytesPerColor
19 encodedWhite = (('\x00' * (bytesPerComponent - 1)) + ("%c" % maxComponentValue)) * componentsPerColor
20 decodedBlack = tuple([0 for x in range(componentsPerColor)])
21 decodedWhite = tuple([maxComponentValue for x in range(componentsPerColor)])
22 blackColorNum = 0
23 whiteColorNum = colorsPerPalette - 1
24 encodedBlackStart = blackColorNum * bytesPerColor
25 encodedBlackEnd = encodedBlackStart + bytesPerColor
26 encodedWhiteStart = whiteColorNum * bytesPerColor
27 encodedWhiteEnd = encodedWhiteStart + bytesPerColor
28 componentPrintFormat = "0x%1X"
29 arraynamePrefix = "amigaPalette"
31 def isColor12Bit(color):
32 """Is the color 12-bit (i.e. 4 bits per color component)?"""
33 for component in color:
34 if not (0 <= component <= maxComponentValue):
35 return False
36 return True
38 def printCommentLineList(lines):
39 """Prints list of lines inside a comment"""
40 if len(lines) > 0:
41 if len(lines) == 1:
42 print "// " + lines[0]
43 else:
44 print "/**"
45 for line in lines:
46 print " * " + line
47 print " */"
49 def printColor(color, tabulate = True, printLastComma = True, newLine = True):
50 """Prints color with optional start tabulation, comma in the end and a newline"""
51 result = ""
52 if tabulate:
53 result += "\t"
54 for component in color[:-1]:
55 result += ((componentPrintFormat + ", ") % component)
56 result += (componentPrintFormat % color[-1])
57 if printLastComma:
58 result += ","
59 if newLine:
60 print result
61 else:
62 print result,
64 def printPalette(palette, filename, arrayname):
65 """Prints out palette as a C-style array"""
66 # Print comments about the palette
67 comments = ["A 16-color, 12-bit RGB palette from an Amiga AGI game."]
68 comments.append("Extracted from file " + os.path.basename(filename))
69 printCommentLineList(comments)
71 # Print the palette as a C-style array
72 print "static const unsigned char " + arrayname + "[] = {"
73 for color in palette[:-1]:
74 printColor(color)
75 printColor(palette[-1], printLastComma = False)
76 print("};")
78 def isAmigaPalette(palette):
79 """Test if the given palette is an Amiga-style palette"""
80 # Palette must be of correct size
81 if len(palette) != colorsPerPalette:
82 return False
84 # First palette color must be black and last palette color must be black
85 if palette[whiteColorNum] != decodedWhite or palette[blackColorNum] != decodedBlack:
86 return False
88 # All colors must be 12-bit (i.e. 4 bits per color component)
89 for color in palette:
90 if not isColor12Bit(color):
91 return False
93 # All colors must be unique
94 if len(set(palette)) != colorsPerPalette:
95 return False
97 return True
99 def preliminaryPaletteTest(data, pos):
100 """Preliminary test for a palette (For speed's sake)."""
101 # Test that palette's last color is white
102 if data[pos + encodedWhiteStart : pos + encodedWhiteEnd] != encodedWhite:
103 return False
104 # Test that palette's first color is black
105 if data[pos + encodedBlackStart : pos + encodedBlackEnd] != encodedBlack:
106 return False
107 return True
109 def searchForAmigaPalettes(filename):
110 """Search file for Amiga AGI game palettes and return any found unique palettes"""
111 try:
112 file = None
113 foundPalettes = []
114 # Open file and read it into memory
115 file = open(filename, 'rb')
116 data = file.read()
117 palette = [decodedBlack for x in range(colorsPerPalette)]
118 # Search through the whole file
119 for searchPosition in range(len(data) - bytesPerPalette + 1):
120 if preliminaryPaletteTest(data, searchPosition):
121 # Parse possible palette from byte data
122 for colorNum in range(colorsPerPalette):
123 colorStart = searchPosition + colorNum * bytesPerColor
124 colorEnd = colorStart + bytesPerColor
125 # Parse color components as unsigned 16-bit big endian integers
126 color = struct.unpack('>' + 'H' * componentsPerColor, data[colorStart:colorEnd])
127 palette[colorNum] = color
128 # Save good candidates to a list
129 if isAmigaPalette(palette):
130 foundPalettes.append(tuple(palette))
131 # Close source file and return unique found palettes
132 file.close()
133 return set(foundPalettes)
134 except IOError:
135 if file != None:
136 file.close()
137 return None
139 # The main program starts here
140 if len(sys.argv) < 2 or sys.argv[1] == "-h" or sys.argv[1] == "--help":
141 quit("Usage: " + os.path.basename(sys.argv[0]) + " FILE [[FILE] ... [FILE]]\n" \
142 " Searches all FILE parameters for Amiga AGI game palettes\n" \
143 " and prints out any found candidates as C-style arrays\n" \
144 " with sequentially numbered names (" + arraynamePrefix + "1, " + arraynamePrefix + "2 etc).\n" \
145 " Supports wildcards.")
147 # Get the list of filenames (Works with wildcards too)
148 filenameList = []
149 for parameter in sys.argv[1:]:
150 filenameList.extend(glob.glob(parameter))
152 # Go through all the files and search for palettes
153 totalPalettesCount = 0
154 if len(filenameList) > 0:
155 negativeFiles = []
156 errorFiles = []
157 for filename in filenameList:
158 foundPalettes = searchForAmigaPalettes(filename)
159 if foundPalettes == None:
160 errorFiles.append(filename)
161 elif len(foundPalettes) == 0:
162 negativeFiles.append(filename)
163 else:
164 # Print out the found palettes
165 for palette in foundPalettes:
166 # Print palettes with sequentially numbered array names
167 totalPalettesCount = totalPalettesCount + 1
168 printPalette(palette, filename, arraynamePrefix + str(totalPalettesCount))
169 print "" # Print an extra newline to separate things
170 # Print comment about files we couldn't find any palettes in
171 if len(negativeFiles) > 0:
172 comments = []
173 comments.append("Couldn't find any palettes in the following files:")
174 comments.extend(negativeFiles)
175 printCommentLineList(comments)
176 print "" # Print an extra newline to separate things
177 # Print comment about errors handling files
178 if len(errorFiles) > 0:
179 comments = []
180 comments.append("Error handling the following files:")
181 comments.extend(errorFiles)
182 printCommentLineList(comments)
183 print "" # Print an extra newline to separate things
184 else:
185 printCommentLineList(["No files found"])