Search code examples
iosswiftoption-typecgimage

CGImageCreateWithImageInRect() returning nil


I'm trying to crop an image into a square, but once I actually try to do the crop by using CGImageCreateWithImageInRect(), this line crashes. I set breakpoints and made sure that the arguments passed into this function are not nil.

I'm fairly new to programming and Swift, but have searched around and haven't found any solution to my problem.

The failure reason:

fatal error: unexpectedly found nil while unwrapping an Optional value

func cropImageToSquare(imageData: NSData) -> NSData {

    let image = UIImage(data: imageData)
    let contextImage : UIImage = UIImage(CGImage: image!.CGImage!)
    let contextSize: CGSize = contextImage.size

    let imageDimension: CGFloat = contextSize.height
    let posY : CGFloat = (contextSize.height + (contextSize.width - contextSize.height)/2)
    let rect: CGRect = CGRectMake(0, posY, imageDimension, imageDimension)

    // error on line below: fatal error: unexpectedly found nil while unwrapping an Optional value
    let imageRef: CGImageRef = CGImageCreateWithImageInRect(contextImage.CGImage, rect)!
    let croppedImage : UIImage = UIImage(CGImage: imageRef, scale: 1.0, orientation: image!.imageOrientation)

    let croppedImageData = UIImageJPEGRepresentation(croppedImage, 1.0)

    return croppedImageData!

}

Solution

  • Your code uses a lot of force-unwrapping with !s. I would recommend avoiding this — the compiler is trying to help you write code that won't crash. Use optional chaining with ?, and if let / guard let, instead.

    The ! on that particular line is hiding an issue where CGImageCreateWithImageInRect might return nil. The documentation explains that this happens when the rect is not correctly inside the image bounds. Your code works for images in portrait orientation, but not landscape.

    Furthermore, there's a convenient function provided by AVFoundation which can automatically find the right rectangle for you to use, called AVMakeRectWithAspectRatioInsideRect. No need to do the calculations manually :-)

    Here's what I would recommend:

    import AVFoundation
    
    extension UIImage
    {
        func croppedToSquare() -> UIImage
        {
            guard let cgImage = self.CGImage else { return self }
    
            // Note: self.size depends on self.imageOrientation, so we use CGImageGetWidth/Height here.
            let boundingRect = CGRect(
                x: 0, y: 0,
                width: CGImageGetWidth(cgImage),
                height: CGImageGetHeight(cgImage))
    
            // Crop to square (1:1 aspect ratio) and round the resulting rectangle to integer coordinates.
            var cropRect = AVMakeRectWithAspectRatioInsideRect(CGSize(width: 1, height: 1), boundingRect)
            cropRect.origin.x = ceil(cropRect.origin.x)
            cropRect.origin.y = ceil(cropRect.origin.y)
            cropRect.size.width = floor(cropRect.size.width)
            cropRect.size.height = floor(cropRect.size.height)
    
            guard let croppedImage = CGImageCreateWithImageInRect(cgImage, cropRect) else {
                assertionFailure("cropRect \(cropRect) was not inside \(boundingRect)")
                return self
            }
    
            return UIImage(CGImage: croppedImage, scale: self.scale, orientation: self.imageOrientation)
        }
    }
    
    // then:
    let croppedImage = myUIImage.croppedToSquare()