"""
Provides tools (currently: only functions, not a real service) for image
processing.
"""
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
from cStringIO import StringIO
import hashlib
from PIL import Image
__all__ = ['resize', 'RESIZE_MODES', 'SCALE', 'FIT', 'CROP']
# resize modes
#: resize without retaining original proportions
SCALE = 'scale'
#: resize image and retain original proportions. Image width and height will be
#: at most specified width and height, respectively; At least width or height
#: will be equal to specified width and height, respectively.
FIT = 'fit'
#: crop image and resize so that it matches specified width and height.
CROP = 'crop'
RESIZE_MODES = frozenset({SCALE, FIT, CROP})
# TODO: cache to file
cache = {}
def open_image(img):
if isinstance(img, basestring):
img = StringIO(img)
pos = img.tell()
image = Image.open(img)
img.seek(pos)
return image
def get_format(img):
image = open_image(img)
return image.format
def get_size(img):
image = open_image(img)
return image.size
def get_save_format(fmt):
if fmt in ('GIF', 'PNG'):
return 'PNG'
return 'JPEG'
[docs]def resize(orig, width, height, mode=FIT):
"""
"""
if isinstance(orig, basestring):
orig = StringIO(orig)
digest = hashlib.md5(orig.read()).digest()
cache_key = (digest, mode, width, height)
if cache_key in cache:
return cache[cache_key]
orig.seek(0)
image = open_image(orig)
format = image.format
x, y = image.size
if (x, y) == (width, height):
orig.seek(0)
return orig.read()
if mode is SCALE:
image = image.resize((width, height), Image.LANCZOS)
assert image.size == (width, height)
elif mode is FIT:
image.thumbnail((width, height), Image.LANCZOS)
x1, y1 = image.size
assert x1 == width or y1 == height
elif mode is CROP:
image = _crop_and_resize(image, width, height)
assert image.size == (width, height)
output = StringIO()
image.save(output, get_save_format(format))
converted = output.getvalue()
cache[cache_key] = converted
return converted
def _crop_and_resize(image, width, height=0):
if not height:
height = width
# Compute cropping coordinates
x0, y0 = image.size
w_ratio = 1.0 * x0 / width
h_ratio = 1.0 * y0 / height
if h_ratio > w_ratio:
x1 = 0
x2 = x0
y1 = int((y0 - x0 * height / width) / 2)
y2 = int((y0 + x0 * height / width) / 2)
else:
x1 = int((x0 - y0 * width / height) / 2)
x2 = int((x0 + y0 * width / height) / 2)
y1 = 0
y2 = y0
image = image.crop((x1, y1, x2, y2))
# image.load()
image = image.resize((width, height), Image.LANCZOS)
return image