Pads any amount of images each to power-of-two dimensions, useful for OpenGL programming.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | #!/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())
|
This script requires the Python Imaging Library (available with most distro-specific package management). Supports basic options such as overwriting or creating a new image, get the full usage by running with the "--help" option. Currently only supports mode RGB or RGBA images, Palette images do not work (IE .gif images). It may be possible to add GIF support with correct PIL handling of transparency in palettes.