[iOS]UIImagePickerControllerで撮影した写真にExif・位置情報を書き込んで保存する方法

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

UIImagePickerControllerで撮影した写真にExif・位置情報を保存したい

iOSアプリで静止画の撮影を実装する際には、UIImagePickerControllerを利用するのが一番手軽です。しかし、UIImagePickerControllerで撮影した写真をUIImageJPEGRepresentation関数でjpegに変換して保存するだけではExif・位置情報が保存されません。iOS標準のカメラアプリで撮影した写真にはExif・位置情報が保存されているので、同じように保存したい場合もあると思います。そういった場合は静止画の保存時に情報を書き込む必要があります。

ios-imagepicker-exifgps01

静止画のメタデータ

UIImagePickerControllerでの静止画撮影後、UIImagePickerControllerDelegateプロトコルのimagePickerController:didFinishPickingMediaWithInfo:が呼び出されますが、このデリゲートメソッドの2番目のパラメータはNSDictionary型のオブジェクトで、UIImagePickerControllerMediaMetadataキーに静止画のメタデータを保持しています。このオブジェクトには、Exif情報や位置情報など様々な情報を格納します。このDictionaryのキーとして以下の定数がImageIO.frameworkに定義されています。

IMAGEIO_EXTERN const CFStringRef kCGImagePropertyTIFFDictionary  IMAGEIO_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_4_0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyGIFDictionary  IMAGEIO_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_4_0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyJFIFDictionary  IMAGEIO_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_4_0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyExifDictionary  IMAGEIO_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_4_0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyPNGDictionary  IMAGEIO_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_4_0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyIPTCDictionary  IMAGEIO_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_4_0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyGPSDictionary  IMAGEIO_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_4_0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyRawDictionary  IMAGEIO_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_4_0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyCIFFDictionary  IMAGEIO_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_4_0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyMakerCanonDictionary  IMAGEIO_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_4_0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyMakerNikonDictionary  IMAGEIO_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_4_0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyMakerMinoltaDictionary  IMAGEIO_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_4_0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyMakerFujiDictionary  IMAGEIO_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_4_0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyMakerOlympusDictionary  IMAGEIO_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_4_0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyMakerPentaxDictionary  IMAGEIO_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_4_0);
IMAGEIO_EXTERN const CFStringRef kCGImageProperty8BIMDictionary  IMAGEIO_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_4_0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyDNGDictionary  IMAGEIO_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_4_0);
IMAGEIO_EXTERN const CFStringRef kCGImagePropertyExifAuxDictionary  IMAGEIO_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_4_0);

この中で、Exif情報のキーはkCGImagePropertyExifDictionary、位置情報のキーはkCGImagePropertyGPSDictionaryとして定義されていますので、先程のメタデータのDictonaryに対してこれらのキーを利用してExif・位置情報の編集ができます。

Exif情報を保存する

UIImagePickerControllerでの撮影後、静止画のメタデータのDictionaryの中にはすでにExif情報がNSDictionary型のオブジェクトとして格納されています。この情報を静止画の保存時に一緒に書き込むことで、Exif情報が保存された静止画データファイルを作成することができます。

Exif情報のDictionaryで利用できるキーについては、CGImageProperties Referenceを参照してください。

静止画をファイルシステムに保存する場合

静止画をファイルシステムに保存する場合はExif情報を含む静止画データを生成した後、ファイルシステムに書き出します。

Exif情報を含む静止画データの作成

下記は、UIImageとExif情報を含むメタデータのDictinaryからメタデータ付きの静止画データをNSData型で生成する処理です。

- (NSData *)createImageDataFromImage:(UIImage *)image metaData:(NSDictionary *)metadata
{
    // メタデータ付きの静止画データの格納先を用意する
    NSMutableData *imageData = [NSMutableData new];
    // imageDataにjpegで1枚画像を書き込む設定のCGImageDestinationRefを作成する
    CGImageDestinationRef dest = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, kUTTypeJPEG, 1, NULL);
    // 作成したCGImageDestinationRefに静止画データとメタデータを追加する
    CGImageDestinationAddImage(dest, image.CGImage, (__bridge CFDictionaryRef)metadata);
    // メタデータ付きの静止画データの作成を実行する
    CGImageDestinationFinalize(dest);
    // CGImageDestinationRefを解放する
    CFRelease(dest);

    return imageData;
}

