diff options
Diffstat (limited to '')
-rw-r--r-- | doc/tilemaker.py | 222 |
1 files changed, 222 insertions, 0 deletions
diff --git a/doc/tilemaker.py b/doc/tilemaker.py new file mode 100644 index 0000000..a02c7d2 --- /dev/null +++ b/doc/tilemaker.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python + +""" +This program assists with cutting down large images into square tiles. It can +take an image of arbitrary size and create tiles of any size. + +python tilemaker.py -s256 -Q9 -t"tile-%d-%d-%d.png" -bFFFFFF -v canvas.png + +Copyright, 2005-2006: Michal Migurski, Serge Wroclawski +License: Apache 2.0 +""" + +import math +from os.path import split, splitext +from PIL import Image + +chatty_default = False +background_default = "FFFFFF" +efficient_default = True +scaling_filter = Image.BICUBIC + +from sys import exit + +def main(): + """Main method""" + from optparse import OptionParser + + parser = OptionParser(usage = "usage: %prog [options] filename") + # Now, Dan wants tile height and width. + parser.add_option('-s', '--tile-size', dest = "size", type="int", + default=512, help = 'The tile height/width') + parser.add_option('-t', '--template', dest = "template", + default = None, + help = "Template filename pattern") + parser.add_option('-v', '--verbose', dest = "verbosity", + action = "store_true", default = False, + help = "Increase verbosity") + parser.add_option('-Q', '--quality', dest="quality", type="int", + help = 'Set the quality level of the image') + parser.add_option('-b', '--background', dest="background", + help = 'Set the background color') + + # Location based arguments are always a pain + (options, args) = parser.parse_args() + if len(args) != 1: + parser.error("incorrect number of arguments") + filename = args[0] + if not options.template: + fname, extension = splitext(split(filename)[1]) + options.template = fname + '-%d-%d-%d' + extension + if not options.background: + options.background = background_default + + verbosity = options.verbosity + size = options.size + quality = options.quality + template = options.template + background = options.background + + # Split the image up into "squares" + img = prepare(filename, bgcolor = background, chatty = verbosity) + + subdivide(img, size = (size, size), + quality = quality, filename = template, chatty = verbosity) + + +def prepare(filename, bgcolor = background_default, chatty = chatty_default): + """ + Prepare a large image for tiling. + + Load an image from a file. Resize the image so that it is square, + with dimensions that are an even power of two in length (e.g. 512, + 1024, 2048, ...). Then, return it. + """ + + src = Image.open(filename) + + if chatty: + print "original size: %s" % str(src.size) + + full_size = (1, 1) + + while full_size[0] < src.size[0] or full_size[1] < src.size[1]: + full_size = (full_size[0] * 2, full_size[1] * 2) + + img = Image.new('RGBA', full_size) + img.paste("#" + bgcolor) + + src.thumbnail(full_size, scaling_filter) + img.paste(src, (int((full_size[0] - src.size[0]) / 2), + int((full_size[1] - src.size[1]) / 2))) + + if chatty: + print "full size: %s" % str(full_size) + + return img + + + +def tile(im, level, quadrant=(0, 0), size=(512, 512), + efficient=efficient_default, chatty=chatty_default): + """ + Extract a single tile from a larger image. + + Given an image, a zoom level (int), a quadrant (column, row tuple; + ints), and an output size, crop and size a portion of the larger + image. If the given zoom level would result in scaling the image up, + throw an error - no need to create information where none exists. + """ + + scale = int(math.pow(2, level)) + + if efficient: + #efficient: crop out the area of interest first, then scale and copy it + + inverse_size = (float(im.size[0]) / float(size[0] * scale), + float(im.size[1]) / float(size[1] * scale)) + top_left = (int(quadrant[0] * size[0] * inverse_size[0]), + int(quadrant[1] * size[1] * inverse_size[1])) + bottom_right = (int(top_left[0] + (size[0] * inverse_size[0])), + int(top_left[1] + (size[1] * inverse_size[1]))) + + if inverse_size[0] < 1.0 or inverse_size[1] < 1.0: + raise Exception('Requested zoom level (%d) is too high' % level) + + if chatty: + print "crop(%s).resize(%s)" % (str(top_left + bottom_right), + str(size)) + + zoomed = im.crop(top_left + bottom_right).resize(size, scaling_filter).copy() + return zoomed + + else: + # inefficient: copy the whole image, scale it and then crop + # out the area of interest + + new_size = (size[0] * scale, size[1] * scale) + top_left = (quadrant[0] * size[0], quadrant[1] * size[1]) + bottom_right = (top_left[0] + size[0], top_left[1] + size[1]) + + if new_size[0] > im.size[0] or new_size[1] > im.size[1]: + raise Exception('Requested zoom level (%d) is too high' % level) + + if chatty: + print "resize(%s).crop(%s)" % (str(new_size), + str(top_left + bottom_right)) + + zoomed = im.copy().resize(new_size, scaling_filter).crop(top_left + bottom_right).copy() + return zoomed + + + +def subdivide(img, level=0, quadrant=(0, 0), size=(512, 512), + filename='tile-%d-%d-%d.jpg', + quality = None, chatty = chatty_default): + """ + Recursively subdivide a large image into small tiles. + + Given an image, a zoom level (int), a quadrant (column, row tuple; + ints), and an output size, cut the image into even quarters and + recursively subdivide each, then generate a combined tile from the + resulting subdivisions. If further subdivision would result in + scaling the image up, use tile() to turn the image itself into a + tile. + """ + + if img.size[0] <= size[0] * math.pow(2, level): + + # looks like we've reached the bottom - the image can't be + # subdivided further. # extract a tile from the passed image. + out_img = tile(img, level, quadrant=quadrant, size=size) + out_img.save(filename % (level, quadrant[0], quadrant[1])) + + if chatty: + print '.', ' ' * level, filename % (level, quadrant[0], quadrant[1]) + return out_img + + # haven't reach the bottom. + # subdivide deeper, construct the current image out of deeper images. + out_img = Image.new('RGBA', (size[0] * 2, size[1] * 2)) + out_img.paste(subdivide(img = img, + level = (level + 1), + quadrant=((quadrant[0] * 2) + 0, + (quadrant[1] * 2) + 0), + size = size, + filename=filename, chatty=chatty), (0,0)) + out_img.paste(subdivide(img = img, + level=(level + 1), + quadrant=((quadrant[0] * 2) + 0, + (quadrant[1] * 2) + 1), + size = size, + filename=filename, chatty=chatty), (0,size[1])) + out_img.paste(subdivide(img = img, + level=(level + 1), + quadrant=((quadrant[0] * 2) + 1, + (quadrant[1] * 2) + 0), + size = size, + filename=filename, chatty=chatty), (size[0], 0)) + out_img.paste(subdivide(img, + level=(level + 1), + quadrant=((quadrant[0] * 2) + 1, + (quadrant[1] * 2) + 1), + size = size, + filename=filename, chatty=chatty), (size[0], size[1])) + + out_img = out_img.resize(size, scaling_filter) + + # In the future, we may want to verify the quality. Right now we let + # the underlying code handle bad values (other than a non-int) + if not quality: + out_img.save(filename % (level, quadrant[0], quadrant[1])) + else: + out_img.save(filename % (level, quadrant[0], quadrant[1]), + quality=quality) + if chatty: + print '-', ' ' * level, filename % (level, quadrant[0], quadrant[1]) + return out_img + + + +if __name__ == '__main__': + exit(main()) |