
"""
Img2PyFile.py
    
    Convert an image to b64 string format and embed it in a Python module
    with appropriate code so it can be imported into a program at runtime.  
    
    Derived from img2py.py by Robin Dunn.
      img2py.py crashes on Win7 64-bit as of 2012-04-26.
    
Usage:
    import Img2PyFile as i2p
    i2p.Img2PyFile( [options] imgFile1 [ imgFile2 ...]  \ 
                    [ pyImagesExist1.py ...] pyImagesNew.py )
    
    Arguments for image files, options and pyImages files 
    may intermixed in any order.
    The last given pyImages file will always be written or 
      or **appended to** if it already exists.
    
    PROGRESS, NOTE and WARNING message are written to stdout.
    
    FAILURE messages indicate file read or write failures.
    They do NOT end the application execution.

    ERROR messages indicate a fatal application error.
    They cannot be suppressed, thus there are no associated 
       command line options available.
    
Options: (NOTE: None of these options *need* to be specified.)
    
    -a -A  Enable/Disable ACTION   messages. Disabled by default.
    -f -F  Enable/Disable FAULURE  messages. Enabled by default.
    -n -N  Enable/Disable NOTE:    messages. Disabled by default.
    -p -P  Enable/Disable PROGRESS messages. Enabled by default.
    -w -W  Enable/Disable WARNING  messages. Disabled by default.
    
    -q  Suppress all nonfatal messages.
        To enable only specific messages specify '-q' then follow 
        with the desired lower case message options.
        
    -h  List this help and exit.     
    -H  List this help THEN CONTINUE.
    
    A PROGRESS message indicates an attempt to do something
      such as read a file while an ACTION message indicates
      the actual success at doing something.

This module can be used internally by instantiating 
  an Img2PyFile() class object. 
  
E.g., the following:
    
    - Disables all messaging except for PROGRESS messages;
    - Reads all existing image data from file pyimagesIn.py and
    -   appends them to file pyimagesOut.py;
    - Processes images from all give .PNG files and writes their
        data to pyimagesOut.py
    
    import Img2PyFile as i2p
    args = '-qp image1.png pyimagesIn.py pyImagesOut.py image2.gif'
    args = args.split()     # Args are in a list just like sys.argv[ 1: ]
    i2p.Img2PyFile( args )

E.g.:  
An initial command line runs and its results when file PyImages.PY 
  does not yet exist:

    > python.exe Image2PyFile.py  About-16.PNG About-32.PNG PyImages.PY
    PROGRESS:  Reading New Image Files
    ACTION:  Adding Image: [ About_16_PNG ]  File: [ About-16.PNG]
    ACTION:  Adding Image: [ About_32_PNG ]  File: [ About-32.PNG]
    PROGRESS:  Writing New PyImages File: [ PyImages.PY ]

A following run when file PyImages.PY does exist:
    
    > python.exe Image2PyFile.py  Cut-32.PNG Delete-16.PNG
    PROGRESS:  Reading Existing PyImages File: [ PyImages.PY ]
    PROGRESS:  Reading New Image Files
    ACTION:  Adding Image: [ Cut_32_PNG ]  File: [ Cut-32.PNG]
    ACTION:  Adding Image: [ Delete_16_PNG ]  File: [ Delete-16.PNG]
    PROGRESS:  OVERWRITING PyImages File: [ PyImages.PY ]


Ray Pasco
pascor(at)verizon(dot)net

Version:
    1.1   2012-04-26

Tested on:

Windows   6.1.7601  {Windows 7 64-bit}
Python    2.6.5 (r265:79096, Mar 19 2010, 21:48:26) [MSC v.1500 32 bit (Intel)]
Wx Version 2.8.12.0
Wx Pltform ('__WXMSW__', 'wxMSW', 'unicode', 'wx-assertions-on', 'SWIG-1.3.29')

Windows   5.1.2600  {Windows XP 32-bit}
Python    2.6.5 (r265:79096, Mar 19 2010, 21:48:26) [MSC v.1500 32 bit (Intel)]
Wx Version 2.8.12.0
Wx Pltform ('__WXMSW__', 'wxMSW', 'unicode', 'wx-assertions-on', 'SWIG-1.3.29')
"""