この処理にはImageIO.frameworkを利用しています。また、kUTTypeJPEGという定数はMobileCoreServices.frameworkで定義されています。XcodeプロジェクトにImageIO.frameworkとMobileCoreServices.frameworkを追加して下記のヘッダをインポートして下さい。

#import <ImageIO/ImageIO.h>
#import <MobileCoreServices/UTCoreTypes.h>

処理としては、ImageIOのCGImageDestinationRefを利用して、NSMutableDataに静止画の画像データとExif情報を含んだメタデータを書き込んでいるだけです。

静止画データをファイルシステムに保存する

あとは、作成した静止画データをファイルシステムに保存するだけです。下記のコードは、静止画撮影後に呼ばれるデリゲートメソッド内で、メタデータの取り出しから静止画データの保存までの処理を行っています。

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    // 静止画の参照を取得
    UIImage *image = info[UIImagePickerControllerOriginalImage];

    // メタデータの参照を取得
    NSMutableDictionary *metadata = info[UIImagePickerControllerMediaMetadata];
    // Exifの参照を取得
    NSMutableDictionary *exif = metadata[(NSString *)kCGImagePropertyExifDictionary];

    // Exifなどのメタデータを含む静止画データを作成
    NSData *imageData = [self createImageDataFromImage:image metaData:metadata];

    // 撮影日時からファイル名を生成して、Documentディレクトリに保存
    NSString *fileName = [self fileNameByExif:exif];
    [self storeFileAtDocumentDirectoryForData:imageData fileName:fileName];

    [self dismissViewControllerAnimated:YES completion:nil];
}

Exif情報は静止画データ保存時のファイル名を生成するために参照を取得していますが、そうでないならアクセスする必要はありません。

保存された静止画ファイルの情報を見ると、Exif情報が保存されていることが確認できます。

ios-imagepicker-exifgps02

静止画をカメラロールに保存する場合

静止画をカメラロールに保存する場合は、AssetsLibrary.frameworkを利用します。XcodeプロジェクトにAssetsLibrary.frameworkを追加して下記のヘッダをインポートして下さい。

#import <AssetsLibrary/AssetsLibrary.h>

AssetsLibraryを利用する場合は、カメラロールへの書き込み処理時にメタデータを渡すとその情報を含んだ静止画ファイルを保存してくれます。したがって、静止画データにメタデータを書き込む処理を記述する必要がありません。

下記コードは、メタデータを含んだ静止画をカメラロールに保存する処理です。

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    UIImage *image = info[UIImagePickerControllerOriginalImage];

    NSMutableDictionary *metadata = info[UIImagePickerControllerMediaMetadata];

    ALAssetsLibrary *assetsLibrary = [[ALAssetsLibrary alloc] init];
    [assetsLibrary writeImageToSavedPhotosAlbum:image.CGImage metadata:metadata completionBlock:^(NSURL *assetURL, NSError *error) {
        if (error) {
            NSLog(@"Save image failed. %@", error);
        }
    }];

    [self dismissViewControllerAnimated:YES completion:nil];
}

ALAssetsLibraryのwriteImageToSavedPhotosAlbum:metadata:completionBlock:メソッドに静止画のUIImageとメタデータのNSMutableDictionaryを渡すだけです。

静止画の保存時にExif情報を書き換える

静止画の保存処理前にExif情報のDictionaryの内容を変更しておくことで、静止画ファイルに保存されるExif情報を書き換えることができます。下記のコードのようにExif情報を保持するDictionaryの対応するキーの値を書き換えるだけでOKです。kCGImagePropertyExifDictionaryはExifのコメントフィールドを表すキーの定数で、コメントに適当な文字列を追記しています。

NSMutableDictionary *exif = metadata[(NSString *)kCGImagePropertyExifDictionary];
exif[(NSString *)kCGImagePropertyExifUserComment] = @"hoge";

ios-imagepicker-exifgps03

位置情報を保存する

