From 9e7ad03d862725ce0aeccaebf002c9c80e2d7604 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 3 Nov 2014 12:25:45 +0000 Subject: [PATCH] Added new image operations --- wagtail/wagtailimages/image_operations.py | 225 ++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 wagtail/wagtailimages/image_operations.py diff --git a/wagtail/wagtailimages/image_operations.py b/wagtail/wagtailimages/image_operations.py new file mode 100644 index 0000000000..2681e63fc7 --- /dev/null +++ b/wagtail/wagtailimages/image_operations.py @@ -0,0 +1,225 @@ +from __future__ import division + + +class DoNothingOperation(object): + def __init__(self, method): + pass + + def run(self, willow, image): + pass + + +class FillOperation(object): + def __init__(self, method, size, *extra): + # Get width and height + width_str, height_str = size.split('x') + self.width = int(width_str) + self.height = int(height_str) + + # Crop closeness + self.crop_closeness = 0 + + for extra_part in extra: + if extra_part.startswith('c'): + self.crop_closeness = int(extra_part[1:]) + else: + raise ValueError("Unrecognised filter spec part: %s" % extra_part) + + # Divide it by 100 (as it's a percentage) + self.crop_closeness /= 100 + + # Clamp it + if self.crop_closeness > 1: + self.crop_closeness = 1 + + def run(self, willow, image): + image_width, image_height = willow.get_size() + focal_point = image.get_focal_point() + + # Get crop aspect ratio + crop_aspect_ratio = self.width / self.height + + # Get crop max + crop_max_scale = min(image_width, image_height * crop_aspect_ratio) + crop_max_width = crop_max_scale + crop_max_height = crop_max_scale / crop_aspect_ratio + + # Initialise crop width and height to max + crop_width = crop_max_width + crop_height = crop_max_height + + # Use crop closeness to zoom in + if focal_point is not None: + # Get crop min + crop_min_scale = max(focal_point.width, focal_point.height * crop_aspect_ratio) + crop_min_width = crop_min_scale + crop_min_height = crop_min_scale / crop_aspect_ratio + + # Sometimes, the focal point may be bigger than the image... + if not crop_min_scale > crop_max_scale: + # Calculate max crop closeness to prevent upscaling + max_crop_closeness = max( + 1 - (self.width - crop_min_width) / (crop_max_width - crop_min_width), + 1 - (self.height - crop_min_height) / (crop_max_height - crop_min_height) + ) + + # Apply max crop closeness + crop_closeness = min(self.crop_closeness, max_crop_closeness) + + if 1 >= crop_closeness >= 0: + # Get crop width and height + crop_width = crop_max_width + (crop_min_width - crop_max_width) * crop_closeness + crop_height = crop_max_height + (crop_min_height - crop_max_height) * crop_closeness + + # Find focal point UV + if focal_point is not None: + fp_x, fp_y = focal_point.centroid + else: + # Fall back to positioning in the centre + fp_x = image_width / 2 + fp_y = image_height / 2 + + fp_u = fp_x / image_width + fp_v = fp_y / image_height + + # Position crop box based on focal point UV + crop_x = fp_x - (fp_u - 0.5) * crop_width + crop_y = fp_y - (fp_v - 0.5) * crop_height + + # Convert crop box into rect + left = crop_x - crop_width / 2 + top = crop_y - crop_height / 2 + right = crop_x + crop_width / 2 + bottom = crop_y + crop_height / 2 + + # Make sure the entire focal point is in the crop box + if focal_point is not None: + if left > focal_point.left: + right -= left - focal_point.left + left = focal_point.left + + if top > focal_point.top: + bottom -= top - focal_point.top + top = focal_point.top + + if right < focal_point.right: + left += focal_point.right - right + right = focal_point.right + + if bottom < focal_point.bottom: + top += focal_point.bottom - bottom + bottom = focal_point.bottom + + # Don't allow the crop box to go over the image boundary + if left < 0: + right -= left + left = 0 + + if top < 0: + bottom -= top + top = 0 + + if right > image_width: + left -= right - image_width + right = image_width + + if bottom > image_height: + top -= bottom - image_height + bottom = image_height + + # Crop! + willow.crop(int(left), int(top), int(right), int(bottom)) + + # Resize the final image + aftercrop_width, aftercrop_height = willow.get_size() + horz_scale = self.width / aftercrop_width + vert_scale = self.height / aftercrop_height + + if aftercrop_width <= self.width or aftercrop_height <= self.height: + return + + if horz_scale > vert_scale: + width = self.width + height = int(aftercrop_height * horz_scale) + else: + width = int(aftercrop_width * vert_scale) + height = self.height + + willow.resize(width, height) + + +class MinMaxOperation(object): + def __init__(self, method, size): + self.method = method + + # Get width and height + width_str, height_str = size.split('x') + self.width = int(width_str) + self.height = int(height_str) + + def run(self, willow, image): + image_width, image_height = willow.get_size() + + horz_scale = self.width / image_width + vert_scale = self.height / image_height + + if self.method == 'min': + if image_width <= self.width or image_height <= self.height: + return + + if horz_scale > vert_scale: + width = self.width + height = int(image_height * horz_scale) + else: + width = int(image_width * vert_scale) + height = self.height + + elif self.method == 'max': + if image_width <= self.width and image_height <= self.height: + return + + if horz_scale < vert_scale: + width = self.width + height = int(image_height * horz_scale) + else: + width = int(image_width * vert_scale) + height = self.height + + else: + # Unknown method + return + + willow.resize(width, height) + + +class WidthHeightOperation(object): + def __init__(self, method, size): + self.method = method + self.size = int(size) + + def run(self, willow, image): + image_width, image_height = willow.get_size() + + if self.method == 'width': + if image_width <= self.size: + return + + scale = self.size / image_width + + width = self.size + height = int(image_height * scale) + + elif self.method == 'height': + if image_height <= self.size: + return + + scale = self.size / image_height + + width = int(image_width * scale) + height = self.size + + else: + # Unknown method + return + + willow.resize(width, height)