Strange CoreImage cropping issues encounter here

Viewed 2067

I have a strange issue where after I cropped a photo from my photo library, it cannot be displayed from the App. It gives me this error after I run this code:

        self.correctedImageView.image = UIImage(ciImage: correctedImage)  

[api] -[CIContext(CIRenderDestination) _startTaskToRender:toDestination:forPrepareRender:error:] The image extent and destination extent do not intersect.

Here is the code I used to crop and display. (inputImage is CIImage)

    let imageSize = inputImage.extent.size
    let correctedImage = inputImage
        .cropped(to: textObvBox.boundingBox.scaled(to: imageSize) )
    DispatchQueue.main.async {
        self.correctedImageView.image = UIImage(ciImage: correctedImage)  
    }

More Info: Debug print the extent of the inputImage and correctedImage

Printing description of self.inputImage: CIImage: 0x1c42047a0 extent [0 0 3024 4032]>

crop [430 3955 31 32] extent=[430 3955 31 32] affine [0 -1 1 0 0 4032] extent=[0 0 3024 4032] opaque affine [1 0 0 -1 0 3024] extent=[0 0 4032 3024] opaque colormatch "sRGB IEC61966-2.1"_to_workingspace extent=[0 0 4032 3024] opaque IOSurface 0x1c4204790(501) seed:1 YCC420f 601 alpha_one extent=[0 0 4032 3024] opaque

Funny thing is that when I put a breakpoint, using Xcode, i was able to preview the cropped image properly. I'm not sure what this extent thing is for CIImage, but UIMageView doesn't like it when I assign the cropped image to it. Any idea what this extent thing does?

3 Answers

I ran into the same problem you describe. Due to some weird behavior in UIKit / CoreImage, I needed to convert the CIImage to a CGImage first. I noticed it only happened when I applied some filters to the CIImage, described below.

let image = /* my CIImage */
let goodImage = UIImage(ciImage: image)
uiImageView.image = goodImage // works great!


let image = /* my CIImage after applying 5-10 filters */
let badImage = UIImage(ciImage: image)
uiImageView.image = badImage // empty view!

This is how I solved it.

let ciContext = CIContext()
let cgImage = self.ciContext.createCGImage(image, from: image.extent)
uiImageView.image = UIImage(cgImage: cgImage) // works great!!!

As other commenters have stated beware creating a CIContext too often; its an expensive operation.

Extent in CIImage can be a bit of a pain, especially when working with cropping and translation filters. Effectively it allows the actual position of the new image to relate directly to the part of the old image it was taken from. If you crop the center out of an image, you'll find the extent's origin is non-zero and you'll get an issue like this. I believe the feature is intended for things such as making an art application that supports layers.

As noted above, you CAN convert the CIImage into a CGImage and from there to a UIImage, but this is slow and wasteful. It works because CGImage doesn't preserve extent, while UIImage can. However, as noted, it requires creating a CIContext which has a lot of overhead.

There is, fortunately, a better way. Simply create a CIAffineTransformFilter and populate it with an affine transform equal to the negative of the extent's origin, thus:

let transformFilter = CIFilter(name: "CIAffineTransform")!
let translate = CGAffineTransform(translationX: -image.extent.minX, y: -image.extent.minY)
let value = NSValue(cgAffineTransform: translate)
transformFilter.setValue(value, forKey: kCIInputTransformKey)
transformFilter.setValue(image, forKey: kCIInputImageKey)
let newImage = transformFilter.outputImage

Now, newImage should be identical to image but with an extent origin of zero. You can then pass this directly to UIView(ciimage:). I timed this and found it to be immensely faster than creating a CIContext and making a CGImage. For the sake of interest:

CGImage Method: 0.135 seconds.

CIFilter Method: 0.00002 seconds.

(Running on iPhone XS Max)

You can use advanced UIGraphicsImageRenderer to draw your image in iOS 10.X or later.

let img:CIImage = /* my ciimage */

let renderer = UIGraphicsImageRenderer(size: img.extent.size)

uiImageView.image = renderer.image { (context) in
    UIImage(ciImage: img).draw(in: .init(origin: .zero, size: img.extent.size))
}

Related