Skip to content

Instantly share code, notes, and snippets.

@sigilioso
Created June 19, 2012 22:58
Show Gist options
  • Star 64 You must be signed in to star a gist
  • Fork 26 You must be signed in to fork a gist
  • Save sigilioso/2957026 to your computer and use it in GitHub Desktop.
Save sigilioso/2957026 to your computer and use it in GitHub Desktop.
Python PIL Example: get a thumbnail by resizing and cropping an image.
# -*- coding: utf-8 -*-
import Image
def resize_and_crop(img_path, modified_path, size, crop_type='top'):
"""
Resize and crop an image to fit the specified size.
args:
img_path: path for the image to resize.
modified_path: path to store the modified image.
size: `(width, height)` tuple.
crop_type: can be 'top', 'middle' or 'bottom', depending on this
value, the image will cropped getting the 'top/left', 'midle' or
'bottom/rigth' of the image to fit the size.
raises:
Exception: if can not open the file in img_path of there is problems
to save the image.
ValueError: if an invalid `crop_type` is provided.
"""
# If height is higher we resize vertically, if not we resize horizontally
img = Image.open(img_path)
# Get current and desired ratio for the images
img_ratio = img.size[0] / float(img.size[1])
ratio = size[0] / float(size[1])
#The image is scaled/cropped vertically or horizontally depending on the ratio
if ratio > img_ratio:
img = img.resize((size[0], size[0] * img.size[1] / img.size[0]),
Image.ANTIALIAS)
# Crop in the top, middle or bottom
if crop_type == 'top':
box = (0, 0, img.size[0], size[1])
elif crop_type == 'middle':
box = (0, (img.size[1] - size[1]) / 2, img.size[0], (img.size[1] + size[1]) / 2)
elif crop_type == 'bottom':
box = (0, img.size[1] - size[1], img.size[0], img.size[1])
else :
raise ValueError('ERROR: invalid value for crop_type')
img = img.crop(box)
elif ratio < img_ratio:
img = img.resize((size[1] * img.size[0] / img.size[1], size[1]),
Image.ANTIALIAS)
# Crop in the top, middle or bottom
if crop_type == 'top':
box = (0, 0, size[0], img.size[1])
elif crop_type == 'middle':
box = ((img.size[0] - size[0]) / 2, 0, (img.size[0] + size[0]) / 2, img.size[1])
elif crop_type == 'bottom':
box = (img.size[0] - size[0], 0, img.size[0], img.size[1])
else :
raise ValueError('ERROR: invalid value for crop_type')
img = img.crop(box)
else :
img = img.resize((size[0], size[1]),
Image.ANTIALIAS)
# If the scale is the same, we do not need to crop
img.save(modified_path)
@oguzhan
Copy link

oguzhan commented Apr 29, 2014

Thanks a lot !
just had to convert the rounded values to integers.

@jetlej
Copy link

jetlej commented Jun 6, 2014

Here is the block edited to convert the rounded numbers to integers:

def resize_and_crop(img_path, modified_path, size, crop_type='top'):
    """
    Resize and crop an image to fit the specified size.

    args:
    img_path: path for the image to resize.
    modified_path: path to store the modified image.
    size: `(width, height)` tuple.
    crop_type: can be 'top', 'middle' or 'bottom', depending on this
    value, the image will cropped getting the 'top/left', 'middle' or
    'bottom/right' of the image to fit the size.
    raises:
    Exception: if can not open the file in img_path of there is problems
    to save the image.
    ValueError: if an invalid `crop_type` is provided.
    """
    # If height is higher we resize vertically, if not we resize horizontally
    img = Image.open(img_path)
    # Get current and desired ratio for the images
    img_ratio = img.size[0] / float(img.size[1])
    ratio = size[0] / float(size[1])
    #The image is scaled/cropped vertically or horizontally depending on the ratio
    if ratio > img_ratio:
        img = img.resize((size[0], int(round(size[0] * img.size[1] / img.size[0]))),
            Image.ANTIALIAS)
        # Crop in the top, middle or bottom
        if crop_type == 'top':
            box = (0, 0, img.size[0], size[1])
        elif crop_type == 'middle':
            box = (0, int(round((img.size[1] - size[1]) / 2)), img.size[0],
                int(round((img.size[1] + size[1]) / 2)))
        elif crop_type == 'bottom':
            box = (0, img.size[1] - size[1], img.size[0], img.size[1])
        else :
            raise ValueError('ERROR: invalid value for crop_type')
        img = img.crop(box)
    elif ratio < img_ratio:
        img = img.resize((int(round(size[1] * img.size[0] / img.size[1])), size[1]),
            Image.ANTIALIAS)
        # Crop in the top, middle or bottom
        if crop_type == 'top':
            box = (0, 0, size[0], img.size[1])
        elif crop_type == 'middle':
            box = (int(round((img.size[0] - size[0]) / 2)), 0,
                int(round((img.size[0] + size[0]) / 2)), img.size[1])
        elif crop_type == 'bottom':
            box = (img.size[0] - size[0], 0, img.size[0], img.size[1])
        else :
            raise ValueError('ERROR: invalid value for crop_type')
        img = img.crop(box)
    else :
        img = img.resize((size[0], size[1]),
            Image.ANTIALIAS)
    # If the scale is the same, we do not need to crop
    img.save(modified_path)

@JoshuaChi
Copy link

Cool!

@megahoy
Copy link

megahoy commented Jan 3, 2015

Use this function and got an error...

Traceback:
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py" in get_response
  111.                     response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "./board/views.py" in add
  111.                                 'middle')
File "./board/views.py" in resize_and_crop
  239.                 Image.ANTIALIAS)
File "/usr/lib/python2.7/dist-packages/PIL/Image.py" in resize
  1319.         self.load()
File "/usr/lib/python2.7/dist-packages/PIL/ImageFile.py" in load
  218.                             raise IOError("image file is truncated (%d bytes not processed)" % len(b))

Exception Type: IOError at /add
Exception Value: image file is truncated (16 bytes not processed)

Django Version: 1.7.1
Python Version: 2.7.6
Ubuntu 14.04 64-bit

@nunenuh
Copy link

nunenuh commented Mar 9, 2015

awsome man... I will use it in my project...

@ProDG
Copy link

ProDG commented Mar 10, 2015

For middle crop (the most popular case, imho) you can use ImageOps.fit.

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

@bjinwright
Copy link

@ProDG: You're the real MVP!!

@marioweid
Copy link

good hint @ProDG
but his script is faster
PIL : 0.029497146606445312
Script: 0.023000240325927734
Good job 👍

@travishen
Copy link

Thanks! @ProDG

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment