Search code examples
iosuiscrollviewuiimageios7crop

Crop a Portion of UIImage from Larger UIImage, and include non-image parts


I think I may have an odd request, however hopefully someone can help. I am using the well known UIScrollView + UIImageView to zoom into and out of an image, as well as pan. This works fine and dandy, but the current project we have needs to be able to crop the image, but also include the black bars on the sides if the image is smaller than the crop rectangle. See the images below.

We wish to capture everything inside of the blue box, including the white (which will be black, since opaque is set to YES).

This works great for images that are completely zoomed out (The white is just the UIImageView's extra space). enter image description here

However the problem arises when we try to zoom into the image, and capture only that portion, plus the empty space. enter image description here

This results in the following image enter image description here

The problem we are seeing is we need to be able to create an image that is exactly what is in the Crop Rect, regardless if there is part of the image there or not. The other problem is we wish to have the ability to dynamically change the output resolution. The aspect ratio is 16:9, and for this example kMaxWidth = 1136 and kMaxHeight = 639, however in the future we may want to request a larger or smaller 16:9 resolution.

Below is the function I have so far:

- (UIImage *)createCroppedImageFromImage:(UIImage *)image {
    CGSize newRect = CGSizeMake(kMaxWidth, kMaxHeight);
    UIGraphicsBeginImageContextWithOptions(newRect, YES, 0.0);

    // 0 is the edge of the screen, to help with zooming
    CGFloat xDisplacement = ((abs(0 - imageView.frame.origin.x) * kMaxWidth) / (self.cropSize.width / self.scrollView.zoomScale) / self.scrollView.zoomScale);

    CGFloat yDisplacement = ((abs(self.cropImageView.frame.origin.y - imageView.frame.origin.y) * kMaxHeight) / (self.cropSize.height / self.scrollView.zoomScale) / self.scrollView.zoomScale);

    CGFloat newImageWidth = (self.image.size.width * kMaxWidth) / (self.cropSize.width / self.scrollView.zoomScale);
    CGFloat newImageHeight = (self.image.size.height * kMaxHeight) / (self.cropSize.height / self.scrollView.zoomScale);    
   [image drawInRect:CGRectMake(xDisplacement, 0, newImageWidth, newImageHeight)];
    UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return croppedImage;
}

Any help would be greatly appreciated.


Solution

  • I ended up just taking a screenshot, and cropping that. It seems to work well enough.

    - (UIImage *)cropImage {
        CGRect cropRect = self.cropOverlay.cropRect;
        UIGraphicsBeginImageContext(self.view.frame.size);
    
        [self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
        UIImage *fullScreenshot = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    
        CGImageRef croppedImage = CGImageCreateWithImageInRect(fullScreenshot.CGImage, cropRect);
        UIImage *crop = [[UIImage imageWithCGImage:croppedImage] resizedImage:self.outputSize interpolationQuality:kCGInterpolationHigh];
        CGImageRelease(croppedImage);
        return crop;
    }
    

    If using iOS 7, you would use drawViewHierarchyInRect:afterScreenUpdates:, instead of renderInContext: