Modified EXIF data doesn't save properly
Scott, a developer at Apple, returned with my incident report and fixed the issue:
The previous code above is writing string values for the GPS values - this won't work, they must be NS/CFNumbers (we extract a float value for EXIF).
I'll be filing a bug report to Apple against their documentation.
Although it took a week to get this response, I really do appreciate the support Apple provides to their developers. (thanks Scott!) ;-)
Below is the improved code and its correct output:
[[self stillImageOutput] captureStillImageAsynchronouslyFromConnection:videoConnection
completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error)
{
NSData *imageNSData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
CGImageSourceRef imgSource = CGImageSourceCreateWithData((__bridge_retained CFDataRef)imageNSData, NULL);
//get all the metadata in the image
NSDictionary *metadata = (__bridge NSDictionary *)CGImageSourceCopyPropertiesAtIndex(imgSource, 0, NULL);
//make the metadata dictionary mutable so we can add properties to it
NSMutableDictionary *metadataAsMutable = [metadata mutableCopy];
NSMutableDictionary *EXIFDictionary = [[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyExifDictionary]mutableCopy];
NSMutableDictionary *GPSDictionary = [[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyGPSDictionary]mutableCopy];
NSMutableDictionary *RAWDictionary = [[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyRawDictionary]mutableCopy];
if(!EXIFDictionary)
EXIFDictionary = [[NSMutableDictionary dictionary] init];
if(!GPSDictionary)
GPSDictionary = [[NSMutableDictionary dictionary] init];
if(!RAWDictionary)
RAWDictionary = [[NSMutableDictionary dictionary] init];
[GPSDictionary setObject:[NSNumber numberWithFloat:37.795]
forKey:(NSString*)kCGImagePropertyGPSLatitude];
[GPSDictionary setObject:@"N" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
[GPSDictionary setObject:[NSNumber numberWithFloat:122.410]
forKey:(NSString*)kCGImagePropertyGPSLongitude];
[GPSDictionary setObject:@"W" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
[GPSDictionary setObject:@"2012:10:18"
forKey:(NSString*)kCGImagePropertyGPSDateStamp];
[GPSDictionary setObject:[NSNumber numberWithFloat:300]
forKey:(NSString*)kCGImagePropertyGPSImgDirection];
[GPSDictionary setObject:[NSNumber numberWithFloat:37.795]
forKey:(NSString*)kCGImagePropertyGPSDestLatitude];
[GPSDictionary setObject:@"N" forKey:(NSString*)kCGImagePropertyGPSDestLatitudeRef];
[GPSDictionary setObject:[NSNumber numberWithFloat:122.410]
forKey:(NSString*)kCGImagePropertyGPSDestLongitude];
[GPSDictionary setObject:@"W" forKey:(NSString*)kCGImagePropertyGPSDestLongitudeRef];
[EXIFDictionary setObject:@"[S.D.] kCGImagePropertyExifUserComment"
forKey:(NSString *)kCGImagePropertyExifUserComment];
[EXIFDictionary setObject:[NSNumber numberWithFloat:69.999]
forKey:(NSString*)kCGImagePropertyExifSubjectDistance];
//Add the modified Data back into the image’s metadata
[metadataAsMutable setObject:EXIFDictionary forKey:(NSString *)kCGImagePropertyExifDictionary];
[metadataAsMutable setObject:GPSDictionary forKey:(NSString *)kCGImagePropertyGPSDictionary];
[metadataAsMutable setObject:RAWDictionary forKey:(NSString *)kCGImagePropertyRawDictionary];
CFStringRef UTI = CGImageSourceGetType(imgSource); //this is the type of image (e.g., public.jpeg)
//this will be the data CGImageDestinationRef will write into
NSMutableData *newImageData = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)newImageData, UTI, 1, NULL);
if(!destination)
NSLog(@"***Could not create image destination ***");
//add the image contained in the image source to the destination, overidding the old metadata with our modified metadata
CGImageDestinationAddImageFromSource(destination, imgSource, 0, (__bridge CFDictionaryRef) metadataAsMutable);
//tell the destination to write the image data and metadata into our data object.
//It will return false if something goes wrong
BOOL success = NO;
success = CGImageDestinationFinalize(destination);
if(!success)
NSLog(@"***Could not create data from image destination ***");
CIImage *testImage = [CIImage imageWithData:newImageData];
NSDictionary *propDict = [testImage properties];
NSLog(@"Final properties %@", propDict);
}];
Which outputs this:
Final properties info {
ColorModel = RGB;
DPIHeight = 72;
DPIWidth = 72;
Depth = 8;
Orientation = 6;
PixelHeight = 2448;
PixelWidth = 3264;
"{Exif}" = {
ApertureValue = "2.526069";
BrightnessValue = "0.547474";
ColorSpace = 1;
ComponentsConfiguration = (
0,
0,
0,
1
);
ExifVersion = (
2,
2,
1
);
ExposureMode = 0;
ExposureProgram = 2;
ExposureTime = "0.05";
FNumber = "2.4";
Flash = 16;
FlashPixVersion = (
1,
0
);
FocalLenIn35mmFilm = 35;
FocalLength = "4.28";
ISOSpeedRatings = (
320
);
MeteringMode = 5;
PixelXDimension = 3264;
PixelYDimension = 2448;
SceneCaptureType = 0;
SensingMethod = 2;
ShutterSpeedValue = "4.321929";
SubjectArea = (
1631,
1223,
881,
881
);
SubjectDistance = "69.999";
UserComment = "[S.D.] kCGImagePropertyExifUserComment";
WhiteBalance = 0;
};
"{GPS}" = {
DateStamp = "2012:10:18";
DestLatitude = "37.795";
DestLatitudeRef = N;
DestLongitude = "122.41";
DestLongitudeRef = W;
ImgDirection = 300;
Latitude = "37.795";
LatitudeRef = N;
Longitude = "122.41";
LongitudeRef = W;
};
"{JFIF}" = {
DensityUnit = 1;
JFIFVersion = (
1,
1
);
XDensity = 72;
YDensity = 72;
};
"{TIFF}" = {
Orientation = 6;
ResolutionUnit = 2;
XResolution = 72;
YResolution = 72;
"_YCbCrPositioning" = 1;
};
}
As you can see, all values are now properly embedded into the EXIF header and I've tested that this is correctly written with the JPG to the camera roll.
Enjoy! :)