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

The Python Imaging Library (PIL) makes many tasks easy in digital photography. This recipe shows how to make a "contact sheet" of images, a single image with thumbnails of many different pictures. It's limited in that it will only work with pictures of the same shape, but you can make some really fun images.

Python, 53 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
def make_contact_sheet(fnames,(ncols,nrows),(photow,photoh),
                       (marl,mart,marr,marb),
                       padding):
    """\
    Make a contact sheet from a group of filenames:

    fnames       A list of names of the image files
    
    ncols        Number of columns in the contact sheet
    nrows        Number of rows in the contact sheet
    photow       The width of the photo thumbs in pixels
    photoh       The height of the photo thumbs in pixels

    marl         The left margin in pixels
    mart         The top margin in pixels
    marr         The right margin in pixels
    marl         The left margin in pixels

    padding      The padding between images in pixels

    returns a PIL image object.
    """

    # Read in all images and resize appropriately
    imgs = [Image.open(fn).resize((photow,photoh)) for fn in fnames]

    # Calculate the size of the output image, based on the
    #  photo thumb sizes, margins, and padding
    marw = marl+marr
    marh = mart+ marb

    padw = (ncols-1)*padding
    padh = (nrows-1)*padding
    isize = (ncols*photow+marw+padw,nrows*photoh+marh+padh)

    # Create the new image. The background doesn't have to be white
    white = (255,255,255)
    inew = Image.new('RGB',isize,white)

    # Insert each thumb:
    for irow in range(nrows):
        for icol in range(ncols):
            left = marl + icol*(photow+padding)
            right = left + photow
            upper = mart + irow*(photoh+padding)
            lower = upper + photoh
            bbox = (left,upper,right,lower)
            try:
                img = imgs.pop(0)
            except:
                break
            inew.paste(img,bbox)
    return inew

I recently used the blog entry at the URL: http://www.mikematas.com/blog/2005/01/how-to-make-life-poster.html to make a Life Poster with rather striking results. I started thinking about how to do this in Python (being that kind of person), and it turns out that PIL makes this very easy indeed.

The original recipe requires IPhoto, and uses it to crop a set of pictures to the same aspect ratio, and then build a contact sheet. That sheet is then printed as a PDF, and then converted to a TIFF by Photoshop, then imported back to IPhoto for printing as a poster.

This program assumes you have a set of pictures already cropped appropriately, and then uses PIL to assemble thumbnails into a single picture. The fun part is that since you own the code, you are free to play around with lots of different settings.

I haven't actually printed out a picture this way, so I can't guarantee that the results are in any way comparable to the IPhoto/Photoshop recipe, but the images certainly look nice on my computer.

I've been testing the code with something like the following: <pre> def test(): ncols,nrows = 7,14

files = glob.glob('*.TIFF')

# Don't bother reading in files we aren't going to use
if len(files) > ncols*nrows: files = files[:ncols*nrows]

# These are all in terms of pixels:
photow,photoh = 200,150
photo = (photow,photoh)

margins = [5,5,5,5]

padding = 1

inew = make_contact_sheet(files,(ncols,nrows),photo,margins,padding)
inew.save('bs.png')
#os.system('display bs.png')
os.system('open bs.png')

</pre>

1 comment

Hugo 9 years, 2 months ago  # | flag

Thanks for this, it's much, much quicker than using ImageMagick's montage.

I've made a fork that vastly improves the memory performance: instead of loading in all the images at the start, just load them when needed.

  • Delete imgs = [Image.open(fn).resize((photow,photoh)) for fn in fnames]
  • Add count = 0 before the loop.
  • Replace img = imgs.pop(0) with img = Image.open(fnames[count]).resize((photow,photoh))
  • Add count += 1 after inew.paste(img,bbox)

See the fork here: http://code.activestate.com/recipes/578267-use-pil-to-make-a-contact-sheet-montage-of-images/

Created by Rick Muller on Mon, 2 May 2005 (PSF)
Python recipes (4591)
Rick Muller's recipes (10)

Required Modules

  • (none specified)

Other Information and Tasks