Search code examples
iosobjective-ccgaffinetransformseparating-axis-theorem

Determine if crop rect is entirely contained within rotated UIView


Premise: I'm building a cropping tool that handles two-finger arbitrary rotation of an image as well as arbitrary cropping.

Sometimes the image ends up rotated in a way that empty space is inserted to fill a gap between the rotated image and the crop rect (see the examples below).

I need to ensure that the image view, when rotated, fits entirely into the cropping rectangle. If it doesn't, I then need to re-transform the image (zoom it) so that it fits into the crop bounds.

Using this answer, I've implemented the ability to check whether a rotated UIImageView intersects with the cropping CGRect, but unfortunately that doesn't tell me if the crop rect is entirely contained in the rotated imageview. Hoping that I can make some easy modifications to this answer?

A visual example of OK:

enter image description here

and not OK, that which I need to detect and deal with:

enter image description here

Update: not working method

- (BOOL)rotatedView:(UIView*)rotatedView containsViewCompletely:(UIView*)containedView {

    CGRect rotatedBounds = rotatedView.bounds;
    CGPoint polyContainedView[4];

    polyContainedView[0] = [containedView convertPoint:rotatedBounds.origin toView:rotatedView];
    polyContainedView[1] = [containedView convertPoint:CGPointMake(rotatedBounds.origin.x + rotatedBounds.size.width, rotatedBounds.origin.y) toView:rotatedView];
    polyContainedView[2] = [containedView convertPoint:CGPointMake(rotatedBounds.origin.x + rotatedBounds.size.width, rotatedBounds.origin.y + rotatedBounds.size.height) toView:rotatedView];
    polyContainedView[3] = [containedView convertPoint:CGPointMake(rotatedBounds.origin.x, rotatedBounds.origin.y + rotatedBounds.size.height) toView:rotatedView];

    if (CGRectContainsPoint(rotatedView.bounds, polyContainedView[0]) &&
        CGRectContainsPoint(rotatedView.bounds, polyContainedView[1]) &&
        CGRectContainsPoint(rotatedView.bounds, polyContainedView[2]) &&
        CGRectContainsPoint(rotatedView.bounds, polyContainedView[3]))
        return YES;
    else
        return NO;
}

Solution

  • That should be easier than checking for intersection (as in the referenced thread).

    The (rotated) image view is a convex quadrilateral. Therefore it suffices to check that all 4 corner points of the crop rectangle are within the rotated image view.

    • Use [cropView convertPoint:point toView:imageView] to convert the corner points of the crop rectangle to the coordinate system of the (rotated) image view.
    • Use CGRectContainsPoint() to check that the 4 converted corner points are within the bounds rectangle of the image view.

    Sample code:

    - (BOOL)rotatedView:(UIView *)rotatedView containsCompletely:(UIView *)cropView {
    
        CGPoint cropRotated[4];
        CGRect rotatedBounds = rotatedView.bounds;
        CGRect cropBounds = cropView.bounds;
    
        // Convert corner points of cropView to the coordinate system of rotatedView:
        cropRotated[0] = [cropView convertPoint:cropBounds.origin toView:rotatedView];
        cropRotated[1] = [cropView convertPoint:CGPointMake(cropBounds.origin.x + cropBounds.size.width, cropBounds.origin.y) toView:rotatedView];
        cropRotated[2] = [cropView convertPoint:CGPointMake(cropBounds.origin.x + cropBounds.size.width, cropBounds.origin.y + cropBounds.size.height) toView:rotatedView];
        cropRotated[3] = [cropView convertPoint:CGPointMake(cropBounds.origin.x, cropBounds.origin.y + cropBounds.size.height) toView:rotatedView];
    
        // Check if all converted points are within the bounds of rotatedView:
        return (CGRectContainsPoint(rotatedBounds, cropRotated[0]) &&
                CGRectContainsPoint(rotatedBounds, cropRotated[1]) &&
                CGRectContainsPoint(rotatedBounds, cropRotated[2]) &&
                CGRectContainsPoint(rotatedBounds, cropRotated[3]));
    }