How to crop a UIImageView to a new UIImage in 'aspect fit' mode?

16,837

Solution 1

Based on my other answer to a similar question I have written this method for a UIImageView category:

-(CGRect) cropRectForFrame:(CGRect)frame
{
    NSAssert(self.contentMode == UIViewContentModeScaleAspectFit, @"content mode must be aspect fit");

    CGFloat widthScale = self.bounds.size.width / self.image.size.width;
    CGFloat heightScale = self.bounds.size.height / self.image.size.height;

    float x, y, w, h, offset;
    if (widthScale<heightScale) {
        offset = (self.bounds.size.height - (self.image.size.height*widthScale))/2;
        x = frame.origin.x / widthScale;
        y = (frame.origin.y-offset) / widthScale;
        w = frame.size.width / widthScale;
        h = frame.size.height / widthScale;
    } else {
        offset = (self.bounds.size.width - (self.image.size.width*heightScale))/2;
        x = (frame.origin.x-offset) / heightScale;
        y = frame.origin.y / heightScale;
        w = frame.size.width / heightScale;
        h = frame.size.height / heightScale;
    }
    return CGRectMake(x, y, w, h);
}

You can pass in the frame of your green rect (assuming it is a subview of the image view) and get the crop rect back for cropping the UIImage. Note that it only works for 'aspect fit' mode! I haven't tested it though. So, tell me if it works!

Solution 2

I know the answer has been but I had some problems getting above right so, I wont to post my solution too.

The problem is the image is scaled with a aspect fit, but we know the scale factor for both the width and the height. Then we can calculate the right cropping rectangle pretty easy:

-(UIImage*)crop:(CGRect)frame
{
    // Find the scalefactors  UIImageView's widht and height / UIImage width and height
    CGFloat widthScale = self.bounds.size.width / self.image.size.width;
    CGFloat heightScale = self.bounds.size.height / self.image.size.height;

    // Calculate the right crop rectangle 
    frame.origin.x = frame.origin.x * (1 / widthScale);
    frame.origin.y = frame.origin.y * (1 / heightScale);
    frame.size.width = frame.size.width * (1 / widthScale);
    frame.size.height = frame.size.height * (1 / heightScale);

    // Create a new UIImage
    CGImageRef imageRef = CGImageCreateWithImageInRect(self.image.CGImage, frame);
    UIImage *croppedImage = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);

    return croppedImage;
}

Solution 3

I think you don't understand what you are doing. You can't "crop" a UIImageView - only UIImages. So your croppedImage method should be in a UIImage category and not a UIImageView category. The bounds rect you pass to the crop method must be in relation to the image size, not to the imageview bounds. It is irrelevant what the contentMode of the image view is - the cropping of the image will always work the same. First get the image from the image view. Then create a new cropped image from it and finally set the cropped image for the image view. This can be done in one line of code:

imageView.image = [imageView.image croppedImage:rect];

Since your contentMode is 'aspect fit' the new image will automatically stretch to fit in the view frame. You can also try to change the view frame based on the image.size to avoid pixelation effects.

Share:
16,837
pj4533
Author by

pj4533

I code for iOS.

Updated on June 17, 2022

Comments

  • pj4533
    pj4533 about 2 years

    enter image description here

    I would like to create a new UIImage by cropping the image inside a UIImageView. For example in the above pic, I want a new UIImage of the area outlined in green. I found code to do this, normally it looks something like this (as a UIImage category):

    - (UIImage *)croppedImage:(CGRect)bounds {
        CGImageRef imageRef = CGImageCreateWithImageInRect([self CGImage], bounds);
        UIImage *croppedImage = [UIImage imageWithCGImage:imageRef];
        CGImageRelease(imageRef);
        return croppedImage;
    }
    

    Makes perfect sense. HOWEVER, my UIImageView is in contentMode 'aspect fit'. Seems like that complicates the CGRect calculations for CGImageCreateWithImageInRect and I haven't been able to figure out the right solution.

    I figure I need to apply the same transformations to the green rect as were done to the image?

    ALSO: I found this other question here (How to know the image size after applying aspect fit for the image in an UIImageView), which seems to show a brute force way of getting the size, but I am still not sure how that fits into a cropping situation.

    Any ideas?

    EDIT: In my case, I literally have a square as shown above drawn in an overlay view over the UIImageView. So while I realize I am cropping the UIImage (inside the UIImageView), I need to somehow change the green CGRect such that it 'matches' the transformation done when 'aspect fit' is applied. For example, if I leave my UIImageView in 'top left' mode, then everything works fine, because there is a 1:1 mapping between the underlying UIImage and the UIImageView. The problem in 'top left' mode is that the entire image doesn't always display, because it might be bigger than my UIImageView.