mirror of
https://github.com/pinry/pinry.git
synced 2025-11-13 16:45:41 +01:00
123 lines
3.6 KiB
Python
123 lines
3.6 KiB
Python
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
|