import getopt
import glob
import os, sys
import re
import tempfile
import base64
import binascii
import wx

scriptName = sys.argv[ 0 ]

#------------------------------------------------------------------------------

def CallersName():
    return sys._getframe(2).f_code.co_name

#------------------------------------------------------------------------------

# Credit to Alex Martelli, "Python Cookbook" p. 440
def DefName() :
    """
    Returns the name of the function calling DefName().
    Does not work inside class __init__() definitions.
    """
    import sys
    return sys._getframe(1).f_code.co_name + '()'

#end def

#------------------------------------------------------------------------------

def WhatsInstalled() :
    """
    List the versions of Python and wxPython.
    """
    defName = DefName()     # For DEBUG
    
    # Which vesrions of Python and wxPython are installed ?
    import os, sys, platform
    print
    print defName + '():'
    if os.name == 'nt' :
        print 'Windows  ', platform.win32_ver()[1]
    else :
        print 'Platform ', platform.system()
    #end if
    print 'Python   ', sys.version
    addon_pkgs = [ ('Wx Version', 'wx.VERSION_STRING'),
                   ('Wx Pltform', 'wx.PlatformInfo'),   ]
                   
    for addonStr, attribute in addon_pkgs :
        try :
            print addonStr, eval( attribute )
        except NameError :
            print
        #end try
    #end for
    print
    
#end WhatsInstalled def

#------------------------------------------------------------------------------

def String2Identifier( s ) :
    """
    Simple character substitution to produce a legal Python identifier string.
    """
    defName = DefName()     # For DEBUG
    
    letters = []
    for letter in s :
        if not letter.isalnum() :
            letter = '_'
        letters.append( letter )
    #end for
    
    if not letters[ 0 ].isalpha() and letters[ 0 ] != '_' :
        letters.insert( 0, '_' )
    identifierString = ''.join( letters )
    
    return identifierString
    
#end String2Identifier def

#------------------------------------------------------------------------------

def ProcessArgs( args=None ) :
    """
    Command line parsing. Separates args into three groups:
    1) List if pyImages files that will be read and/or written
    2) A list set() names indicating which kinds of message 
       will be printed.
    """
    defName = DefName()     # For DEBUG
    
    if not args :
        msg = 'ERROR:  %s::%s:  No Commandline Args Given\n'  \
              %( scriptName, defName )
        print >> sys.stderr, msg
        print >> sys.stderr, __doc__
        sys.exit(1)
    #end if
    
    #-----
    
    try :
        opts, fileArgs = getopt.gnu_getopt( args, 'aA fF hH nN pP q wW' )
        
    except getopt.GetoptError :
        
        msg = '\nERROR:   %s::%s:  UNKNOWN SWITCH ARG in:\n\n%s\n'  \
              %( scriptName, defName, args )
        print >> sys.stderr, msg
        sys.exit(1)
        
    #-----
    
    # [ opts ] is returned as a hard-to-mutate list of tuples.
    enabledMsgs = set( [ 'action', 'progress' ] )   
    for key, valIgnored in opts :
        
        if   key == '-a' :
            enabledMsgs.add( 'action' )        # Enable these messages
        elif   key == '-A' :
            enabledMsgs.discard( 'action' )    # Suppress these messages
        
        elif key == '-f' :
            enabledMsgs.add( 'failure' )
        elif key == '-F' :
            enabledMsgs.discard( 'failure' )
        
        elif key == '-h' :
            print __doc__
            sys.exit(1)
        elif key == '-H' :
            print __doc__
            
        elif key == '-n' :
            enabledMsgs.add( 'note' )
        elif key == '-N' :
            enabledMsgs.discard( 'note' )
        
        elif key == '-p' :
            enabledMsgs.add( 'progress' )
        elif key == '-P' :
            enabledMsgs.discard( 'progress' )
        
        elif key == '-q' :
            enabledMsgs = list()        # Suppress all messages
        
        elif key == '-w' :
            enabledMsgs.add( 'warning' )
        elif key == '-W' :
            enabledMsgs.discard( 'warning' )
        
        else :
            print >> sys.stderr, 'UNRECOGNIZED Arg Switch  [ %s ]' %( key )
        #end if
    
    #end for
    ##print 'enabledMsgs:  ', enabledMsgs; print    # DEBUG
    
    # Sanity check.
    if len( fileArgs ) < 1 :
        
        msg = '\nERROR - %s::%s:   At Least One PyImages Filename Must Be Given.\n'  \
              %( scriptName, defName )
        print >> sys.stderr, msg
        sys.exit(1)
    
    #end if
    
    #-----
    
    return (enabledMsgs, fileArgs)
    
