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@891:         try:
bgneal@891:             exif = im._getexif()
bgneal@891:         except IndexError:
bgneal@891:             # Work around issue seen in Pillow
bgneal@891:             # https://github.com/python-pillow/Pillow/issues/518
bgneal@891:             exif = None
bgneal@891: 
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)