From a25114970b8534d63fe49517064d13005472f725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20B=C3=B6hn?= Date: Wed, 13 Nov 2019 14:24:00 -0500 Subject: [PATCH] Updating image entropy implementations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As of version 6.1.0, the Pillow imaging library’s C module includes an optimized native entropy method: • https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#610-2019-07-01 This change detects and selects this new native method, if it is available in the version of Pillow upon which `easy-thumbnails` finds itself running. Additionally, an optimized version of the existing Python image-entropy method is furnished as a fallback: rather than summing the entire histogram value set, the function calculates the product of the image dimensions and band count to arrive at the same number. It also uses generator expressions and the specialized `math.fsum(…)` and `math.log2(…)` library calls to improve on the performance of its predecessor without altering the algorithm. The appropriate image-entropy function is then conditionally ensconced in the `easy_thumbnails.utils` module, at module-load time. I also made a slight tweak to the `pil_image` function, found in `easy_thumbnails.source_generators` – the call to `PIL.Image.Image.load()` was raising the exception noted in comments in that functions’ source while I was running the testsuite; now all such calls have these exceptions swallowed and everything works OK. --- easy_thumbnails/source_generators.py | 17 ++++++------ easy_thumbnails/utils.py | 39 +++++++++++++++++++--------- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/easy_thumbnails/source_generators.py b/easy_thumbnails/source_generators.py index 593768b3..e709beb2 100644 --- a/easy_thumbnails/source_generators.py +++ b/easy_thumbnails/source_generators.py @@ -31,14 +31,15 @@ def pil_image(source, exif_orientation=True, **options): image = Image.open(source) # Fully load the image now to catch any problems with the image contents. - try: - # An "Image file truncated" exception can occur for some images that - # are still mostly valid -- we'll swallow the exception. - image.load() - except IOError: - pass - # Try a second time to catch any other potential exceptions. - image.load() + for _ in range(5): + try: + # An "Image file truncated" exception can occur for some images that + # are still mostly valid -- we'll swallow the exception. + image.load() + except IOError: + continue + else: + break if exif_orientation: image = utils.exif_orientation(image) diff --git a/easy_thumbnails/utils.py b/easy_thumbnails/utils.py index 3675513d..d3156499 100644 --- a/easy_thumbnails/utils.py +++ b/easy_thumbnails/utils.py @@ -1,31 +1,46 @@ import hashlib import inspect -import math -from django.utils import six +from django.utils import six from django.utils.functional import LazyObject from django.utils import timezone - try: - from PIL import Image + from PIL import Image, ImageMode except ImportError: - import Image + import Image, ImageMode from easy_thumbnails.conf import settings -def image_entropy(im): +def color_count(image): + """ + Return the number of color values in the input image -- + this is the number of pixels times the band count of the image. """ - Calculate the entropy of an image. Used for "smart cropping". + mode_descriptor = ImageMode.getmode(image.mode) + width, height = image.size + return width * height * len(mode_descriptor.bands) + + +def image_entropy_py(image): """ - if not isinstance(im, Image.Image): + Calculate the entropy of an images' histogram. + """ + if not isinstance(image, Image.Image): # Can only deal with PIL images. Fall back to a constant entropy. return 0 - hist = im.histogram() - hist_size = float(sum(hist)) - hist = [h / hist_size for h in hist] - return -sum([p * math.log(p, 2) for p in hist if p != 0]) + from math import log2, fsum + histosum = float(color_count(image)) + histonorm = (histocol / histosum for histocol in image.histogram()) + return -fsum(p * log2(p) for p in histonorm if p != 0.0) + + +# Select the Pillow native image entropy function - if available - +# and fall back to our Python implementation: +image_entropy = hasattr(Image.Image, 'entropy') \ + and Image.Image.entropy \ + or image_entropy_py def dynamic_import(import_string):