#end ProcessArgs

#------------------------------------------------------------------------------

def B64Encode( s, altchars=None ) :
    """
    Encode a string using Base64.

    Optional altchars must be a string of at least length 2 (additional 
    characters are ignored) which specifies an alternative alphabet for 
    the '+' and '/' characters.  This allows an application, e.g., 
    to generate a url or filesystem-safe Base64 strings.

    The encoded string is returned.
    """
    defName = DefName()     # For DEBUG
    
    # Eliminate the trailing newline returned.
    encoded = binascii.b2a_base64( s )[ :-1 ]
    
    if altchars :
        encoded = _translate( encoded, {'+': altchars[0], '/': altchars[1]} )
        
    return encoded

#end B64Encode def

#------------------------------------------------------------------------------

def BitmapToPngFile( wxBmap, outputDir=None, outputName='', enabledMsgs=None ) :
    """
    Save a wx.Bitmap to a PNG file. The contents of this file is intended
    to be b64 encoded in order to finally save it to the output pyImages file.
    """
    defName = DefName()     # For DEBUG
    
    if outputName :
        outFilePathname = outputName

    else :
        
        newMainName = os.path.basename( os.path.splitext( imgFilename )[0] )
        newfileBasename = newMainName + '.png'
        outFilePathname = os.path.join( outputDir, newfileBasename )
    #end if

    if wxBmap.SaveFile( outFilePathname, wx.BITMAP_TYPE_PNG ) :     # SaveFile() success

        return True

    else :      # wx.Bitmap.SaveFile() has failed.
        if ('failure' in enabledMsgs) :
            msg = '\nFAILURE:  CAN\'T WRITE Temporary wx.Bitmap to .PNG file.\n'
            print >> sys.stderr, msg
        #end if
        
        # Try a different save tactic.
        wxImage = wx.ImageFromBitmap( wxBmap )     # ? Avoid some early wx version limitation ?
        if wxImage.SaveFile( outFilePathname, wx.BITMAP_TYPE_PNG ) :
            return True
            
        else :
            msg  = '\nERROR:   CAN\'T WRITE Temporary wx.Image to.PNG  File: [ %s ]\n\n'  \
                   %( outFilePathname )
            print >> sys.stderr, msg
            sys.exit(1)
        #end if
        
    #end if wxBmap.SaveFile()
    
#end BitmapToPngFile def

#------------------------------------------------------------------------------

