Welcome, guest | Sign In | My Account | Store | Cart

Pads any amount of images each to power-of-two dimensions, useful for OpenGL programming.

Python, 183 lines
  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.