bgneal@265: """ bgneal@265: This file contains common utility functions for manipulating images for bgneal@265: the rest of the applications in the project. bgneal@265: """ bgneal@265: from PIL import ImageFile bgneal@265: from PIL import Image bgneal@265: bgneal@265: bgneal@265: def parse_image(file): bgneal@265: """ bgneal@265: Returns a PIL Image from the supplied Django file object. bgneal@265: Throws IOError if the file does not parse as an image file or some other bgneal@265: I/O error occurred. bgneal@265: bgneal@265: """ bgneal@265: parser = ImageFile.Parser() bgneal@265: for chunk in file.chunks(): bgneal@265: parser.feed(chunk) bgneal@265: image = parser.close() bgneal@265: return image bgneal@265: bgneal@265: bgneal@265: def downscale_image_square(image, size): bgneal@265: """ bgneal@265: Scale an image to the square dimensions given by size (in pixels). bgneal@265: The new image is returned. bgneal@265: If the image is already smaller than (size, size) then no scaling bgneal@265: is performed and the image is returned unchanged. bgneal@265: bgneal@265: """ bgneal@265: # don't upscale bgneal@265: if (size, size) >= image.size: bgneal@265: return image bgneal@265: bgneal@265: (w, h) = image.size bgneal@265: if w > h: bgneal@265: diff = (w - h) / 2 bgneal@265: image = image.crop((diff, 0, w - diff, h)) bgneal@265: elif h > w: bgneal@265: diff = (h - w) / 2 bgneal@265: image = image.crop((0, diff, w, h - diff)) bgneal@265: image = image.resize((size, size), Image.ANTIALIAS) bgneal@265: return image bgneal@837: bgneal@837: bgneal@837: # Various image transformation functions: bgneal@837: def flip_horizontal(im): bgneal@837: return im.transpose(Image.FLIP_LEFT_RIGHT) bgneal@837: bgneal@837: def flip_vertical(im): bgneal@837: return im.transpose(Image.FLIP_TOP_BOTTOM) bgneal@837: bgneal@837: def rotate_180(im): bgneal@837: return im.transpose(Image.ROTATE_180) bgneal@837: bgneal@837: def rotate_90(im): bgneal@837: return im.transpose(Image.ROTATE_90) bgneal@837: bgneal@837: def rotate_270(im): bgneal@837: return im.transpose(Image.ROTATE_270) bgneal@837: bgneal@837: def transpose(im): bgneal@837: return rotate_90(flip_horizontal(im)) bgneal@837: bgneal@837: def transverse(im): bgneal@837: return rotate_90(flip_vertical(im)) bgneal@837: bgneal@837: # From http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html bgneal@837: # EXIF Orientation tag values: bgneal@837: # 1 = Horizontal (normal) bgneal@837: # 2 = Mirror horizontal bgneal@837: # 3 = Rotate 180 bgneal@837: # 4 = Mirror vertical bgneal@837: # 5 = Mirror horizontal and rotate 270 CW bgneal@837: # 6 = Rotate 90 CW bgneal@837: # 7 = Mirror horizontal and rotate 90 CW bgneal@837: # 8 = Rotate 270 CW bgneal@837: bgneal@837: ORIENT_FUNCS = { bgneal@837: 2: flip_horizontal, bgneal@837: 3: rotate_180, bgneal@837: 4: flip_vertical, bgneal@837: 5: transpose, bgneal@837: 6: rotate_270, bgneal@837: 7: transverse, bgneal@837: 8: rotate_90, bgneal@837: } bgneal@837: bgneal@837: ORIENT_TAG = 0x112 bgneal@837: bgneal@837: bgneal@837: def orient_image(im): bgneal@837: """Transforms the given image according to embedded EXIF data. bgneal@837: bgneal@837: The image instance, im, should be a PIL Image. bgneal@837: If there is EXIF information for the image, and the orientation tag bgneal@837: indicates that the image should be transformed, perform the transformation. bgneal@837: bgneal@837: Returns a tuple of the form (flag, image) where flag is True if the image bgneal@837: was oriented and False otherwise. image is either a new transformed image or bgneal@837: the original image instance. bgneal@837: bgneal@837: """ bgneal@837: if hasattr(im, '_getexif'): bgneal@837: exif = im._getexif() bgneal@837: if exif and ORIENT_TAG in exif: bgneal@839: orientation = exif[ORIENT_TAG] bgneal@839: func = ORIENT_FUNCS.get(orientation) bgneal@839: if func: bgneal@839: return (True, func(im)) bgneal@837: bgneal@837: return (False, im)