Welcome, guest | Sign In | My Account | Store | Cart
#!/usr/bin/env python
'''Takes any number of images and pads them to be power of two dimensions.

For example: ImageA.png is a 23x10 image.  After being run through
convert_to_POT, it will be the same size centered in a transparent 32x16
canvas.  NOTE: Does not work with palette (ie. '.gif') images.

'''

__author__ = "Martin Wilson: martinmwilson@gmail.com"

import sys
import os
import logging

class PotException(Exception):
    '''Base exception for this module.'''
    pass
class PotArgumentError(PotException): 
    '''Used if a function is passed invalid arguments.'''
    pass


def get_dimensions(size, pots):
    '''Returns closest greater or equal than power-of-two dimensions.
    
    If a dimension is bigger than max(pots), that dimension will be returned
    as None.
    
    '''
    width, height = None, None
    for pot in pots:
        # '<=' means dimension will not change if already a power-of-two
        if size[0] <= pot:
            width = pot
            break
    for pot in pots:
        if size[1] <= pot:
            height = pot
            break
    return width, height


def get_color(mode, image_file, color):
    '''Returns padding color that matches the mode of the original image.
    
    Retuns None if the original image was not an RGB or RGBA image.
    
    '''
    if mode.upper() == "RGBA":
        out_color = color
    elif mode.upper() == "RGB":
        if color[3] == 255:
            logging.info("'%s' not RGBA, falling back to black border..." %
                         (os.path.basename(image_file)))
        out_color = color[0:2]
    else:
        return None
    return out_color


def convert(files, overwrite=False, extension=None, color=(0, 0, 0, 0)):
    '''Takes a list of files and pads each one to power-of-two dimensions.'''
    try:
        from PIL import Image
    except ImportError:
        logging.error("You must install the Python Imaging Library to use " + 
                      "this script.")
        sys.exit(1)

    # ---Validating arguments---
    if overwrite and extension:
        raise PotArgumentError("'overwrite' and 'extension' cannot both be "+
                               "passed.")
    elif not overwrite and not extension:
        raise PotArgumentError("Must pass either overwrite or extension.")
    elif not overwrite and not isinstance(extension, basestring):
        raise PotArgumentError("Extension must be string.")
    if len(color) != 4 or not (isinstance(color, list) or 
                               isinstance(color, tuple)):
        raise PotArgumentError("'color' must be length 4 tuple or list.")

    # ---Image manipulation---
    pots = [2 ** exp for exp in range(16)] #powersoftwo
    for image_file in files:
        image = Image.open(image_file)

        # ---Deciding new dimensions---
        width, height = get_dimensions(image.size, pots)
        if width == None or height == None:
            logging.warning("'%s' has too large dimensions, skipping." % 
                            os.path.basename(image_file))
            continue

        # ---Checking for transparency support---
        out_color = get_color(image.mode, image_file, color)
        if out_color == None:
            logging.warning("'%s' not an RGB or RGBA image, skipping." % 
                            os.path.basename(image_file))
            continue

        # ---Creating output---
        logging.info("'%s' is %dx%d, making new %dx%d image..." % 
                     (os.path.basename(image_file), image.size[0],
                      image.size[1], width, height))
        output = Image.new(image.mode, (width, height), out_color)
        output.paste(image, ((width - image.size[0]) / 2, 
                             (height - image.size[1]) / 2))
        # Be able to write new file with same mode as old file
        mode = os.stat(image_file).st_mode
        # ---Saving output---
        if overwrite:
            output.save(image_file, image.format)
            os.chmod(image_file, mode)
        else:
            image_file = os.path.splitext(image_file)
            output_file = image_file[0] + "." + extension + image_file[1]
            output.save(output_file, image.format)
            os.chmod(output_file, mode)
    return 0


def main():
    '''If run from the command line, get command line option and arguments.'''
    from optparse import OptionParser

    logging.basicConfig(format="%(levelname)s: %(message)s")

    parser = OptionParser(usage="%prog [options] image1 [,image2...]")
    parser.set_defaults(overwrite=False, extension=None, color="alpha",
                        verbose=False)
    parser.add_option("-o", "--overwrite", action="store_true",
                      help="Overwrite all files.  Default is to not overwrite "+
                           "any files and instead generate new files with a "+
                           "'POT' extension.  This option cannot be combined "+
                           "with the 'extension' option.")
    parser.add_option("-e", "--extension", 
                      help="Set the extension added before the filetype.  The "+
                           "default is to add the 'pot' extension, ie. "+
                           "'image1.png' becomes 'image1.pot.png'.  This "+
                           "cannot be combined with the 'overwrite' option.")
    parser.add_option("-c", "--color", type="choice", 
                      choices=("alpha", "black", "white"),
                      help="Choose the padding color.  Default is 'alpha'.")
    parser.add_option("-v", "--verbose", action="store_true",
                      help="Show extra information.")
    (opts, args) = parser.parse_args()

    if opts.verbose:
        logging.root.setLevel(logging.INFO)

    # ---Translate color string to RGBA tuple---
    assert opts.color.lower() in ("alpha", "black", "white")
    if opts.color.lower() == "alpha":
        color = (0,) * 4
    elif opts.color.lower() == "black":
        color = (0, 0, 0, 255)
    else:
        color = (255,) * 4

    # ---Decide between overwriting or adding an extension and run convert---
    if opts.overwrite and opts.extension:
        logging.error("You must specify ONLY an extension or overwrite.")
        parser.print_help()
        sys.exit(1)
    if not args:
        parser.print_usage()
        return 0
    if opts.overwrite:
        return convert(args, True, None, color)
    else:
        if opts.extension:
            if not isinstance(opts.extension, basestring):
                logging.error("'extension' must be a string.")
                parser.print_help()
                return 1
            return convert(args, False, opts.extension, color)
        else:
            return convert(args, False, "pot", color)
                      

if __name__ == "__main__":
    sys.exit(main())

History

  • revision 4 (15 years ago)
  • previous revisions are not available