Search code examples
iosswiftuiimagecore-graphicsavfoundation

Turning retina images into video: "CGContextScaleCTM: invalid context 0x0. This is a serious error."


The goal is to turn an array of retina images into a single video.

The two functions below are responsible for writing an UIImage into a AVAssetWriterInputPixelBufferAdaptor. They work, and the code produces a seemingly valid video out of the images.

However, Xcode complains with this error:

CGContextScaleCTM: invalid context 0x0. This is a serious error. This application, or a library it uses, is using an invalid context and is thereby contributing to an overall degradation of system stability and reliability. This notice is a courtesy: please fix this problem. It will become a fatal error in an upcoming update.

Presumably, it's complaining about the line CGContextScaleCTM(UIGraphicsGetCurrentContext(), scale, scale) in fillPixelBufferFromImage. Removing CGContextScaleCTM(UIGraphicsGetCurrentContext(), scale, scale) causes problems, however. The retina images wind up rendering at half the size (e.g., 320x568 instead of 640x1136) inside the video.

1) Is this an error to worry about? Other SO posts suggest not.

2) Even if benign, how can you make this error go away? Or what's the right way to render retina images into a AVAssetWriterInputPixelBufferAdaptor?

func appendPixelBufferForImageAtURL(image: UIImage, pixelBufferAdaptor: AVAssetWriterInputPixelBufferAdaptor, presentationTime: CMTime) -> Bool {
    var appendSucceeded = false

    autoreleasepool {
        if  let pixelBufferPool = pixelBufferAdaptor.pixelBufferPool {
            let pixelBufferPointer = UnsafeMutablePointer<CVPixelBuffer?>.alloc(1)
            let status: CVReturn = CVPixelBufferPoolCreatePixelBuffer(
                kCFAllocatorDefault,
                pixelBufferPool,
                pixelBufferPointer
            )

            if let pixelBuffer = pixelBufferPointer.memory where status == 0 {
                fillPixelBufferFromImage(image, pixelBuffer: pixelBuffer)
                appendSucceeded = pixelBufferAdaptor.appendPixelBuffer(pixelBuffer, withPresentationTime: presentationTime)
                pixelBufferPointer.destroy()
            } else {
                NSLog("Error: Failed to allocate pixel buffer from pool")
            }

            pixelBufferPointer.dealloc(1)
        }
    }

    return appendSucceeded
}


func fillPixelBufferFromImage(image: UIImage, pixelBuffer: CVPixelBufferRef) {
    /// Lock base address
    CVPixelBufferLockBaseAddress(pixelBuffer, 0)

    // Set properties for context
    let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer)
    let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
    let scale = image.scale
    let scaledWidth = image.size.width * scale
    let scaledHeight = image.size.height * scale

    // Create CGBitmapContext
    let context = CGBitmapContextCreate(
        pixelData,
        Int(scaledWidth),
        Int(scaledHeight),
        8,
        CVPixelBufferGetBytesPerRow(pixelBuffer),
        rgbColorSpace,
        CGImageAlphaInfo.PremultipliedFirst.rawValue
    )

    // Set scale for context
    CGContextScaleCTM(UIGraphicsGetCurrentContext(), scale, scale)

    // Draw image into context
    CGContextDrawImage(context, CGRectMake(0, 0, scaledWidth, scaledHeight), image.CGImage)

    /// Unlock base address
    CVPixelBufferUnlockBaseAddress(pixelBuffer, 0)
}

Solution

  • Yes, this is an error you should worry about. The error message saying "This is a serious error." and "please fix this problem" is not a joke.

    This is incorrect:

    CGContextScaleCTM(UIGraphicsGetCurrentContext(), scale, scale)
    

    because none of these are the case:

    1. You aren't in a UIView's -drawRect: method
    2. You didn't call UIGraphicsBeginImageContext to set up a context
    3. You didn't manually call UIGraphicsPushContext

    Therefore, as far as UIKit knows, there is no "current" context, so UIGraphicsGetCurrentContext() returns nil.

    You created the bitmap context yourself, so you should apply the scale to that context:

    CGContextScaleCTM(context, scale, scale)