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.

7 comments

  • ciastek

    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!

  • andriusm

    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.

  • Nico de Groot

    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)

  • Michelle

    Hi… You say this: Simply specify a directory with .jpg files in it and the script will go through each file

    How/where do you specify the directory within the context of that script?

  • Jonatan Bijl

    On my pogoplug the script crashed on the heavy operation of rotating. I optimized it like this:
    1) do rotation after the first resizing
    2) use thumbnail for both the resizes
    3) use img.transpose for the rotations

    This way I could resize 18 megapixel photos on a system with just 128M ram and a small ARM processor
    Thanks for the script.

  • karan

    hi

    thanks for the post .

    1 question – why have you used the maxsize method ?

    have you done it because if we have a large image – time taken will be high to create the thumbnail ?

    I shall look forward to the reason about why we have created maxsize method

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>