PageRenderTime 20ms CodeModel.GetById 1ms app.highlight 15ms RepoModel.GetById 1ms app.codeStats 0ms

/scripts/tilemaker.py

http://panojs.googlecode.com/
Python | 222 lines | 160 code | 30 blank | 32 comment | 14 complexity | ff2a2c9d892b43d76fda25720dbe2585 MD5 | raw file
  1#!/usr/bin/env python
  2
  3"""
  4This program assists with cutting down large images into square tiles.  It can
  5take an image of arbitrary size and create tiles of any size.
  6
  7python tilemaker.py -s256 -Q9 -t"tile-%d-%d-%d.png" -bFFFFFF -v canvas.png
  8
  9Copyright, 2005-2006: Michal Migurski, Serge Wroclawski
 10License: Apache 2.0
 11"""
 12
 13import math
 14from os.path import split, splitext
 15from PIL import Image
 16
 17chatty_default = False
 18background_default = "FFFFFF"
 19efficient_default = True
 20scaling_filter = Image.BICUBIC
 21
 22from sys import exit
 23
 24def main():
 25    """Main method"""
 26    from optparse import OptionParser
 27    
 28    parser = OptionParser(usage = "usage: %prog [options] filename")
 29    # Now, Dan wants tile height and width.
 30    parser.add_option('-s', '--tile-size', dest = "size", type="int",
 31                      default=512, help = 'The tile height/width')
 32    parser.add_option('-t', '--template', dest = "template",
 33                      default = None,
 34                      help = "Template filename pattern")
 35    parser.add_option('-v', '--verbose', dest = "verbosity",
 36                      action = "store_true", default = False,
 37                      help = "Increase verbosity")
 38    parser.add_option('-Q', '--quality', dest="quality", type="int",
 39                      help = 'Set the quality level of the image')
 40    parser.add_option('-b', '--background', dest="background",
 41                      help = 'Set the background color')
 42    
 43    # Location based arguments are always a pain
 44    (options, args) = parser.parse_args()
 45    if len(args) != 1:
 46        parser.error("incorrect number of arguments")
 47    filename = args[0]
 48    if not options.template:
 49        fname, extension = splitext(split(filename)[1])
 50        options.template = fname + '-%d-%d-%d' + extension
 51    if not options.background:
 52        options.background = background_default
 53
 54    verbosity = options.verbosity
 55    size = options.size
 56    quality = options.quality
 57    template = options.template
 58    background = options.background
 59    
 60    # Split the image up into "squares"
 61    img = prepare(filename, bgcolor = background, chatty = verbosity)
 62
 63    subdivide(img, size = (size, size),
 64              quality = quality, filename = template, chatty = verbosity)
 65
 66
 67def prepare(filename, bgcolor = background_default, chatty = chatty_default):
 68    """
 69    Prepare a large image for tiling.
 70    
 71    Load an image from a file. Resize the image so that it is square,
 72    with dimensions that are an even power of two in length (e.g. 512,
 73    1024, 2048, ...). Then, return it.
 74    """
 75
 76    src = Image.open(filename)
 77
 78    if chatty:
 79        print "original size: %s" % str(src.size)
 80    
 81    full_size = (1, 1)
 82
 83    while full_size[0] < src.size[0] or full_size[1] < src.size[1]:
 84        full_size = (full_size[0] * 2, full_size[1] * 2)
 85    
 86    img = Image.new('RGBA', full_size)
 87    img.paste("#" + bgcolor)
 88    
 89    src.thumbnail(full_size, scaling_filter)
 90    img.paste(src, (int((full_size[0] - src.size[0]) / 2),
 91                    int((full_size[1] - src.size[1]) / 2)))
 92    
 93    if chatty:
 94        print "full size: %s" % str(full_size)
 95        
 96    return img
 97
 98
 99
