diff --git a/src/FocalPointEffectBase.php b/src/FocalPointEffectBase.php index 9bd9b15..c3ba5bf 100644 --- a/src/FocalPointEffectBase.php +++ b/src/FocalPointEffectBase.php @@ -160,10 +160,12 @@ abstract class FocalPointEffectBase extends ResizeImageEffect implements Contain } /** - * Calculate the crop anchor. + * Calculate the top left coordinates of crop rectangle. * * This is based on Crop's anchor function with additional logic to ensure - * that crop area doesn't fall outside of the original image. + * that crop area doesn't fall outside of the original image. Note that the + * image modules crop effect expects the top left coordinate of the crop + * rectangle. * * @param \Drupal\Core\Image\ImageInterface $image * Image object representing original image. @@ -177,23 +179,24 @@ abstract class FocalPointEffectBase extends ResizeImageEffect implements Contain $original_anchor = $crop->anchor(); $new_anchor = $original_anchor; + $image_size = [ + 'width' => $image->getWidth(), + 'height' => $image->getHeight(), + ]; + // Set the minimum number of pixels that must exist between the edge of the // image and the anchor point (in both the x and y directions). $crop_size = $crop->size(); $anchor_min_margin_x = (int)ceil($crop_size['width'] / 2); $anchor_min_margin_y = (int)ceil($crop_size['height'] / 2); - // Ensure that the crop area doesn't fall off the left or right sides of the - // image. - $left_bound = $anchor_min_margin_x; - $right_bound = $image->getWidth() - $anchor_min_margin_x; - $new_anchor['x'] = max(min($original_anchor['x'], $right_bound), $left_bound); - - // Ensure that the crop area doesn't fall off the top or bottom of the - // image. - $top_bound = $anchor_min_margin_y; - $bottom_bound = $image->getHeight() - $anchor_min_margin_y; - $new_anchor['y'] = max(min($original_anchor['y'], $bottom_bound), $top_bound); + // Ensure that the crop area doesn't fall off the bottom right of the image. + $new_anchor['x'] = ($original_anchor['x'] + $anchor_min_margin_x > $image_size['width']) ? $image_size['width'] - $crop_size['width'] : $original_anchor['x'] - $anchor_min_margin_x; + $new_anchor['y'] = ($original_anchor['y'] + $anchor_min_margin_y > $image_size['height']) ? $image_size['height'] - $crop_size['height'] : $original_anchor['y'] - $anchor_min_margin_y; + + // Ensure that the crop area doesn't fall off the top left of the image. + $new_anchor['x'] = max(0, $new_anchor['x']); + $new_anchor['y'] = max(0, $new_anchor['y']); return $new_anchor; } diff --git a/tests/src/Unit/Effects/FocalPointEffectsTest.php b/tests/src/Unit/Effects/FocalPointEffectsTest.php index fcc2a27..d0c43dd 100644 --- a/tests/src/Unit/Effects/FocalPointEffectsTest.php +++ b/tests/src/Unit/Effects/FocalPointEffectsTest.php @@ -63,7 +63,7 @@ class FocalPointEffectsTest extends UnitTestCase { * * @dataProvider calculateAnchorProvider */ - public function testCalculateAnchor($image_size, $crop_size, $focal_point_offset, $expected) { + public function testCalculateAnchor($image_size, $crop_size, $focal_point_anchor, $expected) { $logger = $this->prophesize(LoggerInterface::class); $crop_storage = $this->prophesize(CropStorageInterface::class); $immutable_config = $this->prophesize(ImmutableConfig::class); @@ -76,8 +76,8 @@ class FocalPointEffectsTest extends UnitTestCase { $crop = $this->prophesize(CropInterface::class); $crop->anchor()->willReturn([ - 'x' => $focal_point_offset[0], - 'y' => $focal_point_offset[1], + 'x' => $focal_point_anchor[0], + 'y' => $focal_point_anchor[1], ]); $crop->size()->willReturn([ 'width' => $crop_size[0], @@ -97,15 +97,16 @@ class FocalPointEffectsTest extends UnitTestCase { */ public function calculateAnchorProvider() { $data = []; - $data['crop_fits_within_image'] = [[1000, 100], [30, 20], [250, 50], ['x' => 250, 'y' => 50]]; - $data['crop_does_not_fall_off_image_top'] = [[1000, 100], [30, 40], [313, 10], ['x' => 313, 'y' => 20]]; - $data['crop_does_not_fall_off_image_bottom'] = [[1000, 100], [30, 40], [313, 90], ['x' => 313, 'y' => 80]]; - $data['crop_does_not_fall_off_image_left'] = [[1000, 100], [50, 50], [10, 50], ['x' => 25, 'y' => 50]]; - $data['crop_does_not_fall_off_image_right'] = [[1000, 100], [50, 50], [999, 50], ['x' => 975, 'y' => 50]]; - $data['crop_does_not_fall_off_image_top_left'] = [[1000, 100], [50, 50], [0, 0], ['x' => 25, 'y' => 25]]; - $data['crop_does_not_fall_off_image_top_right'] = [[1000, 100], [50, 50], [999, 5], ['x' => 975, 'y' => 25]]; - $data['crop_does_not_fall_off_image_bottom_left'] = [[1000, 100], [75, 75], [0, 99], ['x' => 38, 'y' => 62]]; - $data['crop_does_not_fall_off_image_bottom_right'] = [[1000, 100], [50, 50], [999, 99], ['x' => 975, 'y' => 75]]; + $data['crop_fits_within_image_even'] = [[1000, 100], [30, 20], [250, 50], ['x' => 235, 'y' => 40]]; + $data['crop_fits_within_image_odd'] = [[1000, 100], [35, 25], [313, 49], ['x' => 295, 'y' => 36]]; + $data['crop_does_not_fall_off_image_top'] = [[1000, 100], [30, 40], [313, 10], ['x' => 298, 'y' => 0]]; + $data['crop_does_not_fall_off_image_bottom'] = [[1000, 100], [50, 50], [900, 90], ['x' => 875, 'y' => 50]]; + $data['crop_does_not_fall_off_image_left'] = [[1000, 100], [50, 50], [10, 50], ['x' => 0, 'y' => 25]]; + $data['crop_does_not_fall_off_image_right'] = [[1000, 100], [50, 50], [975, 60], ['x' => 950, 'y' => 35]]; + $data['crop_does_not_fall_off_image_top_left'] = [[1000, 100], [50, 50], [0, 0], ['x' => 0, 'y' => 0]]; + $data['crop_does_not_fall_off_image_top_right'] = [[1000, 100], [50, 50], [1000, 5], ['x' => 950, 'y' => 0]]; + $data['crop_does_not_fall_off_image_bottom_left'] = [[1000, 100], [75, 75], [0, 100], ['x' => 0, 'y' => 25]]; + $data['crop_does_not_fall_off_image_bottom_right'] = [[1000, 100], [50, 50], [1000, 100], ['x' => 950, 'y' => 50]]; return $data; }