After countless attempts and sieving through every SO answer + google result, it baffles me that working with EXIF on iOS is so frustrating.
Below is working code with its results.
[[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);
NSLog(@"original metadata Info: %@",metadata);
//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:@"camera coord Latitude"
forKey:(NSString*)kCGImagePropertyGPSLatitude];
[GPSDictionary setObject:@"camera coord Longitude"
forKey:(NSString*)kCGImagePropertyGPSLongitude];
[GPSDictionary setObject:@"camera GPS Date Stamp"
forKey:(NSString*)kCGImagePropertyGPSDateStamp];
[GPSDictionary setObject:@"camera direction (heading) in degrees"
forKey:(NSString*)kCGImagePropertyGPSImgDirection];
[GPSDictionary setObject:@"subject coord Latitude"
forKey:(NSString*)kCGImagePropertyGPSDestLatitude];
[GPSDictionary setObject:@"subject coord Longitude"
forKey:(NSString*)kCGImagePropertyGPSDestLongitude];
[EXIFDictionary setObject:@"[S.D.] kCGImagePropertyExifUserComment"
forKey:(NSString *)kCGImagePropertyExifUserComment];
[EXIFDictionary setValue:@"69 m" 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];
NSLog(@"metadataAsMutable Info: %@",metadataAsMutable);
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(@"Properties %@", propDict);
}];
Which outputs this:
2012-10-12 23:17:45.415 Waypointer[3120:907] original metadata Info: {
ColorModel = RGB;
DPIHeight = 72;
DPIWidth = 72;
Depth = 8;
Orientation = 1;
PixelHeight = 2448;
PixelWidth = 3264;
"{Exif}" = {
ApertureValue = "2.526069";
BrightnessValue = "-4.410617";
ColorSpace = 1;
ComponentsConfiguration = (
1,
2,
3,
0
);
ExifVersion = (
2,
2,
1
);
ExposureMode = 0;
ExposureProgram = 2;
ExposureTime = "0.06666667";
FNumber = "2.4";
Flash = 16;
FlashPixVersion = (
1,
0
);
FocalLenIn35mmFilm = 35;
FocalLength = "4.28";
ISOSpeedRatings = (
800
);
MeteringMode = 5;
PixelXDimension = 3264;
PixelYDimension = 2448;
SceneCaptureType = 0;
SensingMethod = 2;
ShutterSpeedValue = "3.906905";
SubjectArea = (
1631,
1223,
881,
881
);
WhiteBalance = 0;
};
"{TIFF}" = {
Orientation = 1;
ResolutionUnit = 2;
XResolution = 72;
YResolution = 72;
"_YCbCrPositioning" = 1;
};
}
And this:
2012-10-12 23:17:45.421 Waypointer[3120:907] metadataAsMutable Info: {
ColorModel = RGB;
DPIHeight = 72;
DPIWidth = 72;
Depth = 8;
Orientation = 1;
PixelHeight = 2448;
PixelWidth = 3264;
"{Exif}" = {
ApertureValue = "2.526069";
BrightnessValue = "-4.410617";
ColorSpace = 1;
ComponentsConfiguration = (
1,
2,
3,
0
);
ExifVersion = (
2,
2,
1
);
ExposureMode = 0;
ExposureProgram = 2;
ExposureTime = "0.06666667";
FNumber = "2.4";
Flash = 16;
FlashPixVersion = (
1,
0
);
FocalLenIn35mmFilm = 35;
FocalLength = "4.28";
ISOSpeedRatings = (
800
);
MeteringMode = 5;
PixelXDimension = 3264;
PixelYDimension = 2448;
SceneCaptureType = 0;
SensingMethod = 2;
ShutterSpeedValue = "3.906905";
SubjectArea = (
1631,
1223,
881,
881
);
SubjectDistance = "69 m";
UserComment = "[S.D.] kCGImagePropertyExifUserComment";
WhiteBalance = 0;
};
"{GPS}" = {
DateStamp = "camera GPS Date Stamp";
DestLatitude = "subject coord Latitude";
DestLongitude = "subject coord Longitude";
ImgDirection = "camera direction (heading) in degrees";
Latitude = "camera coord Latitude";
Longitude = "camera coord Longitude";
};
"{Raw}" = {
};
"{TIFF}" = {
Orientation = 1;
ResolutionUnit = 2;
XResolution = 72;
YResolution = 72;
"_YCbCrPositioning" = 1;
};
}
And, after it's all done, this:
2012-10-12 23:17:47.131 Waypointer[3120:907] Properties {
ColorModel = RGB;
DPIHeight = 72;
DPIWidth = 72;
Depth = 8;
Orientation = 1;
PixelHeight = 2448;
PixelWidth = 3264;
"{Exif}" = {
ApertureValue = "2.526069";
BrightnessValue = "-4.410617";
ColorSpace = 1;
ComponentsConfiguration = (
0,
0,
0,
1
);
ExifVersion = (
2,
2,
1
);
ExposureMode = 0;
ExposureProgram = 2;
ExposureTime = "0.06666667";
FNumber = "2.4";
Flash = 16;
FlashPixVersion = (
1,
0
);
FocalLenIn35mmFilm = 35;
FocalLength = "4.28";
ISOSpeedRatings = (
800
);
MeteringMode = 5;
PixelXDimension = 3264;
PixelYDimension = 2448;
SceneCaptureType = 0;
SensingMethod = 2;
ShutterSpeedValue = "3.906905";
SubjectArea = (
1631,
1223,
881,
881
);
UserComment = "[S.D.] kCGImagePropertyExifUserComment";
WhiteBalance = 0;
};
"{JFIF}" = {
DensityUnit = 1;
JFIFVersion = (
1,
1
);
XDensity = 72;
YDensity = 72;
};
"{TIFF}" = {
Orientation = 1;
ResolutionUnit = 2;
XResolution = 72;
YResolution = 72;
"_YCbCrPositioning" = 1;
};
}
As the example illustrates, you can see the image's original metaData, its modification, and then its final output.
The final output is what bothers me because no matter what I do, I cannot get my modified values to stick!
Is there some very specific format I'm missing? Why is iOS stripping my modifications? What do I need to do to add these extra values? They are listed in the .header and figured it should be easily accepted.
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! :)
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