I am editing photos via PhotoKit but I discovered this does not preserve the original photo's metadata. This occurs even with the SamplePhotosApp provided by Apple when they apply Sepia or Chrome filters. My question is, how do you ensure all the original photo metadata is preserved?
I've discovered how you can obtain the original image's metadata, and I was able to save that metadata to the final CIImage
I create, but it still is stripped out when the edit is committed. There must be an issue in the way I convert the CIImage
to a CGImage
to a UIImage
to NSData
, or how I'm writing it to disk.
asset.requestContentEditingInputWithOptions(options) { (input: PHContentEditingInput!, _) -> Void in
//Get full image
let url = input.fullSizeImageURL
let orientation = self.input.fullSizeImageOrientation
var inputImage = CIImage(contentsOfURL: url)
inputImage = inputImage.imageByApplyingOrientation(orientation)
//do some processing on original photo here and create a CGImage...
//save the original photo's metadata to a new CIImage:
let originalMetadata = inputImage.properties()
let newImage = CIImage(CGImage: editedCGImage, options: [kCIImageProperties: originalMetadata])
println(newImage.properties()) //correctly prints all metadata!
//commit changes to disk - somewhere after this line the metadata is lost
let eaglContext = EAGLContext(API: .OpenGLES2)
let ciContext = CIContext(EAGLContext: eaglContext)
let outputImageRef = ciContext.createCGImage(newImage, fromRect: newImage.extent())
let uiImage = UIImage(CGImage: outputImageRef, scale: 1.0, orientation: UIImageOrientation.Up)
let jpegNSData = UIImageJPEGRepresentation(uiImage, 0.75)
let contentEditingOutput = PHContentEditingOutput(contentEditingInput: input)
let success = jpegData.writeToURL(contentEditingOutput.renderedContentURL, options: NSDataWritingOptions.AtomicWrite, error: _)
PHPhotoLibrary.sharedPhotoLibrary().performChanges({ () -> Void in
let request = PHAssetChangeRequest(forAsset: asset)
request.contentEditingOutput = contentEditingOutput
}, completionHandler: { (success: Bool, error: NSError!) -> Void in
if success == false { println('failed to commit image edit: \(error)') }
})
})
Original - note the GPS tab:
After editing the photo:
According to the Apple document for the CIImage.properties:
If the CIImage object is the output of a filter (or filter chain), this method returns the metadata from the filter’s original input image.
With that said, this is how I would do it if the documentation is not correct.
You need to save the properties on your own. To do this you'll need to read the content of the URL as NSData
NSURL *url = @"http://somewebsite.com/path/to/some/image.jpg";
NSData *imageData = [NSData dataWithContentsOfURL:url];
CIImage *image = [CIImage imageWithData:imageData];
// Save off the properties
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef) imageData);
NSDictionary* metadata = (__bridge NSDictionary *) CGImageSourceCopyProperties(imageSource, NULL);
// process your image
// ...
NSData *outputData = // create data from altered image.
// Save the image
CGImageSourceRef outputImageSource = CGImageSourceCreateWithData((__bridge CFDataRef) outputData);
CFMutableDataRef jpegData = CFDataCreateMutable(NULL, 0);
CGImageDestinationRef outputDestination = CGImageDestinationCreateWithData(jpegData, CGImageSourceGetType(outputImageSource), 1, NULL);
// add the image data to the destination
CGImageDestinationAddImageFromSource(jpegData, outputImageSource, 0, (__bridge CFDictionaryRef) metadata);
if (CGImageDestinationFinalize(outputDestination)) {
NSLog(@"Successful image creation.");
// process the image rendering, adjustment data creation and finalize the asset edit.
} else {
NSLog(@"Image creation failed.");
}
If you don't need to modify the metadata, there is no need to cast the CFDataRef to an NSDictionary*, but I did here just for completeness.
While this was possible to do in Objective-C as Bradley showed, it did not work correctly in Swift and would cause the performChanges
block to fail (see my other question). This has been addressed in the latest releases - Swift 1.2 in Xcode 6.3. To preserve the metadata, it's this simple:
let metadata = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as NSDictionary
CGImageDestinationAddImage(destination, cgImage, metadata)
Or for Objective-C:
NSMutableDictionary *imageMetadata = [(NSDictionary *) CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL)) mutableCopy];
CGImageDestinationAddImageFromSource(destination, imageSource, 0, (__bridge CFDictionaryRef)(imageMetadata));
EDIT: While this works in the iOS Simulator, it did not work on a real device running the Swift code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With