100def tile(im, level, quadrant=(0, 0), size=(512, 512),
101         efficient=efficient_default, chatty=chatty_default):
102    """
103    Extract a single tile from a larger image.
104    
105    Given an image, a zoom level (int), a quadrant (column, row tuple;
106    ints), and an output size, crop and size a portion of the larger
107    image. If the given zoom level would result in scaling the image up,
108    throw an error - no need to create information where none exists.
109    """
110
111    scale = int(math.pow(2, level))
112    
113    if efficient:
114        #efficient: crop out the area of interest first, then scale and copy it
115
116        inverse_size    = (float(im.size[0]) / float(size[0] * scale),
117                           float(im.size[1]) / float(size[1] * scale))
118        top_left        = (int(quadrant[0] *  size[0] * inverse_size[0]),
119                           int(quadrant[1] *  size[1] * inverse_size[1]))
120        bottom_right    = (int(top_left[0] + (size[0] * inverse_size[0])),
121                           int(top_left[1] + (size[1] * inverse_size[1])))
122    
123        if inverse_size[0] < 1.0 or inverse_size[1] < 1.0:
124            raise Exception('Requested zoom level (%d) is too high' % level)
125    
126        if chatty:
127            print "crop(%s).resize(%s)" % (str(top_left + bottom_right),
128                                           str(size))
129
130        zoomed = im.crop(top_left + bottom_right).resize(size, scaling_filter).copy()
131        return zoomed
132
133    else:
134        # inefficient: copy the whole image, scale it and then crop
135        # out the area of interest
136
137        new_size        = (size[0] * scale,         size[1] * scale)
138        top_left        = (quadrant[0] * size[0],   quadrant[1] * size[1])
139        bottom_right    = (top_left[0] + size[0],   top_left[1] + size[1])
140        
141        if new_size[0] > im.size[0] or new_size[1] > im.size[1]:
142            raise Exception('Requested zoom level (%d) is too high' % level)
143    
144        if chatty:
145            print "resize(%s).crop(%s)" % (str(new_size),
146                                           str(top_left + bottom_right))
147
148        zoomed = im.copy().resize(new_size, scaling_filter).crop(top_left + bottom_right).copy()
149        return zoomed
150
151
152
153def subdivide(img, level=0, quadrant=(0, 0), size=(512, 512),
154              filename='tile-%d-%d-%d.jpg',
155              quality = None, chatty = chatty_default):
156    """
157    Recursively subdivide a large image into small tiles.
158
159    Given an image, a zoom level (int), a quadrant (column, row tuple;
160    ints), and an output size, cut the image into even quarters and
161    recursively subdivide each, then generate a combined tile from the
162    resulting subdivisions. If further subdivision would result in
163    scaling the image up, use tile() to turn the image itself into a
164    tile.
165    """
166
167    if img.size[0] <= size[0] * math.pow(2, level):
168
169        # looks like we've reached the bottom - the image can't be
170        # subdivided further. # extract a tile from the passed image.
171        out_img = tile(img, level, quadrant=quadrant, size=size)
172        out_img.save(filename % (level, quadrant[0], quadrant[1]))
173
174        if chatty:
175            print '.', '  ' * level, filename % (level, quadrant[0], quadrant[1])
176        return out_img
177
178    # haven't reach the bottom.
179    # subdivide deeper, construct the current image out of deeper images.
180    out_img = Image.new('RGBA', (size[0] * 2, size[1] * 2))
181    out_img.paste(subdivide(img = img,
182                            level = (level + 1),
183                            quadrant=((quadrant[0] * 2) + 0,
184                                      (quadrant[1] * 2) + 0),
185                            size = size,
186                            filename=filename, chatty=chatty), (0,0))
187    out_img.paste(subdivide(img = img,
188                            level=(level + 1),
189                            quadrant=((quadrant[0] * 2) + 0,
190                                      (quadrant[1] * 2) + 1),
191                            size = size,
192                            filename=filename, chatty=chatty), (0,size[1]))
193    out_img.paste(subdivide(img = img,
194                            level=(level + 1),
195                            quadrant=((quadrant[0] * 2) + 1,
196                                      (quadrant[1] * 2) + 0),
197                            size = size,
198                            filename=filename, chatty=chatty), (size[0], 0))
199    out_img.paste(subdivide(img,
200                            level=(level + 1),
201                            quadrant=((quadrant[0] * 2) + 1,
202                                      (quadrant[1] * 2) + 1),
203                            size = size,
204                            filename=filename, chatty=chatty), (size[0], size[1]))
205
206    out_img = out_img.resize(size, scaling_filter)
207
208    # In the future, we may want to verify the quality. Right now we let
209    # the underlying code handle bad values (other than a non-int)
210    if not quality:
211        out_img.save(filename % (level, quadrant[0], quadrant[1]))
212    else:
213        out_img.save(filename % (level, quadrant[0], quadrant[1]),
214                     quality=quality)
215    if chatty:
216        print '-', '  ' * level, filename % (level, quadrant[0], quadrant[1])
217    return out_img
218
219
220
221if __name__ == '__main__':
222    exit(main())