Using UIImagePickerController I am attempting to gather GPS info from the metadata of the image. Here is my code sample:
NSDictionary *imageMetadata = [info objectForKey: UIImagePickerControllerMediaMetadata];
NSLog(#"Working META: %#",imageMetadata);
CGDataProviderRef dataProvider = CGImageGetDataProvider(originalImage.CGImage);
CFDataRef data = CGDataProviderCopyData(dataProvider);
CGImageSourceRef imageSource = CGImageSourceCreateWithData(data, nil);
NSDictionary *metaDict = (__bridge NSDictionary *)(CGImageSourceCopyProperties(imageSource, nil));
NSLog(#"Not WOrking META: %#",metaDict);
In the output from NSLog I can see that Working Meta has all associated content. Unfortunately the Not Working Meta is outputting null. From what I can tell I need to enhance the options on the image source and CFDictionary rather than using nil. Am I on the right path? If so, what change should I make to pull the GPS data through?
Try obtaining your mutable copy of the source EXIF dictionary like this:
UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
NSData *jpeg = UIImageJPEGRepresentation(image,image.scale);
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)jpeg, NULL);
NSDictionary *metadata = (__bridge NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source,0,NULL);
NSMutableDictionary *mutableMetadata = [metadata mutableCopy];
Now set GPS data to that EXIF dictionary (code courtesy GusUtils):
[mutableMetadata setLocation:self.currentLocation]
And finally you need to populate this data to some destination
CFStringRef UTI = CGImageSourceGetType(source);
NSMutableData *data = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef) data, UTI, 1, NULL);
CGImageDestinationAddImageFromSource(destination,source, 0, (__bridge CFDictionaryRef) mutableMetadata);
BOOL success = CGImageDestinationFinalize(destination);
At this point the variable data should contain all information(image+GPS metadata+other metadata) and you can use this data to store wherever you like.
EDIT:
To add core location functionality in your class declare CLLocationManagerDelegate protocol in
the header. Instantiate a location manager in your viewDidLoad:
self.locManager = [CLLocationManager new];
self.locManager.delegate = self;
self.locManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
self.locManager.distanceFilter = 5.0f; //location would be updated if moved by 5 miter
[self.locManager startUpdatingLocation];
And implement following methods to gather location info:
-(void)locationManager:(CLLocationManager*)manager didUpdateLocations:(NSArray*)locations {
if (!locations || [locations count] == 0) {
return;
}
CLLocation* loc = locations[[locations count] - 1];
if (loc.horizontalAccuracy < 0) {
return;
}
self.currentLocation = loc;
}
-(void)locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation {
if (newLocation.horizontalAccuracy < 0) {
return;
}
self.currentLocation = newLocation;
}
-(void)locationManager:(CLLocationManager*)manager didFailWithError:(NSError*)error {
NSLog(#"core location error: %#",[error localizedDescription]);
if ([error code] != kCLErrorLocationUnknown) {
NSLog(#"location service will terminate now");
self.locManager.delegate = nil;
[self.locManager stopUpdatingLocation];
self.currentLocation = nil;
}
}
Also make sure to stop the service before leaving the VC
self.locManager.delegate = nil;
[self.locManager stopUpdatingLocation];
Then you can use self.currentLocation for your `location variable.
EDIT2
Ok its seems that you are already using "NSMutableDictionary+ImageMetadata.h" category from GusUtils. So I have made some modifications to my answer. Please give it a try.
Related
I have created a custom camera using AVFoundation, now after capturing images, I need to save them in the iPhone's gallery.
I tried saving images with UIImageWriteToSavedPhotosAlbum but found that this does not save EXIF information.
To Save EXIF information with an image refer below code.
- (void)saveImageDataToPhotoAlbum:(NSData *)originalData
{
NSDictionary *dataDic = [self getDataAndMetadata:originalData];
ALAssetsLibrary *assetsLib = [[ALAssetsLibrary alloc] init];
[assetsLib writeImageDataToSavedPhotosAlbum:dataDic[#"data"]
metadata:dataDic[#"metadata"]
completionBlock:^(NSURL *url, NSError *e) {
[self addToMyAlbum:url];
}];
}
- (NSDictionary *)getDataAndMetadata:(NSData *)originalData
{
CGImageSourceRef cimage = CGImageSourceCreateWithData((CFDataRef)originalData, nil);
NSDictionary *metadata = (NSDictionary *)CGImageSourceCopyPropertiesAtIndex(cimage, 0, nil);
NSMutableDictionary *metadataAsMutable = [NSMutableDictionary dictionaryWithDictionary:metadata];
metadataAsMutable[(NSString *)kCGImagePropertyGPSDictionary] = self.myGpsDic;
NSMutableData *dataForMetadataRemoval = [NSMutableData data];
CGImageDestinationRef dest =
CGImageDestinationCreateWithData((CFMutableDataRef)dataForMetadataRemoval, CGImageSourceGetType(cimage), 1, nil);
CGImageDestinationAddImageFromSource(dest, cimage, 0, (CFDictionaryRef)metadataAsMutable);
CGImageDestinationFinalize(dest);
CFRelease(dest);
[metadata release];
CFRelease(cimage);
return #{ #"data" : (dataForMetadataRemoval), #"metadata" : metadataAsMutable};
}
return metadataAsMutable;
}
Following are the method an methods call tree that cause the memory leak
//get the exif info of image asset background
#autoreleasepool {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
self.dataSource = [NSMutableArray new];
__weak typeof(self) weakSelf = self;
dispatch_async(queue, ^{
for (int i = 0; i < self.selectImageList.count; i++) {
PFALAssetImageItemData *dataEntity = weakSelf.selectImageList[i];
//getting the object usering an image asset
ImageExifInfoEntity *imageExifEntity = [ImageExifInfoEntity getAlbumImageFromAsset:dataEntity.imageAsset imageOryder:i];
LOG(#"%#",imageExifEntity.description);
[weakSelf.dataSource addObject:imageExifEntity];
}
//back main thread update views
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
[self hideHud];
});
});
}
In this code I want to creat ImageExifInfoEntity using a static method with an asset in a thread:
[ImageExifInfoEntity getAlbumImageFromAsset:dataEntity.imageAsset imageOryder:i];
In this method,it create an new object of ImageExifInfoEntity type,and get the exif Dictionary using a static method
+(ImageExifInfoEntity *)getAlbumImageFromAsset:(ALAsset *)asset category:(NSString *)category imageOryder:(NSInteger)imageOrder{
ImageExifInfoEntity *albumImage = [ImageExifInfoEntity new];
..........
albumImage.imageSize = [UIImage imageSizeWithAlasset:asset];
albumImage.exifDic = [ImageExifInfoEntity getExifInfoFromAsset:asset] == nil ? #{}:[ImageExifInfoEntity getExifInfoFromAsset:asset];
..........
}
Finally,I get an exif dictionary using this method where the memory leak happed
+(NSDictionary *)getExifInfoFromAsset:(ALAsset *)asset {
NSDictionary *_imageProperty;
__weak ALAsset *tempAsset = asset;
ALAssetRepresentation *representation = tempAsset.defaultRepresentation;
uint8_t *buffer = (uint8_t *)malloc(representation.size);
NSError *error;
NSUInteger length = [representation getBytes:buffer fromOffset:0 length:representation.size error:&error];
NSData *data = [NSData dataWithBytes:buffer length:length];
CGImageSourceRef cImageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(cImageSource, 0, NULL);
_imageProperty = (__bridge_transfer NSDictionary*)imageProperties;
free(buffer);
NSLog(#"image property: %#", _imageProperty);
return _imageProperty;
}
here is the instrument analyze result
call tree
the final method that cause memory leaks
CGImageSourceCreateWithData() is a creation function.
CGImageSourceRef cImageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
According to the CF memory rules, you have to release it with CFRelease().
replace __bridge with __bridge_transfer so that ARC will be in charge of freeing the memory
I think you should call CFRelease(cImageSource); to release the image source. See document: https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CGImageSource/#//apple_ref/c/func/CGImageSourceCreateWithData
An image source. You are responsible for releasing this object using CFRelease.
I am using following code to fetch the connected WIFI with my ios device.
I want to know what data does SSIDDATA contains and how to read this data.
-(id)fetchSSIDInfo
{
NSArray *ifs = (__bridge NSArray *)(CNCopySupportedInterfaces());
NSLog(#"%s: Supported interfaces: %#", __func__, ifs);
id info = nil;
for (NSString *ifnam in ifs) {
info = (__bridge id)(CNCopyCurrentNetworkInfo((__bridge CFStringRef)ifnam));
NSLog(#"%s: %# => %#", __func__, ifnam, info);
if (info && [info count]) {
break;
}
}
return info;
}
SSIDDATA is the hex representation of the SSID. Nothing else, as far as i know.
My application should be able to write custom metadata entries to PNG images for export to the UIPasteboard.
By piecing together various posts on the subject, I've been able to come up with the class given below as source.
Triggering the copyPressed method with a button, I'm able to set custom metadata with JPG images (EXIF):
Image[6101:907] found jpg exif dictionary
Image[6101:907] checking image metadata on clipboard
Image[6101:907] {
ColorModel = RGB;
Depth = 8;
Orientation = 1;
PixelHeight = 224;
PixelWidth = 240;
"{Exif}" = {
ColorSpace = 1;
PixelXDimension = 240;
PixelYDimension = 224;
UserComment = "Here is a comment";
};
"{JFIF}" = {
DensityUnit = 0;
JFIFVersion = (
1,
1
);
XDensity = 1;
YDensity = 1;
};
"{TIFF}" = {
Orientation = 1;
};
}
Although I'm able to read the PNG metadata just fine, I can't seem to write to it:
Image[6116:907] found png property dictionary
Image[6116:907] checking image metadata on clipboard
Image[6116:907] {
ColorModel = RGB;
Depth = 8;
PixelHeight = 224;
PixelWidth = 240;
"{PNG}" = {
InterlaceType = 0;
};
}
However, nothing in the documentation suggests this should fail and the presence of many PNG-specific metadata constants suggests it should succeed.
My application should use PNG to avoid JPG's lossy compression.
Why can I not set custom metadata on an in-memory PNG image in iOS?
Note: I've seen this SO question, but it doesn't address the problem here, which is how to write metadata to PNG images specifically.
IMViewController.m
#import "IMViewController.h"
#import <ImageIO/ImageIO.h>
#interface IMViewController ()
#end
#implementation IMViewController
- (IBAction)copyPressed:(id)sender
{
// [self copyJPG];
[self copyPNG];
}
-(void)copyPNG
{
NSData *pngData = UIImagePNGRepresentation([UIImage imageNamed:#"wow.png"]);
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)pngData, NULL);
NSDictionary *metadata = (__bridge NSDictionary *) CGImageSourceCopyPropertiesAtIndex(source, 0, NULL);
NSMutableDictionary *mutableMetadata = [metadata mutableCopy];
NSMutableDictionary *dict = [[mutableMetadata objectForKey:(NSString *) kCGImagePropertyPNGDictionary] mutableCopy];
if (dict) {
NSLog(#"found png property dictionary");
} else {
NSLog(#"creating png property dictionary");
dict = [NSMutableDictionary dictionary];
}
// set values on the root dictionary
[mutableMetadata setObject:#"Name of Software" forKey:(NSString *)kCGImagePropertyPNGDescription];
[mutableMetadata setObject:dict forKey:(NSString *)kCGImagePropertyPNGDictionary];
// set values on the internal dictionary
[dict setObject:#"works" forKey:(NSString *)kCGImagePropertyPNGDescription];
CFStringRef UTI = CGImageSourceGetType(source);
NSMutableData *data = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef) data, UTI, 1, NULL);
if (!destination) {
NSLog(#">>> Could not create image destination <<<");
return;
}
CGImageDestinationAddImageFromSource(destination, source, 0, (__bridge CFDictionaryRef) mutableMetadata);
BOOL success = CGImageDestinationFinalize(destination);
if (!success) {
NSLog(#">>> Error Writing Data <<<");
}
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
[pasteboard setData:data forPasteboardType:#"public.png"];
[self showPNGMetadata];
}
-(void)copyJPG
{
NSData *jpgData = UIImageJPEGRepresentation([UIImage imageNamed:#"wow.jpg"], 1);
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef) jpgData, NULL);
NSDictionary *metadata = (__bridge NSDictionary *) CGImageSourceCopyPropertiesAtIndex(source, 0, NULL);
NSMutableDictionary *mutableMetadata = [metadata mutableCopy];
NSMutableDictionary *exif = [[mutableMetadata objectForKey:(NSString *)kCGImagePropertyExifDictionary] mutableCopy];
if (exif) {
NSLog(#"found jpg exif dictionary");
} else {
NSLog(#"creating jpg exif dictionary");
}
// set values on the exif dictionary
[exif setObject:#"Here is a comment" forKey:(NSString *)kCGImagePropertyExifUserComment];
[mutableMetadata setObject:exif forKey:(NSString *)kCGImagePropertyExifDictionary];
CFStringRef UTI = CGImageSourceGetType(source);
NSMutableData *data = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef) data, UTI, 1, NULL);
if(!destination) {
NSLog(#">>> Could not create image destination <<<");
return;
}
CGImageDestinationAddImageFromSource(destination,source, 0, (__bridge CFDictionaryRef) mutableMetadata);
BOOL success = CGImageDestinationFinalize(destination);
if (!success) {
NSLog(#">>> Could not create data from image destination <<<");
}
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
[pasteboard setData:data forPasteboardType:#"public.jpeg"];
[self showJPGMetadata];
}
-(void)showJPGMetadata
{
NSLog(#"checking image metadata on clipboard");
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
NSData *data = [pasteboard dataForPasteboardType:#"public.jpeg"];
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
NSDictionary *metadata = (__bridge NSDictionary *) CGImageSourceCopyPropertiesAtIndex(source,0,NULL);
NSLog(#"%#", metadata);
}
-(void)showPNGMetadata
{
NSLog(#"checking image metadata on clipboard");
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
NSData *data = [pasteboard dataForPasteboardType:#"public.png"];
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
NSDictionary *metadata = (__bridge NSDictionary *) CGImageSourceCopyPropertiesAtIndex(source,0,NULL);
NSLog(#"%#", metadata);
}
#end
If you will try to save your image with modified metadata
[data writeToFile:[NSTemporaryDirectory() stringByAppendingPathComponent:#"test.png"]
atomically:YES];
And than view it properties in Finder. You will see that kCGImagePropertyPNGDescription field was setted up successfully.
But if you will try read metadata of this new file, kCGImagePropertyPNGDescription will be lost.
ColorModel = RGB;
Depth = 8;
PixelHeight = 1136;
PixelWidth = 640;
"{PNG}" = {
InterlaceType = 0;
};
After some research I found that PNG doesn't contain metadata. But it may contain XMP metadata. However seems like ImageIO didn't work with XMP.
Maybe you can try to use ImageMagic or libexif.
Useful links:
PNG Specification
Reading/Writing image XMP on iPhone / Objective-c
Does PNG support metadata fields like Author, Camera Model, etc?
Does PNG contain EXIF data like JPG?
libexif.sourceforge.net
I'm trying to create a custom image gallery within my iOS app. I would like to enable the user to be able to save certain meta data with the image so that it can be pulled up in the app later with the attached information.
First, when the user takes a picture, the app saves the image into a custom album for the app:
UITextField *nameField = [alertView textFieldAtIndex:0];
NSMutableDictionary *metaData = [[NSMutableDictionary alloc] init];
[metaData setObject:currentEvent forKey:kMetaDataEventKey];
[metaData setObject:[AppDelegate getActivePerson].name forKey:kMetaDataPersonKey];
[metaData setObject:nameField.text forKey:kMetaDataNameKey];
NSLog(#"Saving image with metadata: %#", metaData);
NSMutableDictionary *realMetaData = [[NSMutableDictionary alloc] init];
[realMetaData setObject:metaData forKey:kCGImagePropertyTIFFDictionary];
[library saveImage:imageToSave toAlbum:albumName metadata:realMetaData withCompletionBlock:^(NSError *error) {
if ( error != nil )
{
NSLog(#"Error saving picture? %#", error);
}
[self.tableView reloadData];
}];
Upon saving I get the following log message:
Saving image with metadata: {
Event = t;
Person = "George James";
PictureName = tt;
}
Then when I attempt to retrieve the images later, I use this function
-(void) loadAssets
{
self.assets = [NSMutableArray arrayWithCapacity:album.numberOfAssets];
[album enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if ( result != nil )
{
NSDictionary *metaData = result.defaultRepresentation.metadata;
NSLog(#"Retrieved image metadata: %#", metaData);
}
else
{
[self.tableView reloadData];
}
}];
}
But the log indicates that it did not successfully save the meta data associated with the image:
Retrieved image metadata: {
ColorModel = RGB;
DPIHeight = 72;
DPIWidth = 72;
Depth = 8;
Orientation = 1;
PixelHeight = 720;
PixelWidth = 960;
"{Exif}" = {
ColorSpace = 1;
ComponentsConfiguration = (
1,
2,
3,
0
);
ExifVersion = (
2,
2,
1
);
FlashPixVersion = (
1,
0
);
PixelXDimension = 960;
PixelYDimension = 720;
SceneCaptureType = 0;
};
"{TIFF}" = {
Orientation = 1;
ResolutionUnit = 2;
XResolution = 72;
YResolution = 72;
"_YCbCrPositioning" = 1;
};
}
library is an ALAssetsLibrary instance, and the saveImage: toAlbum: method is from this blog post, only slightly modified so that I can save metadata as such:
-(void)saveImage:(UIImage *)image toAlbum:(NSString *)albumName metadata:(NSDictionary *)metadata withCompletionBlock:(SaveImageCompletion)completionBlock
{
//write the image data to the assets library (camera roll)
[self writeImageToSavedPhotosAlbum:image.CGImage
metadata:metadata
completionBlock:^(NSURL* assetURL, NSError* error) {
//error handling
if (error!=nil) {
completionBlock(error);
return;
}
//add the asset to the custom photo album
[self addAssetURL: assetURL
toAlbum:albumName
withCompletionBlock:completionBlock];
}
];
}
The image is coming from a UIImagePickerController that uses the camera. The picture is successfully being saved to the correct album, just missing the metadata.
Am I doing something wrong in the save/load process? Am I actually not allowed to save custom meta data to an image?
I did some testing, and from what I can tell, the short answer is 'you can't do that.' It looks like the metadata has to conform to specific EXIF Metadata keys. You could look up the available TIFF Metadata keys and see if there are any values you want to set/overwrite. You could try, for example, using kCGImagePropertyTIFFImageDescription to store your data.
NSMutableDictionary *tiffDictionary= [[NSMutableDictionary alloc] init];
NSMutableDictionary *myMetadata = [[NSMutableDictionary alloc] init];
[tiffDictionary setObject:#"My Metadata" forKey:(NSString*)kCGImagePropertyTIFFImageDescription];
[myMetadata setObject:tiffDictionary forKey:(NSString*)kCGImagePropertyTIFFDictionary];
... and save myMetadata with the image.
For other keys, see this:
http://developer.apple.com/library/mac/#documentation/GraphicsImaging/Reference/CGImageProperties_Reference/Reference/reference.html
Otherwise, what I would do is create an NSDictionary that uses an image's unique identifier as a key, and store the metadata object as the value. Save/Load this NSDictionary whenever you save/load an image.