Search code examples
swiftnsdatacgimagecfdata

How to save CGImage to Data in Swift?


This code type checks and compiles but then crashes. How do I save a CGImage to Data so that I can read it in again later.

let cgi: CGImage? = ...        
var mData = Data()
let imageDest = CGImageDestinationCreateWithData(mData as! CFMutableData, 
                                                 kUTTypePNG, 1, nil)!
CGImageDestinationAddImage(imageDest, cgi!, nil)
CGImageDestinationFinalize(imageDest)

The last line crashes. Error in console is:

2018-01-17 19:25:43.656664-0500 HelloPencil[2799:3101092] -[_NSZeroData 
  appendBytes:length:]: unrecognized selector sent to instance 0x1c80029c0
2018-01-17 19:25:43.658420-0500 HelloPencil[2799:3101092] *** Terminating app 
  due to uncaught exception 'NSInvalidArgumentException', reason: 
  '-[_NSZeroData appendBytes:length:]: unrecognized selector 
  sent to instance 0x1c80029c0'

That cast from Data to CFMutableData was recommended by Xcode, but maybe it's wrong.


Solution

  • The problem is the way you are creating your mutable data. Data cannot be directly converted to NSMutableData.

    The only way to cast from Data to CFMutableData is casting it first to NSData, get its mutableCopy NSMutableData and cast it to CFMutableData.

    NSMutableData is toll-free bridged to CFMutableData

    But in this case it doesn't make any sense considering that you have no data to be converted. Just use CFDataCreateMutable(nil, 0) to initialize a new CFMutableData object:

    if let cgi = cgi, 
        let mutableData = CFDataCreateMutable(nil, 0),
        let destination = CGImageDestinationCreateWithData(mutableData, "public.png" as CFString, 1, nil) {
        CGImageDestinationAddImage(destination, cgi, nil)
        if CGImageDestinationFinalize(destination) {
            let data = mutableData as Data
            if let image = UIImage(data: data) {
                print(image.size)
            }
        } else {
            print("Error writing Image")
        }
    }
    

    edit/update: Xcode 11 • Swift 5.1

    extension CGImage {
        var png: Data? {
            guard let mutableData = CFDataCreateMutable(nil, 0),
                let destination = CGImageDestinationCreateWithData(mutableData, "public.png" as CFString, 1, nil) else { return nil }
            CGImageDestinationAddImage(destination, self, nil)
            guard CGImageDestinationFinalize(destination) else { return nil }
            return mutableData as Data
        }
    }