UIImage、EXIF、および少しのランタイム

画像

iOSデバイスの所有者には、リソースに写真を公開する機能を提供する膨大な数のWebサービスがあります。 長い間、例に行く必要はありません。 このソーシャルネットワークVKontakte、Facebook-サービス、いわば、幅広いプロファイル、ほぼすべてのユーザーにインストールされるアプリケーション。 たとえば、FourSquare、Pathなど、高度に専門化されています。

そのようなサービスは多数あり、それらの多くには、サードパーティの開発者(およびこれが私たち)がアプリケーションまたはサービスと対話する個々の部分を実装できるオープンAPIがあります。 フォトアルバムから写真を撮影するコードを作成するか、新しい写真を作成するのは非常に簡単です。 最初のオプションを検討してください。

コントローラーの.hファイルで:
@interface MYViewController : UIViewController<UIImagePickerControllerDelegate, UINavigationControllerDelegate> ... @end 


コントローラーの.mファイルで:
 @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 


そのようなもの。 簡単です。すでにこれを行っているか、本で読んでいるだけです。 WebサービスがEXIF画像データの処理を開始する晴れた日まで、すべてがうまく機能します。 たとえば、撮影場所の座標と、写真が撮影されたカメラの種類、各写真の下の写真の向きの表示を開始します。 そして、EXIFデータを送信していないことがわかります。

定数UIImagePickerControllerOriginalImage Originalの名前はUIImagePickerControllerOriginalImage オリジナルではありません。 デリゲートメソッドが呼び出される前に切り取ることが可能であるため、フラグ(フラグを設定することで行われます)
 imagePicker.allowsEditing = YES 

デバイス上では次のようになります。
画像

そのため、ピクセルに加えてメタ情報を含む元の写真ファイルにアクセスできるようにコードを変更する必要があります。 UIImagePickerControllerReferenceURLキーとAssetsLibraryフレームワークが助けになります。

iOS SDK 4.1以降、デリゲートメソッドで提供されるinfoパラメーターには、アルバムの写真にアクセスする場合のキーUIImagePickerControllerReferenceURLの値が含まれます。 このキーの値は、必要なファイルであるアセットへのリンクです。

デリゲートメソッドのコードは次のようになります。
 - (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]; -ここでは、写真のジオロケーションに目を向けます。 ファイル<AssetsLibrary / ALAsset.h>には、開発者が利用できる残りのキーがあります。

ここでさらにジオマーキングを検討し、残りのデータの操作は類推によって実行されます。

最終的に、サービス側で理解しているように、 UIImageJPEGRepresentationメソッドを使用して受信する画像のバイナリ表現を使用してPOSTリクエストを作成します。これらのデータはメタ情報で補足する必要があります。 iphone-exifライブラリーが役に立ちます。これにより、exifで作業するときに、より高いレベルの抽象化が可能になります。

画像のバイナリ表現に追加するコード、ジオロケーションは次のようになります。

 #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; } 


私たちは成功から一歩離れているように思えます。 NSDataにメタデータを追加してから、逆の操作を行うと、UIImageを取得します。 また、exifのヒントなしでUIImageのみを受け入れるメソッドのシグネチャを変更する必要もありません。

残念ながら、これは機能しません。 そして、ここに理由があります。

まず 、残念ながら、 [UIImage imageWithData:data]のようなメソッドは、NSDataに格納されているすべてのメタデータ(この場合は位置)を失います。 そのため、UIImageレベルで画像のバイナリ表現の座標を直接「内側」に保つことは不可能です。

第二に 、画像のさまざまな操作、たとえばサイズの縮小を行うことができます。 これは通常、メタデータを正確に知らないCoreGraphics機能を使用して行われます。

出口

NSDataクラスのオブジェクトを受信して​​サーバーへのPOSTリクエストを作成するまで、画像とそのメタデータをパス全体に沿って添付する必要があることがわかります。 そして、このようなサポート(UIImage + NSData-Exif)により、このパスに沿ってメソッドと関数のすべてのシグネチャを変更する必要がないように、すべてを実行する方が良いでしょう。

もちろん、UIImageとNSData間の対応を維持するクラスNSMutableDictionaryのオブジェクトを作成できます。 しかし、これは役に立ちません。そのような接続はUIImageに直接追加できます。

関連オブジェクト

iOS 4.0以降、オブジェクトの関連付け(プログラムの実行中に確立する接続)を言語ランタイムに追加する機能が追加されました。 詳細はこちら 。 メソッド宣言は<objc / runtime.h>で確認できます
次のように機能します。

関連付けは、関数を呼び出すことで設定できます。
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)

最後の引数の値は、ObjCクラスのプロパティの属性に慣れ親しんでいます。
  /* 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; 

オブジェクトへのポインタは、次の方法で取得できます。
id objc_getAssociatedObject(id object, void *key)
ALAssetsLibraryを使用してUIImageオブジェクトを受け取った後、次のことを行う必要があります。

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


UIImageとメタデータのパスは次のようになります

1)写真とメタデータを取得します。
2)これら2つのオブジェクトを関連付けます。
3)画像が更新されるたびに、最も関連性のあるUIImageオブジェクトとの関連付けを維持します(画像を縮小したり、フィルターを適用したりするなど)。
4)バイナリデータを送信する直前に、iphone-exifライブラリを使用します。

以上です。

リクエストがある場合は、撮影したばかりの写真を使用して「戦闘」コードに配置し、githubに配置することを検討できます。
画像

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


All Articles