最近はAssetCatalogにインストールすることが多いけど、そこの画像にアクセスする方法がよくわからないので、以前のようにXcodeのプロジェクトナビゲータの所に突っ込むこと。
書き編
//リソースにある画像を読み、メタデータとともにアプリ内のDocumentディレクトリに保存
- (void)writeImageWithMetadata {
//🌟準備としてリソース画像とmetadataを読む
//リソースディレクトリへのパスを得る
NSBundle *bundle = [NSBundle mainBundle];
NSString *resourcePath = [bundle resourcePath];
NSString *imgPath = [resourcePath stringByAppendingPathComponent:@"gazo.jpg"];
NSLog(@"リソースのpath %@",imgPath);
//リソースの画像とメタデータを得る
CGImageSourceRef sourceRef = CGImageSourceCreateWithURL((CFURLRef)CFBridgingRetain([NSURL fileURLWithPath:imgPath]), nil);
NSDictionary *metadata = (NSDictionary *) CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(sourceRef, 0, NULL));
//🌟画像とmetadataをDocumentディレクトリに保存
//Documentに保存するファイルのpathを作る
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *filePath = [documentPath stringByAppendingPathComponent:@"gazo.jpg"];
//書き込むための画像データを入れるNSMutableData型変数を作る
NSMutableData *imageData = [[NSMutableData alloc] init];
//書き込むための情報を入れる変数destRefに、書き込む画像データの変数(imageData)の情報と、データタイプ(JPEGとか?)、画像ファイル中の画像枚数(複数画像を持つフォーマットもあるため)を入れる
//sourceRefはデータタイプを得る関数で参照しているだけなので、ここでは画像そのものは加えられていない
CGImageDestinationRef destRef = CGImageDestinationCreateWithData((CFMutableDataRef)imageData, CGImageSourceGetType(sourceRef), 1, nil);
//引数1のdestRefに、引数2の画像と引数4のメタデータを追加する。引数3の0は複数画像がある場合の画像のインデックスらしい
CGImageDestinationAddImageFromSource(destRef, sourceRef, 0,(CFDictionaryRef)metadata);
//書き込むための情報destRefをファイナライズして完成
CGImageDestinationFinalize(destRef);
//画像データのimageDataの書き込みをすることで、destRefを参照してメタデータともども書き込んでくれるらしい
[imageData writeToFile:filePath atomically:YES];
//使用後は解放
CFRelease(sourceRef);
CFRelease(destRef);
}
読み編
//Documentディレクトリ内の画像を読み込んで表示&メタデータ取得して表示
- (void)readImageWithMetadata {
//Documentディレクトリにあるファイルのpath作成
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *filePath = [documentPath stringByAppendingPathComponent:@"gazo.jpg"];
//画像読み込み
NSError *err = nil;
NSData *imageData = [NSData dataWithContentsOfFile:filePath
options:NSDataReadingMappedAlways
error:&err];
//エラー処理するならこの辺で
NSLog(@"Error -> %@",err);
//NSDataからCIImage経由でUIImageに変換
CIImage *ciImage = [CIImage imageWithData:imageData];
UIImage *image = [UIImage imageWithCIImage:ciImage];
//こっから。UIImage作り直し開始
UIGraphicsBeginImageContext(image.size);
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//ここまで。UIImage作り直し終了
_myImageView.image = [UIImage imageWithData:imageData];
//メタデータ取得
//CGImageSourceCreateWithURLを使う場合
//Documentディレクトリ内の画像とメタデータを得る
CGImageSourceRef sourceRef = CGImageSourceCreateWithURL((CFURLRef)CFBridgingRetain([NSURL fileURLWithPath:filePath]), nil);
//sourceRefの情報からメタデータを取り出す
NSDictionary *metadata = (NSDictionary *)CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(sourceRef, 0, nil));
//使用後は解放
CFRelease(sourceRef);
//確認のためmetadata表示
NSLog(@"メタデータ %@",metadata);
//EXIF情報取り出し
NSDictionary *ExifDictionary = [metadata objectForKey:(NSString*)kCGImagePropertyExifDictionary];
NSLog(@"EXIFデータ %@",ExifDictionary);
}
各メソッド、関数について
NSBundleはアプリにインストールされてるリソースにアクセスするためのクラス。
resourcePathメソッドでリソースディレクトリへのpathが得られるので、ファイル名を追加。
CGImage〜Ref
CGImageSourceRef、CGImageDestinationRefは画像ファイルに付加されたEXIFなどの情報にアクセスするためのクラスで、ImageIO Frameworkが必要。
メタデータの中身にアクセス
metadataの辞書からEXIF情報を、キーを使って取り出し表示している。
ちなみにEXIFは入れ子になっている辞書なので、
float ss = [metadata[(NSString *)kCGImagePropertyExifDictionary][(NSString *)kCGImagePropertyExifExposureTime] floatValue];
などとやることで下の階層の情報にアクセスする。
CGImageDestinationCreateWithData
引数
consumer:書き込みデータのコンシューマー(何じゃそりゃ?)だそうだ。初期化しただけのNSMutableDataを指定してる。多分ここで指定した変数に画像データのsourceを書き込み、さらに結果をdestに返り値を返してるんだと思う。
type:データのタイプ(たとえばJPEGとかPNGとか)らしいが、この場合直接指定せずにCGImageSourceGetType関数でソース画像のタイプを得ている。
count:TIFFなどのように複数の画像を含む画像の場合、何枚あるのかを指定するが、1枚だけなら1でいい。
options:将来のための予約とあるので、nilでよし。
CGImageDestinationAddImageFromSource
引数
idst:画像のdestination
isrc:画像のソース
index:ソース画像で画像の位置を指定するインデックス(0オリジン)。多分複数画像を持つフォーマットの場合に、何枚目の画像かを指定するんじゃないかと思うが、よくわからん。とりあえず1枚だけのフォーマットなら0でいいかと。
properties:ソース画像のプロパティに上書きするか追加するか指定する辞書。プロパティのキーがkCFNullなら画像のdestinationは削除される。わかりづらいが、ここに辞書を指定してやればソース画像(source)+辞書をdestinationに追加してくれるらしい。
変数destは書き込む画像のデータ(imagaData)とメタデータ(metadata)の情報を参照していて、CGImageDestinationFinalizeでファイナライズした後にimagaDataの方をwriteToFileすると、メタデータ付きで書き込まれるのではないか?
最後にsourceとdestはreleaseしてやる必要があるようだ。
destに色々情報を追加しつつ、最後はimageDataを書き込んでるのがわかりづらい。imageDataを書き込むとdestに書かれた参照情報を見てmetadataも一緒に書き込まれるという理解でいいのか?
CGImageにすると向きの情報が消えることがある
読み書き時に止むを得ずCGImageに変換を要する場合があるが、UIImageでは持っている画像の向きの情報であるimageOrientationプロパティがないため、UIImageに復元しても縦長に撮った画像が90度横倒しに表示されてしまうことがあった。
その場合、
[UIImage imageWithCGImage: scale: orientation:]メソッドでorientationを指定しつつ復元してやるが、それでもうまくいかない場合は、
UIGraphicsBeginImageContext()
と
UIGraphicsEndImageContext()
に挟まれた間で、UIImageを作り直してやるといい。
引数1に引数2以降の情報を追加するという仕様や、情報を追加した変数(たとえばdestRef)じゃなく画像本体の方を書き込むとdestRefを参照してメタデータも書き込まれるとかはわかりづらい。
今回はリソースとDocumentディレクトリの間だったけど、イメージライブラリとの間だとALAssetLibraryを使って(最近はPhotosFrameworkなの?)また別のやり方になるはず。