位置情報はExif情報とは異なり、UIImagePickerControllerでの撮影時に情報が取得されてません。このため、CoreLocation.frameworkを利用して位置情報を取得した上で、静止画の保存時に一緒に書き込む必要があります。

位置情報はExif情報と同様に静止画メタデータのDictionaryオブジェクト内にNSDictionary型のオブジェクトとして格納します。位置情報のDictionaryで利用できるキーについては、CGImageProperties Referenceを参照してください。

位置情報オブジェクトを作成する

下記のコードは、CLLocationから静止画のメタデータにセットする位置情報のDictionaryを作成する処理です。

- (NSDictionary *)GPSDictionaryForLocation:(CLLocation *)location
{
    NSMutableDictionary *gps = [NSMutableDictionary new];

    // 日付
    gps[(NSString *)kCGImagePropertyGPSDateStamp] = [[FormatterUtil GPSDateFormatter] stringFromDate:location.timestamp];
    // タイムスタンプ
    gps[(NSString *)kCGImagePropertyGPSTimeStamp] = [[FormatterUtil GPSTimeFormatter] stringFromDate:location.timestamp];

    // 緯度
    CGFloat latitude = location.coordinate.latitude;
    NSString *gpsLatitudeRef;
    if (latitude < 0) {
        latitude = -latitude;
        gpsLatitudeRef = @"S";
    } else {
        gpsLatitudeRef = @"N";
    }
    gps[(NSString *)kCGImagePropertyGPSLatitudeRef] = gpsLatitudeRef;
    gps[(NSString *)kCGImagePropertyGPSLatitude] = @(latitude);

    // 経度
    CGFloat longitude = location.coordinate.longitude;
    NSString *gpsLongitudeRef;
    if (longitude < 0) {
        longitude = -longitude;
        gpsLongitudeRef = @"W";
    } else {
        gpsLongitudeRef = @"E";
    }
    gps[(NSString *)kCGImagePropertyGPSLongitudeRef] = gpsLongitudeRef;
    gps[(NSString *)kCGImagePropertyGPSLongitude] = @(longitude);

    // 標高
    CGFloat altitude = location.altitude;
    if (!isnan(altitude)){
        NSString *gpsAltitudeRef;
        if (altitude < 0) {
            altitude = -altitude;
            gpsAltitudeRef = @"1";
        } else {
            gpsAltitudeRef = @"0";
        }
        gps[(NSString *)kCGImagePropertyGPSAltitudeRef] = gpsAltitudeRef;
        gps[(NSString *)kCGImagePropertyGPSAltitude] = @(altitude);
    }

    return gps;
}

CLLocationから、日付・タイプスタンプ・緯度・経度・標高の情報を取得してDictionaryにセットしています。

静止画のメタデータに位置情報を追加する

静止画のメタデータに位置情報を追加する処理は、下記コードのようにメタデータにkCGImagePropertyGPSDictionaryキーで先程作成した位置情報オブジェクトをセットするだけです。

metadata[(NSString *)kCGImagePropertyGPSDictionary] = [self GPSDictionaryForLocation:self.locationManager.location];

あとは、Exif情報保存の場合と同じ手順で静止画ファイルを保存するだけです。これで、UIImagePickerControllerから撮影した静止画に位置情報を保存することができるようになります。保存したファイルの情報を見ると、位置情報が保存されていることが確認できます。

ios-imagepicker-exifgps04

Exifと位置情報の日付データ

Exifに保存する日付と位置情報に保存する日付のフォーマットは下記の通りです。

Exif 位置情報(日付) 位置情報(タイムスタンプ)
yyyy:MM:dd HH:mm:ss yyyy:MM:dd HH:mm:ss

ただし、現行のExifにはタイムゾーンの概念がないようなのでシステムのタイムゾーンの時間で保存しますが、位置情報は必ずUTCで保存することになっていますので注意して下さい。

UIImagePickerControllerで撮影した静止画に、Exif・位置情報を保存するサンプルアプリを作成してGitHubにアップロードしていますので、そちらも参考にして下さい。

参考サイト

[iPhoneプログラミング]JPEGファイルのExif情報を読み書きする

Saving Geotag info with photo on iOS4.1

CGImageProperties Reference