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
    marb         The bottom margin in pixels

    padding      The padding between images in pixels

    returns a PIL image object.
    """

    # 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)

    count = 0
    # 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:
                # Read in an image and resize appropriately
                img = Image.open(fnames[count]).resize((photow,photoh))
            except:
                break
            inew.paste(img,bbox)
            count += 1
    return inew

This fork improves the memory performance: rather than loading in all the images at the start, only load each image when required. Original description follows.


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:

import glob
from PIL import Image

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')
inew.show()

3 comments

Barry Walker 11 years, 6 months ago  # | flag

I guess "marb" must be the bottom margin... ;o)

just a clerical error I noticed on the docstring...

See yer...

Bazza...

Hugo (author) 11 years, 6 months ago  # | flag

Thanks Bazza, fixed!

Howard Jones 8 years, 11 months ago  # | flag

Here's my variation on your fork that actually scales the image proportionally to fit the thumbnail box...

count = 0 # 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

        try:
            img = Image.open(fnames[count])
            img_bbox = img.getbbox()
            width = img_bbox[2] - img_bbox[0]
            height = img_bbox[3] - img_bbox[1]

            # calculate a scaling factor depending on fitting the larger dimension into the thumbnail                
            ratio = max(height/float(photoh), width/float(photow))

            newWidth = int(width/ratio)
            newHeight = int(height/ratio)
            newSize = (newWidth, newHeight)

            img = img.resize(newSize)

        except:
            break

        new_left = left
        new_upper = upper

        if ( newWidth < photow):
            new_left = ( left + ((photow - newWidth)/2))

        if ( newHeight < photoh):
            new_upper = (upper + ((photoh - newHeight)/2))

        inew.paste(img, (new_left, new_upper))
        count += 1
Created by Hugo on Fri, 21 Sep 2012 (PSF)
Python recipes (4591)
Hugo's recipes (2)

Required Modules

Other Information and Tasks