Source code for kaiju_images.functions

"""

Available operations (builtin)
------------------------------

`Documentation <http://willow.readthedocs.org/en/latest/reference.html#builtin-operations>`_

=================================== ==================== ==================== ====================
Operation                           Pillow               Wand                 OpenCV
=================================== ==================== ==================== ====================
``resize(size)``                    ✓                    ✓
``crop(rect)``                      ✓                    ✓
``rotate(angle)``                   ✓                    ✓
``set_background_color_rgb(color)`` ✓                    ✓
``auto_orient()``                   ✓                    ✓
=================================== ==================== ==================== ====================

Available operations (custom)
-----------------------------

Probably they will work with PIL type images only.

===================================
Operation
===================================
``scale(factor)``
``abs_scale(size)``
``aspect_crop(ratio, rel_point)``
``blur(size)``
``flip_horizontal()``
``flip_vertical()``
===================================

"""

from fractions import Fraction
from typing import *

import PIL.Image
from willow.registry import registry
from willow.plugins.pillow import PillowImage
from willow.plugins.wand import WandImage

import kaiju_tools.jsonschema as schema

__all__ = [
    'aspect_crop', 'wand_blur', 'pillow_blur', 'scale', 'abs_scale',
    'pillow_flip_horizontal', 'pillow_flip_vertical', 'pillow_to_buffer_rgb', 'willow_to_buffer_rgb'
]


[docs]def pillow_flip_horizontal(image): image = image.image.transpose(PIL.Image.FLIP_LEFT_RIGHT) return PillowImage(image)
[docs]def pillow_flip_vertical(image): image = image.image.transpose(PIL.Image.FLIP_TOP_BOTTOM) return PillowImage(image)
[docs]def pillow_to_buffer_rgb(image): if image.image.mode != 'RGB': image = image.image.convert('RGB') return PillowImage(image) return PillowImage(image.image)
[docs]def willow_to_buffer_rgb(image): if image.image.mode != 'RGB': image = image.image.convert('RGB') return WandImage(image) return WandImage(image)
willow_to_buffer_rgb.__json_spec__ = None pillow_to_buffer_rgb.__json_spec__ = None pillow_flip_horizontal.__json_spec__ = None pillow_flip_vertical.__json_spec__ = None registry.register_operation(PillowImage, 'to_buffer_rgb', pillow_to_buffer_rgb) registry.register_operation(WandImage, 'to_buffer_rgb', willow_to_buffer_rgb) registry.register_operation(PillowImage, 'flip_horizontal', pillow_flip_horizontal) registry.register_operation(PillowImage, 'flip_vertical', pillow_flip_vertical)
[docs]def abs_scale(image, size: int): """Scales image so the longest dimension will become equal to `size` and the aspect ratio stays the same. :param image: :param size: size in px """ size = max(1, int(size)) x, y = image.get_size() x, y = int(x), int(y) aspect = Fraction(x, y) if x > y: x = size y = round(x / aspect) else: y = size x = round(y * aspect) image = image.resize((x, y)) return image
abs_scale.__json_spec__ = schema.Object( size=schema.Integer( minimum=1, maximum=10000, title='Longest dimension final size (px).' ), required=['size'], additionalProperties=False ) registry.register_operation(PillowImage, 'abs_scale', abs_scale) registry.register_operation(WandImage, 'abs_scale', abs_scale)
[docs]def scale(image, scale: float): """Scale image. :param image: :param scale: new scale scale """ x, y = image.get_size() new_x, new_y = round(x * scale), round(y * scale) image = image.resize((new_x, new_y)) return image
scale.__json_spec__ = schema.Object( scale=schema.Number( exclusiveMinimum=0, maximum=10, title='Relative scale value.' ), required=['ratio'], additionalProperties=False ) registry.register_operation(PillowImage, 'scale', scale) registry.register_operation(WandImage, 'scale', scale)
[docs]def aspect_crop(image, ratio: Tuple[int, int], rel_shift=(0.5, 0.5)): """Crop image to a new aspect ratio. :param image: :param ratio: an integer ratio of new X:Y :param rel_shift: default - centered, i.e. 0.5, 0.5 """ new_aspect = Fraction(int(ratio[0]), int(ratio[1])) x, y = image.get_size() x, y = int(x), int(y) old_aspect = Fraction(x, y) if old_aspect < new_aspect: x1 = x y1 = y * old_aspect / new_aspect else: x1 = x * new_aspect / old_aspect y1 = y dx, dy = x - x1, y - y1 shift_x, shift_y = rel_shift x0, y0 = int(dx * float(shift_x)), int(dy * float(shift_y)) x1, y1 = x0 + x1, y0 + y1 image = image.crop((x0, y0, x1, y1)) return image
aspect_crop.__json_spec__ = schema.Object( ratio=schema.Array( items=[ schema.Integer( minimum=1, maximum=100, title='Width relative value (numerator)' ), schema.Integer( minimum=1, maximum=100, title='Height relative value (denominator)' ) ], title='New width/height integer ratio.', additionalItems=False, minItems=2, maxItems=2 ), rel_shift=schema.Array( items=[ schema.Number( minimum=0, maximum=1, title='Width relative' ), schema.Integer( minimum=0, maximum=1, title='Height relative' ) ], title='Crop center position relative to the center of the original image.', additionalItems=False, minItems=2, maxItems=2 ), required=['ratio'], additionalProperties=False ) registry.register_operation(PillowImage, 'aspect_crop', aspect_crop) registry.register_operation(WandImage, 'aspect_crop', aspect_crop)
[docs]def pillow_blur(image): from PIL import ImageFilter blurred_image = image.image.filter(ImageFilter.BLUR) return PillowImage(blurred_image)
[docs]def wand_blur(image): blurred_image = image.image.clone() blurred_image.gaussian_blur() return WandImage(blurred_image)
pillow_blur.__json_spec__ = wand_blur.__json_spec__ = None registry.register_operation(PillowImage, 'blur', pillow_blur) registry.register_operation(WandImage, 'blur', wand_blur) pillow_resize = registry.get_operation(PillowImage, 'resize') wand_resize = registry.get_operation(WandImage, 'resize') pillow_resize.__json_spec__ = wand_resize.__json_spec__ = schema.Object( size=schema.Array( items=[ schema.Integer( minimum=1, maximum=10000, title='Width (px)' ), schema.Integer( minimum=1, maximum=10000, title='Height (px)' ) ], title='New dimensions.', additionalItems=False, minItems=2, maxItems=2 ), required=['size'], additionalProperties=False ) pillow_crop = registry.get_operation(PillowImage, 'crop') wand_crop = registry.get_operation(WandImage, 'crop') wand_crop.__json_spec__ = wand_crop.__json_spec__ = schema.Object( rect=schema.Array( items=[ schema.Integer( minimum=0, maximum=10000, title='Left' ), schema.Integer( minimum=0, maximum=10000, title='Top' ), schema.Integer( minimum=0, maximum=10000, title='Right' ), schema.Integer( minimum=0, maximum=10000, title='Bottom' ) ], title='New cropped image rectangle coordinates.', additionalItems=False, minItems=4, maxItems=4 ), required=['rect'], additionalProperties=False ) pillow_rotate = registry.get_operation(PillowImage, 'rotate') wand_rotate = registry.get_operation(WandImage, 'rotate') pillow_rotate.__json_spec__ = wand_rotate.__json_spec__ = schema.Object( angle=schema.Integer( minimum=90, maximum=360, multipleOf=90, title='Left' ), required=['angle'], additionalProperties=False )