How do you strip all exif data in a UIImage using objective-c? I have been able to get the exif data using the following :
NSData* pngData = UIImagePNGRepresentation(image);
CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)pngData, NULL);
NSDictionary* dic = nil;
if ( NULL == imageSource )
{
#ifdef _DEBUG
CGImageSourceStatus status = CGImageSourceGetStatus ( source );
NSLog ( @"Error: file name : %@ - Status: %d", file, status );
#endif
}
else
{
CFDictionaryRef propertyRef = CGImageSourceCopyPropertiesAtIndex ( imageSource, 0, NULL );
CGImageMetadataRef metadataRef = CGImageSourceCopyMetadataAtIndex ( imageSource, 0, NULL );
// CFDictionaryRef metadataRef = CFDictionaryGetValue(imageProperties, kCGImagePropertyExifDictionary);
if (metadataRef) {
NSDictionary* immutableMetadata = (NSDictionary *)metadataRef;
if ( immutableMetadata ) {
dic = [ NSDictionary dictionaryWithDictionary : (NSDictionary *)metadataRef ];
}
CFRelease ( metadataRef );
}
CFRelease(imageSource);
imageSource = nil;
}
return dic;
A couple of thoughts:
Generally the process of loading an image from the NSData
or from the contents of a file into a UIImage
, re-extracting the data with UIImagePNGRepresentation
and saving that back to a NSData
will strip the metadata from the image.
This simple technique has its disadvantages, though. Notably, the process of forcing it to a PNG representation may affect the size of the output NSData
dramatically, though. For example, if the original image was a compressed JPEG (such as captured by the camera), the resulting PNG file can actually be larger than the original JPEG.
Generally I would prefer to get the original data (if a file in Documents or the bundle, load that directly to a NSData
; if it's something retrieved from the ALAssetsLibrary
, I would retrieve the ALAsset
, and from that, the ALAssetRepresentation
, and from that I'd use getBytes
to get the original binary representation of that original asset). This avoids the round-tripping it through a UIImage
(particularly if subsequently using UIImagePNGRepresentation
or UIImageJEPGRepresentation
).
If you want to strip exif data, you can:
Create an image source from the original NSData
;
Create an image destination, using the same "number of images" (almost always 1) and the "type";
When copying the images from the source to the destination, tell it that the appropriate keys (kCGImagePropertyExifDictionary
and kCGImagePropertyGPSDictionary
should be set to kCFNull
), which according to the CGImageDestinationAddImageFromSource
documentation is how you specify that those should be removed in the destination if they happened to appear in the source).
Thus, it might look like:
- (NSData *)dataByRemovingExif:(NSData *)data
{
CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)data, NULL);
NSMutableData *mutableData = nil;
if (source) {
CFStringRef type = CGImageSourceGetType(source);
size_t count = CGImageSourceGetCount(source);
mutableData = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((CFMutableDataRef)mutableData, type, count, NULL);
NSDictionary *removeExifProperties = @{(id)kCGImagePropertyExifDictionary: (id)kCFNull,
(id)kCGImagePropertyGPSDictionary : (id)kCFNull};
if (destination) {
for (size_t index = 0; index < count; index++) {
CGImageDestinationAddImageFromSource(destination, source, index, (__bridge CFDictionaryRef)removeExifProperties);
}
if (!CGImageDestinationFinalize(destination)) {
NSLog(@"CGImageDestinationFinalize failed");
}
CFRelease(destination);
}
CFRelease(source);
}
return mutableData;
}
Note, GPS information is not technically exif data, but I assume you wanted to remove that, too. If you wanted to keep the GPS data, remove the kCGImagePropertyGPSDictionary
entry from my removeExifProperties
dictionary.
BTW, in your code to extract the meta data, you seem to be casting the CGImageMetadataRef
as a NSDictionary
. If your technique works, that's fine, but I think that CGImageMetadataRef
is considered to be an opaque data type, and that one really should be using CGImageMetadataCopyTags
to extract the array of tags:
- (NSArray *)metadataForData:(NSData *)data
{
NSArray *metadataArray = nil;
CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)data, NULL);
if (source) {
CGImageMetadataRef metadata = CGImageSourceCopyMetadataAtIndex(source, 0, NULL);
if (metadata) {
metadataArray = CFBridgingRelease(CGImageMetadataCopyTags(metadata));
CFRelease(metadata);
}
CFRelease(source);
}
return metadataArray;
}
For the sake of completeness, in iOS versions prior to 7.0, you can extract the data from the properties:
- (NSDictionary *)metadataForData:(NSData *)data
{
NSDictionary *properties = nil;
CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)data, NULL);
if (source) {
properties = CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(source, 0, NULL));
CFRelease(source);
}
return properties;
}
According to the Image I/O programming guide, you can use CGImageDestinationSetProperties
to add a CFDictionaryRef
of the properties.
Their sample code is:
float compression = 1.0; // Lossless compression if available.
int orientation = 4; // Origin is at bottom, left.
CFStringRef myKeys[3];
CFTypeRef myValues[3];
CFDictionaryRef myOptions = NULL;
myKeys[0] = kCGImagePropertyOrientation;
myValues[0] = CFNumberCreate(NULL, kCFNumberIntType, &orientation);
myKeys[1] = kCGImagePropertyHasAlpha;
myValues[1] = kCFBooleanTrue;
myKeys[2] = kCGImageDestinationLossyCompressionQuality;
myValues[2] = CFNumberCreate(NULL, kCFNumberFloatType, &compression);
myOptions = CFDictionaryCreate( NULL, (const void **)myKeys, (const void **)myValues, 3,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
// Release the CFNumber and CFDictionary objects when you no longer need them.
And I don't understand why they didn't take it all the way. I'm guessing what comes next would be:
CFImageDestinationRef
How to do the first step isn't very clear from the docs. So I looked at the CGImageDestination
reference, and it looks like it can be done this way (NOT TESTED):
NSMutableData* mutableData = [[NSMutableData alloc] initWithCapacity:[pngData length]];
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge_transfer CFMutableDataRef)mutableData, <# Some UTI Type #>, 1, NULL);
CGImageDestinationAddImage(imageDestination, image, yourProperties);
CGImageDestinationFinalize(imageDestination);
So create your properties dictionary the way Apple shows in the docs, then create an image destination and write everything to it, including your properties. Afterward you can access the NSMutableData
object and read your image data.
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