Chiquis Chiquis - 4 months ago 39
Objective-C Question

Save CUSTOM metadata in an image taken from AVFoundation in iOS

How do I save custom metadata in an image when i get the image using the

AVFoundation framework
?

I know I can access the properties weather it I have my image as
UIImage
or
CIImage
but the properties that these seem to have differ from each other (even if it is the same image).

So far I access the dictionary like this: (Code taken from Caffeinated Cocoa blog)

NSLog(@"about to request a capture from: %@", [self stillImageOutput]);
[[self stillImageOutput] captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error)
{

NSData *jpeg = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer] ;

CGImageSourceRef source = CGImageSourceCreateWithData((__bridge_retained CFDataRef)jpeg, NULL);

//get all the metadata in the image
NSDictionary *metadata = (__bridge NSDictionary *) CGImageSourceCopyPropertiesAtIndex(source,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];
}
if(!GPSDictionary) {
GPSDictionary = [NSMutableDictionary dictionary];
}
if(!RAWDictionary) {
RAWDictionary = [NSMutableDictionary dictionary];
}

float _lat = gpsInfo.coordinate.latitude;
float _lon = gpsInfo.coordinate.longitude;
float _alt = gpsInfo.altitude;
NSDate *_date = gpsInfo.timestamp;
float _heading = gpsInfo.course;;

[GPSDictionary setValue:[NSNumber numberWithFloat:_lat]
forKey:(NSString*)kCGImagePropertyGPSLatitude];
[GPSDictionary setValue:[NSNumber numberWithFloat:_lon]
forKey:(NSString*)kCGImagePropertyGPSLongitude];
[GPSDictionary setValue:[NSNumber numberWithFloat:_alt]
forKey:(NSString*)kCGImagePropertyGPSAltitude];
[GPSDictionary setValue:_date
forKey:(NSString*)kCGImagePropertyGPSDateStamp];
[GPSDictionary setValue:[NSNumber numberWithFloat:_heading]
forKey:(NSString*)kCGImagePropertyGPSImgDirection];


[EXIFDictionary setValue:@"Wasup" forKey:(NSString *)kCGImagePropertyExifUserComment];

[RAWDictionary setValue:attitude forKey:@"Attitude"];

//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(@"Info: %@",metadataAsMutable);

CFStringRef UTI = CGImageSourceGetType(source); //this is the type of image (e.g., public.jpeg)

//this will be the data CGImageDestinationRef will write into
NSMutableData *data = [NSMutableData data];

CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)data,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,source,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 ***");
}


And i can either save my image like this:

CIImage *testImage = [CIImage imageWithData:data];

NSDictionary *propDict = [testImage properties];


NSLog(@"Properties %@",propDict);


Or like this

UIImage *imageMod = [[UIImage alloc] initWithData:data];


CGImageSourceRef source2 = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);

//get all the metadata in the image
NSDictionary *metadata2 = (__bridge NSDictionary *) CGImageSourceCopyPropertiesAtIndex(source2,0,NULL);

NSLog(@"WASD: %@",metadata2);


BUt neither of the options return my Custom Raw Data Dictionary.

In summary what i want to do is save my own object in a field created by myself (in this case a cmattitude object)

PD: When i
NSLOG
the dictionary before merging it with the image it does have all the files like i want them.

