/scripts/tilemaker.py
http://panojs.googlecode.com/ · Python · 222 lines · 133 code · 46 blank · 43 comment · 21 complexity · ff2a2c9d892b43d76fda25720dbe2585 MD5 · raw file
- #!/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())