swiftoption-typecapture-output

"unexpectedly found nil while unwrapping an Optional value" inside captureOutput function


I have a function to rotate the hue on UIImages that work from other functions within the same class, but within the captureOutput function (used with AVCaptureVideoDataOutput), the same image object crashes my app with the "unexpectedly found nil while unwrapping an Optional value". There doesn't look to be a difference in how I'm calling it, so maybe it has to do with the captureOutput function and what it allows?

Outside of viewController class declaration:

var image:UIImage? = nil
var imageSource:UIImage? = nil
var imageView:UIImageView? = nil;
var hueDeg:CGFloat = 0.00

functions:

func rotateHue(with source: UIImage, rotatedByHue deltaHueRadians: CGFloat) -> UIImage {
    // Create a Core Image version of the image.
    let sourceCore = CIImage(cgImage: (source.cgImage)!) // crashes here
    // Apply a CIHueAdjust filter
    let hueAdjust = CIFilter(name: "CIHueAdjust")
    hueAdjust?.setDefaults()
    hueAdjust?.setValue(sourceCore, forKey: "inputImage")
    hueAdjust?.setValue(deltaHueRadians, forKey: "inputAngle")
    let resultCore = hueAdjust?.value(forKey: "outputImage") as? CIImage
    // Convert the filter output back into a UIImage.
    let context = CIContext(options: nil)
    let resultRef = context.createCGImage(resultCore!, from: (resultCore?.extent)!)
    let result = UIImage(cgImage: resultRef!)
    //CGImageRelease(resultRef)
    return result
}

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection){
    let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
    let cameraImage = CIImage(cvPixelBuffer: pixelBuffer!)

    image = UIImage(ciImage: cameraImage)

    image = rotateHue(with:image!, rotatedByHue:hueDeg) 

    DispatchQueue.main.async(){

        imageView!.image = image
    }

}

Here's another function that I call rotateHue from, and it doesn't crash. It's a function that is called by a timer:

@objc func cycleHue(){
    hueDeg += 0.2;
    image = imageSource
    image = rotateHue(with:image!, rotatedByHue:hueDeg)
    imageView!.image = UIImage(cgImage: (image?.cgImage)!, scale: 1.0, orientation: UIImageOrientation.right)
}

Solution

  • The documentation for UIImage's cgImage property says this:

    If the UIImage object was initialized using a CIImage object, the value of the property is NULL.

    Since this value can be nil, you should not use the force-unwrap (!) operator, or you could get a crash, as you're seeing. Instead, we need to test for the condition that the value may be nil.

    Fortunately, the condition that makes cgImage be nil is that it was apparently initialized using a CIImage. Since it appears a CIImage is what you want anyway, then the proper way to go about what you're trying to do is to:

    1. call UIImage's ciImage property, and if you get a non-nil value, use that.
    2. If ciImage is nil, then fall back on calling cgImage and making a CIImage from it if it is non-nil.
    3. If cgImage also happens to be nil, then something weird happened; in this case, you can throw an error or otherwise bail out gracefully.