準備
プロジェクトにメタデータの付いた画像をインストールしておく。(いきなりDocumentディレクトリに画像入れられないから)
名前は一応gazo.jpgで。
最近はAssetCatalogにインストールすることが多いけど、そこの画像にアクセスする方法がよくわからないので、以前のようにXcodeのプロジェクトナビゲータの所に突っ込むこと。
今回は以下の手順を取る。
- リソースとしてインストールされた画像gazo.jpgとメタデータを読み込み
- Documentディレクトリにメタデータとともに書き込み
- 画像とメタデータを読み込んで表示する
また、ImageIO.frameworkをインポートしとくこと。
#import <ImageIO/ImageIO.h>
書き編
//リソースにある画像を読み、メタデータとともにアプリ内の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が得られるので、ファイル名を追加。
resourcePathメソッドでリソースディレクトリへのpathが得られるので、ファイル名を追加。
CGImage〜Ref
CGImageSourceRef、CGImageDestinationRefは画像ファイルに付加されたEXIFなどの情報にアクセスするためのクラスで、ImageIO Frameworkが必要。- 画像ファイル読み込み時
- CGImageSourceRef
- 画像ファイル書き込み時
- CGImageDestinationRef
メタデータの中身にアクセス
metadataの辞書からEXIF情報を、キーを使って取り出し表示している。
ちなみにEXIFは入れ子になっている辞書なので、
などとやることで下の階層の情報にアクセスする。
ちなみにEXIFは入れ子になっている辞書なので、
float ss = [metadata[(NSString *)kCGImagePropertyExifDictionary][(NSString *)kCGImagePropertyExifExposureTime] floatValue];
CGImageDestinationCreateWithData
CGImageDestinationRef CGImageDestinationCreateWithDataConsumer ( CGDataConsumerRef consumer,CFStringRef type, size_t count, CFDictionaryRef options );
引数
consumer:書き込みデータのコンシューマー(何じゃそりゃ?)だそうだ。初期化しただけのNSMutableDataを指定してる。多分ここで指定した変数に画像データのsourceを書き込み、さらに結果をdestに返り値を返してるんだと思う。
consumer:書き込みデータのコンシューマー(何じゃそりゃ?)だそうだ。初期化しただけのNSMutableDataを指定してる。多分ここで指定した変数に画像データのsourceを書き込み、さらに結果をdestに返り値を返してるんだと思う。
type:データのタイプ(たとえばJPEGとかPNGとか)らしいが、この場合直接指定せずにCGImageSourceGetType関数でソース画像のタイプを得ている。
count:TIFFなどのように複数の画像を含む画像の場合、何枚あるのかを指定するが、1枚だけなら1でいい。
options:将来のための予約とあるので、nilでよし。
count:TIFFなどのように複数の画像を含む画像の場合、何枚あるのかを指定するが、1枚だけなら1でいい。
options:将来のための予約とあるので、nilでよし。
CGImageDestinationAddImageFromSource
void CGImageDestinationAddImageFromSource ( CGImageDestinationRef idst, CGImageSourceRef isrc, size_t index, CFDictionaryRef properties );
引数
idst:画像のdestination
isrc:画像のソース
index:ソース画像で画像の位置を指定するインデックス(0オリジン)。多分複数画像を持つフォーマットの場合に、何枚目の画像かを指定するんじゃないかと思うが、よくわからん。とりあえず1枚だけのフォーマットなら0でいいかと。
properties:ソース画像のプロパティに上書きするか追加するか指定する辞書。プロパティのキーがkCFNullなら画像のdestinationは削除される。わかりづらいが、ここに辞書を指定してやればソース画像(source)+辞書をdestinationに追加してくれるらしい。
変数destは書き込む画像のデータ(imagaData)とメタデータ(metadata)の情報を参照していて、CGImageDestinationFinalizeでファイナライズした後にimagaDataの方をwriteToFileすると、メタデータ付きで書き込まれるのではないか?
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を作り直してやるといい。
なにやらめんどくさいがしょうがない。
ちなみに前述のコード中、直接CGImageから変換したのではうまくいかなかったため、一度CIImageを経由した。
なにやら本当にめんどくさい。
謎のメタデータ追加メソッド
メタデータを追加するメソッドとしては
CGImageDestinationAddImageAndMetadata(CGImageDestinationRef idst, CGImageRef image, CGImageMetadataRef metadata, CFDictionaryRef options)
というのもあるようなのだが、APIリファレンス見てもちゃんとした説明が書いてないし、よくわからない。
釈然としないけど、CGImageDestinationAddImageFromSourceでうまくいくんならそれでいんじゃね?
アセットカタログのjsonファイル(Contents.json)を開くと、
{
"images" : [
{
"idiom" : "universal",
"filename" : "gazo2.jpg",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
などとなっており、アクセスにはファイル名にidiomやscaleのサフィックスを付けるというような情報を見たのだけど、サフィックスが合ってないのか、そもそも情報自体が間違いなのか、imgPathはnilになってしまい、ダメだった。
メタデータを取るためにアクセスするようなことがなければ、単にUIImageのファイル名で楽にアクセスできるのにねぇ。
アセットカタログ内の画像にアクセスしてみる
結論から言うとうまくいきませんでした。
NSString *imgSuffix = @"-universal-1x";
NSBundle *bundle = [NSBundle mainBundle];
//Assets Catalogのディレクトリへのパスを作る
NSString *imgPath = [bundle pathForResource:[NSStringstringWithFormat:@"%@%@",@"gazo2",imgSuffix] ofType:@"jpg"];
アセットカタログのjsonファイル(Contents.json)を開くと、
{
"images" : [
{
"idiom" : "universal",
"filename" : "gazo2.jpg",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
などとなっており、アクセスにはファイル名にidiomやscaleのサフィックスを付けるというような情報を見たのだけど、サフィックスが合ってないのか、そもそも情報自体が間違いなのか、imgPathはnilになってしまい、ダメだった。
メタデータを取るためにアクセスするようなことがなければ、単にUIImageのファイル名で楽にアクセスできるのにねぇ。
感想
CGImageSourceRefとCGImageDestinationRefがどういうものかよくわからず、ひたすら悩んで調べまくった。
今もよくわかってないけどね。
引数1に引数2以降の情報を追加するという仕様や、情報を追加した変数(たとえばdestRef)じゃなく画像本体の方を書き込むとdestRefを参照してメタデータも書き込まれるとかはわかりづらい。
今回はリソースとDocumentディレクトリの間だったけど、イメージライブラリとの間だとALAssetLibraryを使って(最近はPhotosFrameworkなの?)また別のやり方になるはず。
とにかくめんどっちー。
UIImageのプロパティとして簡単に扱えるようにならんもんか…。
0 件のコメント:
コメントを投稿