def CreatePngFileData( imgFilename, enabledMsgs ) :
    """
    
    """
    defName = DefName()     # For DEBUG
    
    if not os.path.exists( imgFilename ) :
        if ('warning' in enabledMsgs) :
            msg = '\nWARNING:  Image File Doesn\'t Exist[ %s ].' %( imgFilename )
            print msg
        
        return None
    #end if
    
    msg = '\nFAILURE:  Image File is UNREADABLE  [ %s ]' %( imgFilename )
    try :
        wxBmap = wx.Bitmap( imgFilename, wx.BITMAP_TYPE_ANY )
    
    except :
        if ('failure' in enabledMsgs) :
            print >> sys.stderr, msg
        return None
        
        #-----
    
    # Handle bad image file data
    if (not wxBmap.Ok())  and  ('failure' in enabledMsgs) :
        print >> sys.stderr, msg
        ##print '5'*70          # DEBUG
        return None

    #end if

    #-----
    
    # Read the original image file and write it to a new PNG file.
    tmpPngFilename = tempfile.mktemp()
    
    success = BitmapToPngFile( wxBmap, None, tmpPngFilename, enabledMsgs )
    if not success :
        print >> sys.stderr, '\nERROR:  CAN\'T WRITE to Temporary PNG File.\n'  \
                          %( scriptName, defName )
        return None
        
        #-----
    
    #-----
    
    # Encode the PNG file's lossless-compressed binary image data into a single, big b64 string.
    pngFile = open( tmpPngFilename, 'rb' )
    pngImageData = pngFile.read()
    pngFile.close()
    os.remove( tmpPngFilename )
    
    return pngImageData
    
#end CreatePngFileData def

#------------------------------------------------------------------------------

def B64EncodeBinaryData( pngImageData ) :
    """
    B64 encodes a binary byte string. Returns a list of lines of strings
    suitable for embedding in a Python file.
    """
    defName = DefName()     # For DEBUG
    
    # Encode the PNG file's lossless-compressed binary image data into a single, big b64 string.
    encPngImgData = B64Encode( pngImageData )
    
    # Chop the long b64 character-encoded encPngImgData into manageable 
    #   line lengths for writing to a file.
    linesOfEncPngImgData = list()   # b64 linesOfEncPngImgData list.
    while encPngImgData :
        
        aLineOfEncPngImgData = encPngImgData[ :72 ]     # A chunk length of 72 chars
        encPngImgData = encPngImgData[ 72: ]            # The remainder of data to be encoded.
        aLineOfEncPngImgData = '    "%s"' %( aLineOfEncPngImgData )
        
        linesOfEncPngImgData.append( aLineOfEncPngImgData )  # Add to linesOfEncPngImgData list.
        
    #end while encPngImgData
    linesOfEncPngImgData = '\n'.join( linesOfEncPngImgData )
    
    return linesOfEncPngImgData
    
#end B64EncodeBinaryData def

#------------------------------------------------------------------------------

class ImageObject() :
    """
    Image data storage. A collection of the most useful attributes
    for both reading images from existing pyImages files and
    also for generating a new output file.
    """
    
    def __init__( self, idName='', baseName='', mainName='', fileExt='', encPngData='' ) :
        defName = 'ImageObject()'
        
        # Init the attributes. Null ones will be defined shortly.
        self.idName     = idName        # Suitable for use as a Python identifier.
        self.baseName   = baseName      # The image's source file basename.
        self.mainName   = mainName      # The "main" part of the source basename
        self.fileExt    = fileExt       # The extension part of the source basename
        self.createdExt = False         # First assume a baseName was originally supplied.
        self.encPngData = encPngData    # Image's .PNG file binary data
        
        # All the most common 3-letter image file extensions.
        # FUTURE: jpeg, tiff, any other non-3-letter image file extensions (if there are any).
        IMG_EXTS = [ '.bmp', '.gif', '.ico', '.jpg', '.png', '.tif' ]
        
        if baseName :       # [is given] File extension may or may not be appended.
            
            extLen = 4      # Includes the leading period.
            extStartIndex = len( self.baseName ) - extLen
            
            # Is the file extension is appended ?
            if self.baseName.lower()[ extStartIndex: ] in IMG_EXTS :   
                
                self.mainName, self.fileExt = os.path.splitext( baseName )
            
            else :      # File extension is not appended.
                
                self.mainName = self.baseName
                self.fileExt = '.png'
                self.createdExt = True
                self.baseName = self.baseName + self.fileExt
                
            #end if
            
        elif self.mainName :    # [is given] Has no fileExt
            
            if self.fileExt :   # The fileExt is defined - create the baseName.
                if not self.fileExt.startswith( '.' ) :
                    self.fileExt = self.fileExt + '.'
                
                self.baseName = self.mainName + self.fileExt
                
            else :              # No fileExt defined - create a reasonable one.
                
                print >> sys.stderr, '\nERROR:  %s::%s:  NEITHER baseName NOR mainName Given.'  \
                                  %( scriptName, defName )
                sys.exit(1)
                
        #end if self.baseName [is given]
        
        self.idName = String2Identifier( self.baseName )
        
    #end __init__
    
