#!/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())