Djangosaur
Django thumbnails, resize image with PIL and python

There is a wide variety of django filters and tags to crop, resize or make a thumbnail out of an image but most of them seem to reinvent the wheel.

How to resize, crop or create a thumbnail with python and PIL

You need to install the Python Image Library and this snake image to run this example.

#!/usr/bin/env python
try:
    from PIL import Image, ImageOps
except ImportError:
    import Image
    import ImageOps


image = Image.open('snake.jpg')

# ImageOps compatible mode
if image.mode not in ("L", "RGB"):
    image = image.convert("RGB")


imageresize = image.resize((200,200), Image.ANTIALIAS)
imageresize.save('resize_200_200_aa.jpg', 'JPEG', quality=75)

image.thumbnail((200,200), Image.ANTIALIAS)
image.save('thumbnail_200_200_aa.jpg', 'JPEG', quality=75)

imagefit = ImageOps.fit(image, (200, 200), Image.ANTIALIAS)
imagefit.save('fit_200_200_aa.jpg', 'JPEG', quality=75)

Image.resize

image resize

resize changes the aspect ratio of the image.

Image.thumbnail

image thumbnail

thumbnail keeps the aspect ratio of the image and scales it to the defined area.

Imageops.fit

image imageops fit

fit keeps the aspect ratio and crops the image to the size of the defined area.

Conclusion

We want our images to look nice so we will discard the resize function and implement thumbnail and fit inside django filters.

Django filters to scale and crop images

Our filter will receive an imagefield object (with path and url properties) and dimensions. It will check if the image has already been generated and serve it, otherwise, it will create a new resized image.

# my_apps/image/templatetags/image_tags.py
import os.path

from django import template

FMT = 'JPEG'
EXT = 'jpg'
QUAL = 75

register = template.Library()


def resized_path(path, size, method):
    "Returns the path for the resized image."

    dir, name = os.path.split(path)
    image_name, ext = name.rsplit('.', 1)
    return os.path.join(dir, '%s_%s_%s.%s' % (image_name, method, size, EXT))


def scale(imagefield, size, method='scale'):
    """ 
    Template filter used to scale an image
    that will fit inside the defined area.

    Returns the url of the resized image.

    {% load image_tags %}
    {{ profile.picture|scale:"48x48" }}
    """

    # imagefield can be a dict with "path" and "url" keys
    if imagefield.__class__.__name__ == 'dict':
        imagefield = type('imageobj', (object,), imagefield)

    image_path = resized_path(imagefield.path, size, method)

    if not os.path.exists(image_path):
        try:
            import Image
        except ImportError:
            try:
                from PIL import Image
            except ImportError:
                raise ImportError('Cannot import the Python Image Library.')

        image = Image.open(imagefield.path)

        # normalize image mode
        if image.mode != 'RGB':
            image = image.convert('RGB')

        # parse size string 'WIDTHxHEIGHT'
        width, height = [int(i) for i in size.split('x')]

        # use PIL methods to edit images
        if method == 'scale':
            image.thumbnail((width, height), Image.ANTIALIAS)
            image.save(image_path, FMT, quality=QUAL)

        elif method == 'crop':
            try:
                import ImageOps
            except ImportError:
                from PIL import ImageOps

            ImageOps.fit(image, (width, height), Image.ANTIALIAS
                        ).save(image_path, FMT, quality=QUAL)

    return resized_path(imagefield.url, size, method)



def crop(imagefield, size):
    """
    Template filter used to crop an image
    to make it fill the defined area.

    {% load image_tags %}
    {{ profile.picture|crop:"48x48" }}

    """
    return scale(imagefield, size, 'crop')


register.filter('scale', scale)
register.filter('crop', crop)

Those filters work with imagefields, but you can pass them any object that has path and url properties, even a dict.

Edited images will be saved under the same path as the original file.

  1. lambda-dusk reblogged this from djangosaur and added:
    I found it on tumblr.
  2. djangosaur posted this