#end ImageObject class

#------------------------------------------------------------------------------

def ListImageDict( imageDict ) :
    """
    Simple DEBUG utiliy to list all an imageDict's attributes 
      except for the image data.
    """
    defName = DefName()     # For DEBUG
    
    print '\n----  imageDict :'
    for imgObj in imageDict.itervalues() :
        print '  baseName   = %s' %( imgObj.baseName )
        print '  createdExt = %r' %( imgObj.createdExt )
        print '  mainName   = %s' %( imgObj.mainName )
        print '  idName     = %s' %( imgObj.idName )
        print
    #end for
    
#end def

#------------------------------------------------------------------------------

def SortFilenames( fileArgs, enabledMsgs ) :
    """
    Sorts a list of filenames into:
    1) inPyImageFilesList - a list of pyImage files to be input;
    2) outPyImageFilename - the intended pyImages filename to be written;
    3) imageFileList - a list of image files to add the the output pyImages file.
    
    At minimum, one pyImages filename must be given in the args.
    Checks that all image files exist.
    
    The last given pyImages file will be the output pyImages file.
    It doen't have to exist if 1 or more previous existing piImages.py files have been given.
    
    A lone given pyImages filename will be both input and then overwritten.
    """
    defName = DefName()     # For DEBUG
    
    
    allPyImageFilesList = list()
    imageFileList       = list()
    
    # Sort the filenames in two groups: 1) PyImages files,  2) image files.
    for filename in fileArgs :
        
        if filename.lower().endswith( '.py' ) :     # A pyImages filename.
            
            allPyImageFilesList.append( filename )
            
        else :      # Must be an image filename.
            
            if os.path.exists( filename ) :     # Check later if this actually is an image file.
                imageFileList.append( filename )
            else :
                if ('warning' in enabledMsgs) :
                    print 'WARNING:  Input Image File DOES NOT EXIST  [ %s ]'  \
                          %( filename )
            #end if os.path.exists
            
        #end if filename.lower().endswith( '.py' )
        
    #end for filename in fileArgs
    
    # Create inpPyimgFileList and outPyImageFilename from allPyImageFilesList.
    numTotalPyImgFiles = len( allPyImageFilesList )
    if (numTotalPyImgFiles == 0) :
        
        print >> sys.stderr, 'ERROR:  %s::%s:   NO GIVEN Output PyImages Filename\n'  \
                             %( scriptName, defName )
        sys.exit(1)
        
    #-----
        
    # It's not necessary that this file doesn't yet exist.
    outPyImageFilename = allPyImageFilesList[ -1 ]    # ALWAYS the last filename in the list.
    
    if numTotalPyImgFiles == 1 :
        
        # This lone PyImages file will be used for both input and output.
        # The net effect is to check the validty of its contents.
        # Additional given image files will be added to its pyImages.
        # The existance of this file is checked elsewhere.
        inPyImageFilesList = allPyImageFilesList            # The same single-item list
        
    else :  # numTotalPyImgFiles >= 2
        
        inPyImageFilesList = list()
        allPyImageFilesList = allPyImageFilesList[ :-1 ]    # Check all but the last the given files.
        for anInpPyImgFile in allPyImageFilesList :         # The last is ALWAYS the output file.

            if (not os.path.exists( anInpPyImgFile )) :
                if ('failure' in enabledMsgs) :
                    msg = 'FAILURE:  GIVEN Input PyImages File DOES NOT EXIST [ %s ]'  \
                          %( anInpPyImgFile )
                    print msg
            else :
                inPyImageFilesList.append( os.path.abspath( anInpPyImgFile ) )
            #end if

        #end for
        
    #end if
    
    if (not imageFileList) :
        if ('note' in enabledMsgs) :
            print 'NOTE:  NO GIVEN Image Files to Add\n'
    #end if
    
    """ DEBUG
    print '$$$$  inPyImageFilesList'
    print '     ', inPyImageFilesList
    print
    print '$$$$  outPyImageFilename'
    print '     ', outPyImageFilename
    print
    print '$$$$  imageFileList'
    print '     ', imageFileList
    print
    """
    
    return inPyImageFilesList, outPyImageFilename, imageFileList
    
