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

resize changes the aspect ratio of the image.
Image.thumbnail
thumbnail keeps the aspect ratio of the image and scales it to the defined area.
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.