diff --git a/pilkit/lib.py b/pilkit/lib.py index 1ff1b4a..7444826 100644 --- a/pilkit/lib.py +++ b/pilkit/lib.py @@ -34,3 +34,34 @@ string_types = [basestring, str] except NameError: string_types = [str] + + +def getattrsafe(obj, attr, fallback_attr = None): + """Similar to getattr but accept dotted path + + The idea of this function is to pass dotted path to attribute. + If the path is missing then the fallback will be evaluated as dotted path also. + If the fallback is not present then the attribute error for the first path is thrown + + The main idea of this function is for compatibility with Pillow < 10 + + Example:: + + >>> from PIL import Image + >>> getattrsafe(Image, 'Transpose.FLIP_HORIZONTAL', 'FLIP_HORIZONTAL') + """ + names = attr.split('.') + res = obj + for i, name in enumerate(names): + try: + res = getattr(res, name) + except AttributeError: + missing = '.'.join(names[:i + 1]) + if fallback_attr is None: + raise AttributeError("'{}' object has no attribute '{}'".format(type(obj).__name__, missing)) + else: + try: + return getattrsafe(obj, fallback_attr) + except AttributeError: + raise AttributeError("'{}' object has no attribute '{}' or '{}'".format(type(obj).__name__, missing, fallback_attr)) + return res diff --git a/pilkit/processors/base.py b/pilkit/processors/base.py index 4d9c5f0..69f89f9 100644 --- a/pilkit/processors/base.py +++ b/pilkit/processors/base.py @@ -1,4 +1,4 @@ -from pilkit.lib import Image, ImageColor, ImageEnhance +from pilkit.lib import Image, ImageColor, ImageEnhance, getattrsafe class ProcessorPipeline(list): @@ -80,7 +80,7 @@ def process(self, img): # Handle palleted images. img = img.convert('RGBA') # Copy orignial image and flip the orientation. - reflection = img.copy().transpose(Image.FLIP_TOP_BOTTOM) + reflection = img.copy().transpose(Transpose.FLIP_VERTICAL) # Create a new image filled with the bgcolor the same size. background = Image.new("RGBA", img.size, background_color) # Calculate our alpha mask. @@ -116,11 +116,11 @@ class Transpose(object): """ AUTO = 'auto' - FLIP_HORIZONTAL = Image.FLIP_LEFT_RIGHT - FLIP_VERTICAL = Image.FLIP_TOP_BOTTOM - ROTATE_90 = Image.ROTATE_90 - ROTATE_180 = Image.ROTATE_180 - ROTATE_270 = Image.ROTATE_270 + FLIP_HORIZONTAL = getattrsafe(Image, 'Transpose.FLIP_LEFT_RIGHT', 'FLIP_LEFT_RIGHT') # noqa + FLIP_VERTICAL = getattrsafe(Image, 'Transpose.FLIP_TOP_BOTTOM', 'FLIP_TOP_BOTTOM') # noqa + ROTATE_90 = getattrsafe(Image, 'Transpose.ROTATE_90', 'ROTATE_90') + ROTATE_180 = getattrsafe(Image, 'Transpose.ROTATE_180', 'ROTATE_180') + ROTATE_270 = getattrsafe(Image, 'Transpose.ROTATE_270', 'ROTATE_270') methods = [AUTO] _EXIF_ORIENTATION_STEPS = { diff --git a/pilkit/processors/resize.py b/pilkit/processors/resize.py index 413e386..f1ed950 100644 --- a/pilkit/processors/resize.py +++ b/pilkit/processors/resize.py @@ -1,6 +1,6 @@ from .base import Anchor from .utils import resolve_palette -from ..lib import Image +from ..lib import Image, getattrsafe class Resize(object): @@ -8,6 +8,8 @@ class Resize(object): Resizes an image to the specified width and height. """ + LANCZOS = getattrsafe(Image, 'Resampling.LANCZOS', 'LANCZOS') + def __init__(self, width, height, upscale=True): """ :param width: The target width, in pixels. @@ -22,7 +24,7 @@ def __init__(self, width, height, upscale=True): def process(self, img): if self.upscale or (self.width < img.size[0] and self.height < img.size[1]): img = resolve_palette(img) - img = img.resize((self.width, self.height), Image.LANCZOS) + img = img.resize((self.width, self.height), self.LANCZOS) return img diff --git a/pilkit/utils.py b/pilkit/utils.py index f79dea7..187f825 100644 --- a/pilkit/utils.py +++ b/pilkit/utils.py @@ -3,7 +3,7 @@ import sys from io import UnsupportedOperation from .exceptions import UnknownExtension, UnknownFormat -from .lib import Image, ImageFile, StringIO, string_types +from .lib import Image, ImageFile, StringIO, string_types, getattrsafe RGBA_TRANSPARENCY_FORMATS = ['PNG', 'WEBP'] @@ -311,8 +311,8 @@ def prepare_image(img, format): alpha = img.split()[-1] mask = Image.eval(alpha, lambda a: 255 if a <= 128 else 0) - img = img.convert('RGB').convert('P', palette=Image.ADAPTIVE, - colors=255) + palette = getattrsafe(Image, 'Palette.ADAPTIVE', 'ADAPTIVE') + img = img.convert('RGB').convert('P', palette=palette, colors=255) img.paste(255, mask) save_kwargs['transparency'] = 255 else: @@ -344,7 +344,8 @@ def prepare_image(img, format): # quantization (above). Images that are already in P mode don't need # any quantization because their colors are already limited. if format == 'GIF': - img = img.convert('P', palette=Image.ADAPTIVE) + palette = getattrsafe(Image, 'Palette.ADAPTIVE', 'ADAPTIVE') + img = img.convert('P', palette=palette) if make_opaque: from .processors import MakeOpaque