You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

123 lines
3.6 KiB

  1. from contextlib import contextmanager
  2. from io import BytesIO
  3. import PIL
  4. from PIL import Image
  5. @contextmanager
  6. def open_django_file(field_file):
  7. field_file.open()
  8. try:
  9. yield field_file
  10. finally:
  11. field_file.close()
  12. def scale_and_crop_iter(image, options):
  13. """
  14. Generator which will yield several variations on the input image.
  15. Resize, crop and/or change quality of image.
  16. :param image: Source image file
  17. :type image : :class:`django.core.files.images.ImageFile
  18. :param options: List of option dictionaries, See scale_and_crop_single
  19. argument names for available keys.
  20. :type options: list of dict
  21. """
  22. with open_django_file(image) as img:
  23. im = Image.open(img)
  24. im.load()
  25. for opts in options:
  26. # Use already-loaded file when cropping.
  27. yield scale_and_crop_single(im, **opts)
  28. # this neat function is based on easy-thumbnails
  29. def scale_and_crop_single(image, size, crop=False, upscale=False, quality=None):
  30. """
  31. Resize, crop and/or change quality of an image.
  32. :param image: Source image file
  33. :type image: :class:`PIL.Image`
  34. :param size: Size as width & height, zero as either means unrestricted
  35. :type size: tuple of two int
  36. :param crop: Truncate image or not
  37. :type crop: bool
  38. :param upscale: Enable scale up
  39. :type upscale: bool
  40. :param quality: Value between 1 to 95, or None for keep the same
  41. :type quality: int or NoneType
  42. :return: Handled image
  43. :rtype: class:`PIL.Image`
  44. """
  45. im = image
  46. source_x, source_y = [float(v) for v in im.size]
  47. target_x, target_y = [float(v) for v in size]
  48. if crop or not target_x or not target_y:
  49. scale = max(target_x / source_x, target_y / source_y)
  50. else:
  51. scale = min(target_x / source_x, target_y / source_y)
  52. # Handle one-dimensional targets.
  53. if not target_x:
  54. target_x = source_x * scale
  55. elif not target_y:
  56. target_y = source_y * scale
  57. if scale < 1.0 or (scale > 1.0 and upscale):
  58. im = im.resize((int(source_x * scale), int(source_y * scale)),
  59. resample=Image.ANTIALIAS)
  60. if crop:
  61. # Use integer values now.
  62. source_x, source_y = im.size
  63. # Difference between new image size and requested size.
  64. diff_x = int(source_x - min(source_x, target_x))
  65. diff_y = int(source_y - min(source_y, target_y))
  66. if diff_x or diff_y:
  67. # Center cropping (default).
  68. half_diff_x, half_diff_y = diff_x // 2, diff_y // 2
  69. box = [half_diff_x, half_diff_y,
  70. min(source_x, int(target_x) + half_diff_x),
  71. min(source_y, int(target_y) + half_diff_y)]
  72. # Finally, crop the image!
  73. im = im.crop(box)
  74. # Close image and replace format/metadata, as PIL blows this away.
  75. # We mutate the quality, but needs to passed into save() to actually
  76. # do anything.
  77. info = image.info
  78. if quality is not None:
  79. info['quality'] = quality
  80. im.format, im.info = image.format, info
  81. return im
  82. def write_image_in_memory(img):
  83. # save to memory
  84. buf = BytesIO()
  85. try:
  86. img.save(buf, img.format, **img.info)
  87. except IOError:
  88. if img.info.get('progression'):
  89. orig_MAXBLOCK = PIL.ImageFile.MAXBLOCK
  90. temp_MAXBLOCK = 1048576
  91. if orig_MAXBLOCK >= temp_MAXBLOCK:
  92. raise
  93. PIL.ImageFile.MAXBLOCK = temp_MAXBLOCK
  94. try:
  95. img.save(buf, img.format, **img.info)
  96. finally:
  97. PIL.ImageFile.MAXBLOCK = orig_MAXBLOCK
  98. else:
  99. raise
  100. return buf