Info: {
ColorModel = RGB;
DPIHeight = 72;
DPIWidth = 72;
Depth = 8;
Orientation = 6;
PixelHeight = 1080;
PixelWidth = 1920;
"{Exif}" = {
ApertureValue = "2.526069";
BrightnessValue = "4.013361";
ColorSpace = 1;
ComponentsConfiguration = (
1,
2,
3,
0
);
ExifVersion = (
2,
2,
1
);
ExposureMode = 0;
ExposureProgram = 2;
ExposureTime = "0.03333334";
FNumber = "2.4";
Flash = 16;
FlashPixVersion = (
1,
0
);
FocalLenIn35mmFilm = 35;
FocalLength = "4.28";
ISOSpeedRatings = (
80
);
MeteringMode = 5;
PixelXDimension = 1920;
PixelYDimension = 1080;
SceneCaptureType = 0;
SensingMethod = 2;
Sharpness = 0;
ShutterSpeedValue = "4.906905";
SubjectArea = (
959,
539,
518,
388
);
UserComment = "Wazup";
WhiteBalance = 0;
};
"{GPS}" = {
Altitude = 102;
DateStamp = "2012-01-20 06:55:48 +0000";
ImgDirection = "-1";
Latitude = "35.02496";
Longitude = "135.754";
};
"{Raw}" = {
Attitude = "Pitch: 50.913760, Roll: 36.342350, Yaw: -164.272361 @ 0.048291\n";
};
"{TIFF}" = {
Orientation = 6;
ResolutionUnit = 2;
XResolution = 72;
YResolution = 72;
"_YCbCrPositioning" = 1;
};


But when i NSLOG the dictionary from the new image it gives me this for the CIIMage:

Properties {
ColorModel = RGB;
DPIHeight = 72;
DPIWidth = 72;
Depth = 8;
Orientation = 6;
PixelHeight = 1080;
PixelWidth = 1920;
"{Exif}" = {
ApertureValue = "2.526069";
BrightnessValue = "4.013361";
ColorSpace = 1;
ComponentsConfiguration = (
0,
0,
0,
1
);
ExifVersion = (
2,
2,
1
);
ExposureMode = 0;
ExposureProgram = 2;
ExposureTime = "0.03333334";
FNumber = "2.4";
Flash = 16;
FlashPixVersion = (
1,
0
);
FocalLenIn35mmFilm = 35;
FocalLength = "4.28";
ISOSpeedRatings = (
80
);
MeteringMode = 5;
PixelXDimension = 1920;
PixelYDimension = 1080;
SceneCaptureType = 0;
SensingMethod = 2;
Sharpness = 0;
ShutterSpeedValue = "4.906905";
SubjectArea = (
959,
539,
518,
388
);
UserComment = "Wazup";
WhiteBalance = 0;
};
"{GPS}" = {
Altitude = 102;
ImgDirection = 0;
Latitude = "35.025";
Longitude = "135.754";
};
"{JFIF}" = {
DensityUnit = 1;
JFIFVersion = (
1,
1
);
XDensity = 72;
YDensity = 72;
};
"{TIFF}" = {
Orientation = 6;
ResolutionUnit = 2;
XResolution = 72;
YResolution = 72;
"_YCbCrPositioning" = 1;
};


And if I
NSLog
the
UIImage
it gives me even less info.

Thanks!

Answer

UIImage and CIImage don't hold all the metadata. In fact, converting it from NSData to either of those two formats will strip a lot of that metadata out, so IMHO, UIImage and CIImage should only be used if you are planning on displaying it in the UI or passing it to CoreImage.

You can read the image properties like this:

- (NSDictionary *)getImageProperties:(NSData *)imageData {
    // get the original image metadata
    CGImageSourceRef imageSourceRef = CGImageSourceCreateWithData((__bridge CFDataRef) imageData, NULL);
    CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSourceRef, 0, NULL);
    NSDictionary *props = (__bridge_transfer NSDictionary *) properties;
    CFRelease(imageSourceRef);

    return props;
}

You can then convert your NSDictionary to a NSMutableDictionary:

NSDictionary *props = [self getImageProperties:imageData];
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:props];

After that, add whatever fields you would like. Note that EXIF data is found by grabbing the kCGImagePropertyExifDictionary, GPS data by the kCGImagePropertyGPSDictionary, and so on:

NSMutableDictionary *exifDictionary = properties[(__bridge NSString *) kCGImagePropertyExifDictionary];

Look at the CGImageProperties header file for all the keys available.

Okay, so now that you have made whatever changes you needed to the metadata, adding it back takes a couple of more steps:

// incoming image data
NSData *imageData;

// create the image ref
CGDataProviderRef imgDataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef) imageData);
CGImageRef imageRef = CGImageCreateWithJPEGDataProvider(imgDataProvider, NULL, true, kCGRenderingIntentDefault);

// the updated properties from above
NSMutableDictionary *mutableDictionary;

// create the new output data
CFMutableDataRef newImageData = CFDataCreateMutable(NULL, 0);
// my code assumes JPEG type since the input is from the iOS device camera
CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef) @"image/jpg", kUTTypeImage);
// create the destination
CGImageDestinationRef destination = CGImageDestinationCreateWithData(newImageData, type, 1, NULL);
// add the image to the destination
CGImageDestinationAddImage(destination, imageRef, (__bridge CFDictionaryRef) mutableDictionary);
// finalize the write
CGImageDestinationFinalize(destination);

// memory cleanup
CGDataProviderRelease(imgDataProvider);
CGImageRelease(imageRef);
CFRelease(type);
CFRelease(destination);

// your new image data
NSData *newImage = (__bridge_transfer NSData *)newImageData;

And there you have it.

Comments