from contextlib import contextmanager from io import BytesIO import PIL from PIL import Image @contextmanager def open_django_file(field_file): field_file.open() try: yield field_file finally: field_file.close() def scale_and_crop_iter(image, options): """ Generator which will yield several variations on the input image. Resize, crop and/or change quality of image. :param image: Source image file :type image : :class:`django.core.files.images.ImageFile :param options: List of option dictionaries, See scale_and_crop_single argument names for available keys. :type options: list of dict """ with open_django_file(image) as img: im = Image.open(img) im.load() for opts in options: # Use already-loaded file when cropping. yield scale_and_crop_single(im, **opts) # this neat function is based on easy-thumbnails def scale_and_crop_single(image, size, crop=False, upscale=False, quality=None): """ Resize, crop and/or change quality of an image. :param image: Source image file :type image: :class:`PIL.Image` :param size: Size as width & height, zero as either means unrestricted :type size: tuple of two int :param crop: Truncate image or not :type crop: bool :param upscale: Enable scale up :type upscale: bool :param quality: Value between 1 to 95, or None for keep the same :type quality: int or NoneType :return: Handled image :rtype: class:`PIL.Image` """ im = image source_x, source_y = [float(v) for v in im.size] target_x, target_y = [float(v) for v in size] if crop or not target_x or not target_y: scale = max(target_x / source_x, target_y / source_y) else: scale = min(target_x / source_x, target_y / source_y) # Handle one-dimensional targets. if not target_x: target_x = source_x * scale elif not target_y: target_y = source_y * scale if scale < 1.0 or (scale > 1.0 and upscale): im = im.resize((int(source_x * scale), int(source_y * scale)), resample=Image.ANTIALIAS) if crop: # Use integer values now. source_x, source_y = im.size # Difference between new image size and requested size. diff_x = int(source_x - min(source_x, target_x)) diff_y = int(source_y - min(source_y, target_y)) if diff_x or diff_y: # Center cropping (default). half_diff_x, half_diff_y = diff_x // 2, diff_y // 2 box = [half_diff_x, half_diff_y, min(source_x, int(target_x) + half_diff_x), min(source_y, int(target_y) + half_diff_y)] # Finally, crop the image! im = im.crop(box) # Close image and replace format/metadata, as PIL blows this away. # We mutate the quality, but needs to passed into save() to actually # do anything. info = image.info if quality is not None: info['quality'] = quality im.format, im.info = image.format, info return im def write_image_in_memory(img): # save to memory buf = BytesIO() try: img.save(buf, img.format, **img.info) except IOError: if img.info.get('progression'): orig_MAXBLOCK = PIL.ImageFile.MAXBLOCK temp_MAXBLOCK = 1048576 if orig_MAXBLOCK >= temp_MAXBLOCK: raise PIL.ImageFile.MAXBLOCK = temp_MAXBLOCK try: img.save(buf, img.format, **img.info) finally: PIL.ImageFile.MAXBLOCK = orig_MAXBLOCK else: raise return buf