#end SortFilenames def

#------------------------------------------------------------------------------

def ImportPyImages( inpPyImgFilename, imageDict, enabledMsgs ) :
    """
    Import the pyImages file and add its images to imageDict{}.
    The file's existance is expected to have been verified.
    """
    defName = DefName()     # For DEBUG
    
    pyImgMainName, pyImgExt = os.path.splitext( os.path.basename( inpPyImgFilename ) )
    pyImportStr = 'from %s import *' %( pyImgMainName )
    
    try :
        exec( pyImportStr )
    except :                # Not a fatal error
        if ('failure' in enabledMsgs) :
            if os.path.exists( inpPyImgFilename ) :
                print >> sys.stderr, 'FAILURE:  PyImages File Has BAD PYTHON SYNTAX  [ %s ]'  \
                                  %( inpPyImgFilename )
        #end if
        
        return imageDict    # An empty disctionary.
        
        #-----
        
    #end if
    
    # Check if the Py file is actually a PyImages file.
    try:
        catalog.iteritems()     # Should have been defined
        
    except NameError :
        if ('failure' in enabledMsgs) :
            msg = 'FAILURE:  NOT A PYIMAGES File:  [ %s ]' %( inpPyImgFilename )
            print >> sys.stderr, msg
        #end if
        
        return imageDict
        
        #-----
        
    #end try
    
    # Assume, for now, that the key is a baseName and not a mainName.
    for baseName, catIdName in catalog.iteritems() :
        
        # Construct the callable function name from the catalog's idName.
        funcStr = 'GetData_' + catIdName + '()'
        pngImageData = eval( funcStr )
        
        # Encode the binary data into viewable 7-bit characters.
        encPngData = B64EncodeBinaryData( pngImageData )
        
        # Create an ImageObject for each encoded image and add it to imageDict.
        # Assume that the baseName from the catalog dict has an extension appended.
        # ImageObject () will create a nominal extName if none exists in baseName.
        imgObj = ImageObject( idName=catIdName, baseName=baseName, encPngData=encPngData )
        
        newIdName = imgObj.idName       # Extract the possibley-corrected idName.
        
        # Check if idName already exists.
        alreadyInDict = False
        for anIdName in imageDict.iterkeys() :
            
            # imageDict keys are the corrected idNames.
            if (newIdName == anIdName) :
                alreadyInDict = True
                break       # Search no further - dictionaries can't have duplicate keys.
            #end if
            
        #end for
        
        baseName = imgObj.baseName
        if alreadyInDict :
            if ('warning' in enabledMsgs) :
                print 'WARNING:  REDEFINING Image: [ %s ]  File: [ %s ]'  \
                      %( newIdName, baseName )
            
        else :
            if ('note' in enabledMsgs) :
                print 'NOTE:  Reading Image: [ %s ]  File: [ %s ]'  \
                      %( newIdName, baseName )
        #end if
        
        # Use the extension-appended idName for the key, 
        #   not the original, possibly extension-less idName read from the pyImages file.
        imageDict[ imgObj.idName ] = imgObj     
        
    #end for baseName, catIdName in catalog.iteritems()
    
    return imageDict
    
