Logo Search packages:      
Sourcecode: scummvm version File versions  Download package

agi-palex.py

#!/usr/bin/python
# Amiga AGI game palette extractor.
# Extracts palette from an Amiga AGI game's executable file.
# Initial version written during summer of 2007 by Buddha^.
# 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     = 0xF
colorsPerPalette      = 16
componentsPerColor    = 3
bytesPerComponent     = 2
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 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"""
      result = ""
      if tabulate:
            result += "\t"
      for component in color[:-1]:
            result += ((componentPrintFormat + ", ") % component)
      result += (componentPrintFormat % color[-1])
      if printLastComma:
            result += ","
      if newLine:
            print result
      else:
            print result,

def printPalette(palette, filename, arrayname):
      """Prints out palette as a C-style array"""
      # 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)
      print("};")

def isAmigaPalette(palette):
      """Test if the given palette is an Amiga-style palette"""
      # Palette must be of correct size
      if len(palette) != colorsPerPalette:
            return False

      # 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 isColor12Bit(color):
                  return False

      # All colors must be unique
      if len(set(palette)) != colorsPerPalette:
            return False

      return True

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

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

# 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.")

# Get the list of filenames (Works with wildcards too)
filenameList = []
for parameter in sys.argv[1:]:
      filenameList.extend(glob.glob(parameter))

# 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:
      printCommentLineList(["No files found"])

Generated by  Doxygen 1.6.0   Back to index