Creating thumbnails from photos with Python PIL

This time around I want to share with you a little Python library I found called PIL. If you’re a Python developer chances are you already know of it, but I use Python only from time to time, usually to automate some tasks so I was very excited to come across such a library. First thing’s first, download PIL and install it. I’m using it on a Windows platform so I used the packaged binary version, but you can get the source code and use it on any platform that supports Python. There’s also links to documentation on the same page. If you already know Python it should be fairly easy to use the library. What I liked best about it is speed, though my comparison is to Java-based image processing which can be painfully slow. One of the first things I did with it was create a simple little thumbnail generator to help me handle my photo collection which you can see below. Simply specify a directory with .jpg files in it and the script will go through each file, shrink it to fit a chosen format of 1024×768 while maintaining the aspect ratio and create a thumbnail for it. All the created files are placed in a directory named album which is relative to your working directory. Oh, almost forgot, it tries to read the EXIF data of the photos and if Orientation tag is found it automatically rotates the image for your viewing pleasure.

import os
import sys
import Image
from PIL import Image
from PIL.ExifTags import TAGS

albumdir = 'album'
thumbdir = albumdir + '/thumbs'

def mkdir(dirname):
    try:
        os.mkdir(dirname)
    except:
        pass

def maxSize(image, maxSize, method = 3):
    imAspect = float(image.size[0])/float(image.size[1])
    outAspect = float(maxSize[0])/float(maxSize[1])

    if imAspect >= outAspect:
        return image.resize((maxSize[0], int((float(maxSize[0])/imAspect) + 0.5)), method)
    else:
        return image.resize((int((float(maxSize[1])*imAspect) + 0.5), maxSize[1]), method)

def processImage(imgdir, fname):
    img = Image.open(imgdir + fname)
    exif = img._getexif()
    if exif != None:
        for tag, value in exif.items():
            decoded = TAGS.get(tag, tag)
            if decoded == 'Orientation':
                if value == 3: img = img.rotate(180)
                if value == 6: img = img.rotate(270)
                if value == 8: img = img.rotate(90)
                break
    img = maxSize(img, (1024, 768), Image.ANTIALIAS)
    img.save(albumdir + '/' + fname, 'JPEG', quality=100)
    img.thumbnail((192, 192), Image.ANTIALIAS)
    img.save(thumbdir + '/' + fname, 'JPEG')

def main():
    if len(sys.argv) < 2:
        print "Usage: album.py imgdir"
        exit(0)
    else:
        imgdir = sys.argv[1] + '/'

    mkdir(albumdir)
    mkdir(thumbdir)
    files = os.listdir(imgdir)

    for fname in files:
        if fname.lower().endswith('.jpg'):
            processImage(imgdir, fname)

    print 'done'

if __name__ == "__main__":
    main()

Google for some PIL tutorials to learn more, there are some nice pages out there like this one.

Discussion

7 Comments

  • this is wrong:
    if value == 6: img = img.rotate(90)
    if value == 8: img = img.rotate(270)

    there should be:
    if value == 6: img = img.rotate(270)
    if value == 8: img = img.rotate(90)

    because PIL:Image:rotate() rotates counterclockwise.

    Thanks for the code!

  • You’re right. I actually fixed this in my own script when experimenting with PIL, but forgot to fix it in the post :) Thanks for pointing it out.

  • Nice, two remarks:
    1. you can use os.path.join(a,b,c) for a more cross platform approach instead of concatenating ‘/’ like in line 37
    2. Did you know that saving the image using PIL after rotating removes the exif data? See http://stackoverflow.com/questions/400788/resize-image-in-python-without-losing-exif-data
    (More most purposes this isn’t a problem unless you want to view the image with a exif-capable viewer or send the image to a photo finisher. If you do want to add this remember to update the orientation info)