#end ImportPyImages def
    
#------------------------------------------------------------------------------

def WritePyImagesFileHeader( pyImagesFile ) :
    """
    Writes Python statements to the output pyImages file.
    """
    defName = DefName()     # For DEBUG
    
    pyImagesFile.write( '#' + '-'*69 + '\n\n' )
    line = '# This file was generated by %s\n\n' %( scriptName )
    pyImagesFile.write( line )
    pyImagesFile.write( 'from wx.lib.embeddedimage import PyEmbeddedImage\n' )
    pyImagesFile.write( '\n' )
    pyImagesFile.write( 'catalog = {}\n' )
    
#end WritePyImagesFileHeader def

#------------------------------------------------------------------------------

def WritePyImageDataAndFuncs( imgObj, pyImagesFile, enabledMsgs ) :
    """
    Writes the Python code to the output pyImages file that both define an image 
    and to be able to generate raw data, wx.Image, wx.Bitmap and wx.Icon objects
    when its pyImmages file is imported by any Python application.
    """
    defName = DefName()     # For DEBUG
    
    pyImagesFile.write( '#' + '-'*69 + '\n\n' )
    pyImagesFile.write( '%s = PyEmbeddedImage( \n%s\n'  \
                        %( imgObj.idName, imgObj.encPngData) )
    pyImagesFile.write( '    )\n\n' )
    
    # When the PyImages file is imported, 
    #   the following dictionary idName value will become a function name.
    pyImagesFile.write( 'catalog["%s"] = "%s"\n' %( imgObj.baseName, imgObj.idName) )

    pyImagesFile.write( 'GetData_%s   = %s.GetData\n'   %( imgObj.idName, imgObj.idName ) )
    pyImagesFile.write( 'GetImage_%s  = %s.GetImage\n'  %( imgObj.idName, imgObj.idName ) )
    pyImagesFile.write( 'GetBitmap_%s = %s.GetBitmap\n' %( imgObj.idName, imgObj.idName ) )
    pyImagesFile.write( 'GetIcon_%s   = %s.GetIcon\n'   %( imgObj.idName, imgObj.idName ) )
    pyImagesFile.write( '\n' )
    
    """  DEBUG
    print '$$$$  Img2PyFile::ImageFileEncodeAndWrite:'
    print ' '*8 + 'Embedding image as callable identifier [ %s ]' %( imgObj.idName )
    """
    
#end WritePyImagesFileFunctions def

#------------------------------------------------------------------------------

def WritePyImageFile( imageDict, outPyImageFilename, enabledMsgs ) :
    """
    Writes a new pyImages file from an imageDict.
    """
    defName = DefName()     # For DEBUG
    
    if not imageDict :
        if ('warning' in enabledMsgs) :
            print 'WARNING:  NO IMAGES TO WRITE to PyImages File: [ %s ]'  \
                              %( outPyImageFilename )
        return
    #end if
    
    #-----
    
    if (not os.path.exists( outPyImageFilename )) :
        if ('progress' in enabledMsgs) :
            print 'PROGRESS:  Writing New PyImages File: [ %s ]'  \
                              %( outPyImageFilename )

    else :
        if ('progress' in enabledMsgs) :
            print 'PROGRESS:  OVERWRITING PyImages File: [ %s ]'  \
                              %( outPyImageFilename )
        
    pyImagesFile = open( outPyImageFilename, 'w' )    # Delete any existing file.
    
    # Write the header once,
    WritePyImagesFileHeader( pyImagesFile )
    
    # For each imgObject write its encoded data and functions.
    for imgObj in imageDict.itervalues() :
        WritePyImageDataAndFuncs( imgObj, pyImagesFile, enabledMsgs )
    
    pyImagesFile.close()
    
