プログラミングノート

一からものを作ることが好きなエンジニアの開発ブログです。

Exifなどのメタデータを自由に操作するにはどうするか

Best Albumの開発でもっとも苦しめられたと言っても過言ではないのがExif操作。iOS4以降で自由にいじれるようになったとはいえ、あまりまとまった資料がなくて大変だったのでこれから作る人のために。(iOS4.2で動作確認をしています)

ライブラリ

ここでは下記のフレームワークを利用します。

#import <AssetsLibrary/AssetsLibrary.h>
#import <ImageIO/ImageIO.h>

フォトライブラリへの保存

ImagePickerControllerで撮影を行ってから保存する場合、通常の方法ではExif情報が保存されません。Exifを保存したい場合はiOS4.1から追加されたAssetsLibraryのメソッドを利用します。


まずはカメラ(or フォトライブラリ)の起動

UIImagePickerController *imgPicker = [[UIImagePickerController alloc] init];
imgPicker.delegate = self;
imgPicker.sourceType = UIImagePickerControllerSourceTypeCamera;
[self presentModalViewController:imgPicker animated:YES];  
[imgPicker release];


撮影後、オリジナルイメージとメタデータを取得できます。この時点で、オリジナルイメージにはメタデータは含まれていませんので、そのまま保存するとメタデータが落ちてしまいます。

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
  [picker dismissModalViewControllerAnimated:YES];
  
  // オリジナルイメージ
  UIImage *original = [info objectForKey:UIImagePickerControllerOriginalImage];
  
  // メタデータ
  NSMutableDictionary *metadata = (NSMutableDictionary *)[info objectForKey:UIImagePickerControllerMediaMetadata];  
  NSLog(@"%@", [metadata description]);

  // このメソッドを利用するとメタデータは保存されない
  UIImageWriteToSavedPhotosAlbum(original, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
}


メタデータも保存する場合は、AssetsLibraryを利用しましょう。

ALAssetsLibrary *lib = [[ALAssetsLibrary alloc] init];
[lib writeImageToSavedPhotosAlbum:original.CGImage
                                          metadata:metadata
                               completionBlock:^(NSURL* url, NSError* error){
                                 NSLog(@"Saved: %@<%@>", url, error);
                               }];
[lib release];

AssetsLibraryを利用したイメージデータの読み込み

AssetsLibraryを利用する場合は画像とメタデータが結合されたrawデータを取得可能です。独自のImagePickerを開発する場合にはこの方法が楽です。

  NSURL *url = [NSURL URLWithString:@"assets-library://asset/asset.JPG?id=1000000179&ext=JPG"];
  ALAssetsLibrary *lib = [[[ALAssetsLibrary alloc] init] autorelease];
  [lib assetForURL:url
     resultBlock:^(ALAsset *asset){
       ALAssetRepresentation *representation = [asset defaultRepresentation];      
       
       // raw data
       NSUInteger size = [representation size];
       uint8_t *buff = (uint8_t *)malloc(sizeof(uint8_t)*size);      
       if(buff != nil){
         NSError *error = nil;
         NSUInteger bytesRead = [representation getBytes:buff fromOffset:0 length:size error:&error];
         if (bytesRead && !error) {         	
           NSData *photo = [NSData dataWithBytesNoCopy:buff length:bytesRead freeWhenDone:YES];
         }
         if (error) {
           NSLog(@"error:%@", error);
           [error release];
           free(buff);
         }        
       }         
     }
    failureBlock:^(NSError *error){
      NSLog(@"error:%@", error);
    }];

イメージデータからメタデータを読む

上記AssetsLibraryで取得できたNSDataからメタデータを読みたい場合は、Image I/Oフレームワークを利用して読むことができます。

CGImageSourceRef cgImage = CGImageSourceCreateWithData((CFDataRef)photo, nil);           
NSDictionary *metadata = (NSDictionary *)CGImageSourceCopyPropertiesAtIndex(cgImage, 0, nil);
if (metadata) {
  NSLog(@"%@", [metadata description]);
} else {
  NSLog(@"no metadata");
}
[metadata release];
CFRelease(cgImage);

イメージデータとメタデータの結合

フォトライブラリ保存や、rawデータの取得以外の方法で画像とメタデータを結合する場合も Image I/O を利用すれば実現できます。
(kUTTypeJPEGはMobileCoreServices/UTCoreTypes.hで定義されています)

NSMutableData *concatData = [[NSMutableData alloc] init];
CGImageDestinationRef dest = CGImageDestinationCreateWithData((CFMutableDataRef)concatData, kUTTypeJPEG, 1, nil);
CGImageDestinationAddImage(dest, original.CGImage, (CFDictionaryRef)metadata);
CGImageDestinationFinalize(dest);
CFRelease(dest);


メタデータの書き換えはDictionaryを直接変更すればOKです。
これだけ押さえておけば大抵の処理は大丈夫でしょう。