📜 ⬆️ ⬇️

UIImage, EXIF ​​and a bit of runtime

image

For owners of iOS-devices, there is a huge number of web-services that provide the ability to publish photos on their resources. There is no need to go long for examples. These are the social networks VKontakte, Facebook - services, if you can put it, of a wide profile, applications of which are installed by almost all users. So are highly specialized, for example, FourSquare, Path.

There are plenty of such services and for many of them there is an open API, with the help of which third-party developers (and this we are with you) can implement applications or their individual parts interacting with the service. It is quite easy to write code that takes pictures from photo albums or makes a new one. Consider the first option.

In your controller's .h file:
@interface MYViewController : UIViewController<UIImagePickerControllerDelegate, UINavigationControllerDelegate> ... @end 

')
In your controller's .m file:
 @implementation MYViewController - (IBAction)pickupImage:(id)sender { UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init]; imagePicker.delegate = self; imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; [self presentModalViewController:imagePicker animated:YES]; [imagePicker release]; } #pragma mark - UIImagePickerControllerDelegate - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage]; //   UIImage  // [serviceAPI postWithText:@" !" withImage:image]; [picker dismissModalViewControllerAnimated:YES]; } @end 


Something like this. It's simple, you have already done this or just read about it in books. Everything works well until one day the web service starts processing the EXIF ​​data of the images. For example, it starts displaying the coordinates of the shooting location and the type of camera on which the photo was taken, the orientation of the photo under each photo. And it turns out that you do not send any EXIF ​​data.

In the name of the constant UIImagePickerControllerOriginalImage Original is not quite Original. It is original only from the point of view that the entire image is given to you from point (0,0) to point ( width -1, height -1), since it is possible to cut it before the delegate method is called, which is done by setting the flag
 imagePicker.allowsEditing = YES 

It will look like this on the device:
image

And so, now we need to change the code so that we can access the source file of the photo, which contains meta information in addition to the pixels. The UIImagePickerControllerReferenceURL key and the AssetsLibrary framework come to the rescue .

Starting with iOS SDK 4.1, the info parameter, which comes to us in the delegate method, contains the value by the key UIImagePickerControllerReferenceURL in the case when we access photos from albums. The value for this key is a link to an asset, a file that we need.

The code of the delegate method will look like this:
 - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage]; NSURL *assetURL = [info objectForKey:UIImagePickerControllerReferenceURL]; ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; [library assetForURL:assetURL resultBlock:^(ALAsset *asset) { CLLocation *location = [asset valueForProperty:ALAssetPropertyLocation]; // [serviceAPI postWithText:@" !" withImage:image withMetadata:...]; [library autorelease]; } failureBlock:^(NSError *error) { }]; [picker dismissModalViewControllerAnimated:YES]; } 


CLLocation *location = [asset valueForProperty:ALAssetPropertyLocation]; - here we turn to the geography of the photo. In the file <AssetsLibrary / ALAsset.h> we can view the other keys available to developers.

Hereinafter we consider geo-marks, work with the rest of the data is carried out by analogy.

As you understand, ultimately we will make a POST request with a binary representation of the image, which we will get by the UIImageJPEGRepresentation method, this data will need to be supplemented with meta-information. We are helped by the iphone-exif library, which will take us to a higher level of abstraction when working with exif.

The code that adds to the binary representation of the image, geotagging looks like this:

 #import "EXF.h" #import "EXFUtils.h" + (NSData *)populateData:(NSData *)data byLocation:(CLLocation *)location { EXFJpeg *exfJpeg = [[[EXFJpeg alloc] init] autorelease]; [exfJpeg scanImageData:data]; //  ,      //     EXFGPSLoc* gpsLocLatitude = [[[EXFGPSLoc alloc] init] autorelease]; [self populateGPS:gpsLocLatitude byValue:[self locationArrayForValue:location.coordinate.latitude]]; [exfJpeg.exifMetaData addTagValue:gpsLocLatitude forKey:[NSNumber numberWithInt:EXIF_GPSLatitude]]; //     EXFGPSLoc* gpsLocLongitude = [[[EXFGPSLoc alloc] init] autorelease]; [self populateGPS:gpsLocLongitude byValue:[self locationArrayForValue:location.coordinate.longitude]]; [exfJpeg.exifMetaData addTagValue:gpsLocLongitude forKey:[NSNumber numberWithInt:EXIF_GPSLongitude]]; //  ""  NSString *refLatitude = (location.coordinate.latitude < 0 ? @"S" : @"N"); [exfJpeg.exifMetaData addTagValue:refLatitude forKey:[NSNumber numberWithInt:EXIF_GPSLatitudeRef]]; //  ""  NSString *refLongitude = (location.coordinate.longitude < 0 ? @"W" : @"E"); [exfJpeg.exifMetaData addTagValue:refLongitude forKey:[NSNumber numberWithInt:EXIF_GPSLongitudeRef]]; NSMutableData *dataWithExif = [NSMutableData data]; [exfJpeg populateImageData:dataWithExif]; return dataWithExif; } 


It would seem that we are one step away from success. We add metadata to NSData, and then, doing a wrap operation, we get a UIImage. And we don’t even have to change the signatures of methods that accept only a UIImage without any hints on exif.

Alas, this will not work. And that's why.

First , unfortunately, methods like [UIImage imageWithData: data] lose all the metadata (geo-location, in our case) that is stored in NSData. So, it will not be possible to keep the coordinates right “inside” the binary representation of the image at the UIImage level.

Secondly , you can do various manipulations with a picture, say, reduce its size. This is usually done using CoreGraphics functions that do not know exactly about any metadata.

Output

So, we will have to accompany the image and its metadata along the entire path from receiving it until we receive an object of the NSData class and form a POST request to the server. And we'd better do everything so that we don’t have to change all signatures of methods and functions along the way due to such maintenance (UIImage + NSData-Exif).

It would be possible, of course, to create an object of the NSMutableDictionary class, which would store the correspondence between UIImage and NSData. But this is to nothing, such a link can be added directly to the UIImage.

Associated objects

Starting with iOS 4.0, the ability to add object associations was added to the language runtime - a connection that you establish while the program is running. Read more here . Method declarations can be viewed in <objc / runtime.h>
It works like this:

The association can be established by calling the function:
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)

The values ​​of the last argument will be familiar to you by the attributes of the ObjC classes' properties:
  /* objc_setAssociatedObject() options */ enum { OBJC_ASSOCIATION_ASSIGN = 0, OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, OBJC_ASSOCIATION_COPY_NONATOMIC = 3, OBJC_ASSOCIATION_RETAIN = 01401, OBJC_ASSOCIATION_COPY = 01403 }; typedef uintptr_t objc_AssociationPolicy; 

A pointer to an object can be obtained by the method:
id objc_getAssociatedObject(id object, void *key)
So after getting the UIImage object using ALAssetsLibrary, we will need to do the following:

  #import <objc/runtime.h> // -   .      char kUIImageExifKey; ... objc_setAssociatedObject(<#image#>, &kUIImageExifKey, <#DATA#>, OBJC_ASSOCIATION_RETAIN); ... 


The path of the UIImage and metadata will be as follows

1) Get a photo and metadata to it;
2) to associate these two objects;
3) With each update of the image, keep the association with the most relevant UIImage object (the image can be reduced, a filter is applied, etc.).
4) Immediately before sending binary data, use the iphone-exif library.

It's all.

If there are requests, it will be possible to review the work with the photo just taken and place it into a “combat” code, and put it on github.
image

Source: https://habr.com/ru/post/148837/


All Articles