From c974975590c91bef09a85c1551348e0ebba725fe Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 15 Sep 2014 17:33:34 +0100 Subject: [PATCH] Improved method for calculating the crop box on images with focal points --- wagtail/wagtailimages/backends/base.py | 91 +++++++++++++++++++------- 1 file changed, 66 insertions(+), 25 deletions(-) diff --git a/wagtail/wagtailimages/backends/base.py b/wagtail/wagtailimages/backends/base.py index d0d5ba852..0db17197b 100644 --- a/wagtail/wagtailimages/backends/base.py +++ b/wagtail/wagtailimages/backends/base.py @@ -133,7 +133,7 @@ class BaseImageBackend(object): if original_height <= target_height: return image - scale = target_height / original_height + scale = target_height / original_hight final_size = (int(original_width * scale), target_height) @@ -157,34 +157,75 @@ class BaseImageBackend(object): else: crop_closeness = 0 - if focal_point is not None and crop_closeness > 0: - # Get focal point as a rect - left = focal_point.x - focal_point.width / 2 - top = focal_point.y - focal_point.height / 2 - right = focal_point.x + focal_point.width / 2 - bottom = focal_point.y + focal_point.height / 2 + # Get image width and height + (im_width, im_height) = image.size - # Interpolate focal point rect with the images original size by crop closeness - # When crop_closeness = 0: new FP = image size - # When crop_closeness = 1: new FP = original FP size - (original_width, original_height) = image.size - left = left * crop_closeness - top = top * crop_closeness - right = (right - original_width) * crop_closeness + original_width - bottom = (bottom - original_height) * crop_closeness + original_height + # Get filter width and height + fl_width = size[0] + fl_height = size[1] - # Create new focal point - new_x = (left + right) / 2 - new_y = (top + bottom) / 2 - new_width = right - left - new_height = bottom - top - new_focal_point = FocalPoint(new_x, new_y, width=new_width, height=new_height) + # Get crop aspect ratio + crop_aspect_ratio = fl_width / fl_height - # Crop - return self.crop_to_point(image, size, new_focal_point) + # Get crop max + crop_max_scale = min(im_width, im_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: + fp_width = focal_point.width + fp_height = focal_point.height + + # Get crop min + crop_min_scale = max(fp_width, fp_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 - (fl_width - crop_min_width) / (crop_max_width - crop_min_width), + 1 - (fl_height - crop_min_height) / (crop_max_height - crop_min_height) + ) + + # Apply max crop closeness + crop_closeness = min(crop_closeness, max_crop_closeness) + + # 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 = focal_point.x + fp_y = focal_point.y else: - resized_image = self.resize_to_min(image, size) - return self.crop_to_centre(resized_image, size) + # Fall back to positioning in the centre + fp_x = im_width / 2 + fp_y = im_height / 2 + + fp_u = fp_x / im_width + fp_v = fp_y / im_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 + + # Crop! + return self.resize_to_min(self.crop(image, crop.CropBox(left, top, right, bottom)), size) + def no_operation(self, image, param, focal_point=None): """Return the image unchanged"""