The perfect crop using PIL

I’ve always wanted to display good looking thumbnail pictures with each thumbnail having the same width and height (dimension). Using some calculations, cropping and resizing, I was able to create thumbnails cropped from center and scaled down to my width/height of choice. This is done using Python’s PIL or Python Imaging Library.

The strategy

I don’t know why there is no already built package or helper module for this but I just did some experiments and it works so well. Below is the flow of how the picture is being cropped and scaled.

  • Get the target width/height – which is the thumbnail size
  • Scale up the thumbnail size so that it is as close as the original width/height. We don’t want to crop a giant picture then get the tiny part of it as thumbnail, do we?
  • Crop the scaled up dimension.
  • Resize the cropped picture into the target dimension.
  • Save the cropped and resized version of the picture to file

The code

My work is written in Python using the Django framework.

First, we need to know the target width/height or the target dimension where we wanted our thumbnails to be cropped and resized. This code can be done by using a simple configuration, or we can even hard code it.

Next, assuming that the original picture is a large photo and the thumbnail is like 170×170, we need to scale 170×170 to get larger as close as the original image. This is because we wanted to scale down the large picture but at the same time crop it if it is not same ratio as 170×170. Below is the sample code used to scale up 170×170 against the original dimension.

@classmethod
def get_crop_size_by_scaleup(cls, input_dimension, scale_dimension):
    """
    From the original dimension, a target dimension is given
    Before resizing, the original image should be cropped based on the 
    best scale dimension.
    """
    # If somehow, scale dimension is larger than the original size,
    # don't scale and return the original instead
    new_size = input_dimension
    if input_dimension[0] >= scale_dimension[0] or input_dimension[1] >= scale_dimension[1]:
        by_width = cls.get_size_by_width(scale_dimension, input_dimension)
        by_height = cls.get_size_by_height(scale_dimension, input_dimension)
        if by_width[0] <= input_dimension[0] and by_width[1] <= input_dimension[1]:
            new_size = by_width
        else:
            new_size = by_height
    return new_size

@classmethod
def get_size_by_width(cls, input_dimension, scale_dimension):
    width = scale_dimension[0]
    # Get the height based on scaled image dimension against base width
    width_percent = (width / float(input_dimension[0]))
    height = int((float(input_dimension[1]) * float(width_percent)))
    return (width, height)

@classmethod
def get_size_by_height(cls, input_dimension, scale_dimension):
    height = scale_dimension[1]
    # Get the width based on scaled image dimension against base height
    height_percent = (height / float(input_dimension[1]))
    width = int((float(input_dimension[0]) * float(height_percent)))
    return (width, height)

The method get_crop_size_by_scaleup decides what is the best crop dimension for the original photo, given that we know the original picture dimension and target thumbnail dimension. It could follow the width and crop the excess height or vice-versa.

Next, we need to crop the picture from the center so what we could at least get a good view when converted to thumbnail. It would trim outer portion but this is what we need anyway.

left = (img.size[0] - crop_size[0])/2
top = (img.size[1] - crop_size[1])/2
right = (img.size[0] + crop_size[0])/2
bottom = (img.size[1] + crop_size[1])/2

cropped = img.crop((left, top, right, bottom))

The last part is easier as there are built-in methods existing in PIL. Let’s resize and save it finally using the small thumbnail dimension (referred to as target_dimension).

cropped.thumbnail(target_dimension, Image.ANTIALIAS)
cropped.save(destination, 'JPEG')

Where destination is the full path filename of the thumbnail picture. That’s it.

Enjoy and share!

This entry was posted in Python and tagged , , , , . Bookmark the permalink.

Related Posts

4 Responses to The perfect crop using PIL

  1. jojosiao says:

    Hello. Thanks for this post.
    I have a question. Do you think generating thumbnails should be done in the back end? or should the logic in generating thumbnails be included in the controller?
    I thought I should ask your opinion in that scenario.

  2. lysender says:

    Nice question. In my case, generating thumbnails is done via background task, something that doesn’t make sense in a controller. It’s a very fat model wrapped around another fat model.

  3. PIL contains cool thing, ImageOps. You can use it like this:

    thumbnail = ImageOps.fit(
    thumbnail,
    (height, width),
    Image.ANTIALIAS
    )

  4. lysender says:

    @Igor, I might not be aware of that when I was developing this application. Will check that.

Leave a Reply

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