#end WritePyImageFile def

#------------------------------------------------------------------------------

class Img2PyFile() :
    """
    The main class to instantiate. See the __doc__ string for all details.
    """
    
    def __init__( self, args=None ) :
        defName = 'Img2PyFile()'
        
        enabledMsgs, fileArgs = ProcessArgs( args )
        
        inPyImageFilesList, outPyImageFilename, imageFileList = SortFilenames( fileArgs, enabledMsgs )
        
        if not wx.GetApp() :                # Has the user's program already created a wx.App ?
            app = wx.App( redirect=False )  # No, create a dummy app to enable accessinng all wx's methods.
        
        #-----
        
        # Import any existing PyImages files and populate imageDict from them.
        # There must be at least one pyImages file.
        imageDict = dict()      # Brand new, empty
        for anInpPyImgFile in inPyImageFilesList :
        
            if (os.path.exists( anInpPyImgFile )  and  ('progress' in enabledMsgs)) :
                print 'PROGRESS:  Reading Existing PyImages File: [ %s ]'  \
                      %( anInpPyImgFile )
            
            # Add to imageDict{} given the current content of imageDict{}.
            imageDict = ImportPyImages( anInpPyImgFile, imageDict, enabledMsgs )
            
            #ListImageDict( imageDict )        # DEBUG
        
        #end for
        
        #-----
        
        if imageFileList :      # Might be "None".
            
            if ('progress' in enabledMsgs) :
                print 'PROGRESS:  Reading New Image Files:'
            
            # Add the new images to imageDict
            for imgFilename in imageFileList :
                
                # Convert the filename to a legal Python identifier string.
                baseName  = os.path.basename( imgFilename )
                mainName, fileExt = os.path.splitext( baseName )
                idName = String2Identifier( baseName )
                
                # Check if the image name already exists imageDict.
                overWriting = False
                for imgObj in imageDict.values() :
                    
                    if (idName == imgObj.idName) :
                        overWriting = True
                        break
                #end for
                
                # Convert image to PNG file data format.
                # Result may be None if an error occurs.
                pngImageData = CreatePngFileData( imgFilename, enabledMsgs )
                if pngImageData :
                    
                    # Any errors 
                    encPngData = B64EncodeBinaryData( pngImageData )

                    # Create a new ImageObject and populate it.
                    imgObj = ImageObject( idName, baseName, mainName, fileExt, encPngData )
                    
                    # Add it to the image dictionary.
                    imageDict[ idName ] = imgObj
                    
                    if overWriting :
                        if ('warning' in enabledMsgs) :
                            print 'WARNING:  OVERWITING Image: [ %s ]  File: [ %s ]'  \
                                  %( idName, baseName )
                    else :
                        if ('action' in enabledMsgs) :
                            print 'ACTION:  Adding Image: [ %s ]  File: [ %s ]'  \
                                  %( idName, baseName )
                   #end if overWriting
                   
                #end if pngImageData
            #end for imgFilename in imageFileList
        #end if imageFileList
        
        """ DEBUG
        ListImageDict( imageDict )
        """
        
        #-----
        
        # Write a new PiImages.py file.
        WritePyImageFile( imageDict, outPyImageFilename, enabledMsgs )
        
    #end __init__
    
#end Img2PyFile class

#==============================================================================

if __name__ == '__main__' :
    
    #WhatsInstalled()       # Platform information
    
    cmdLineArgs = sys.argv[ 1: ]
    Img2PyFile( cmdLineArgs )
    
#end if
