私は最近、.NETプラットフォーム、C#言語、およびWindows Presentation Foundationに精通することにしました。
勉強の過程で(そしてパイロットプロジェクトの開発過程で常に言語と技術を勉強します)、私はかなりの落とし穴と微妙な点に出会いました。 誰もがhabrasocietyと共有したい(多くの新しいWPF開発者が望んでいると思う)が、結果のhabratopikのボリュームが大きすぎるので、イメージメタデータから始めることにしました。 このテーマに関する情報は、英語を話すインターネットでも十分ではありません。
一般に、さまざまな形式の画像にメタデータを含めることができますが、例としてJPEGについて説明します。 彼と働いた。 他の形式の場合、違いは小さいと思います。
メタデータの種類
最初に、画像に含まれるメタデータのタイプを確認しましょう。 おそらく誰もがこれを知っていますが、念のために、私はあなたに教えます:
- EXIF(Exchangeable Image File Format)は、メタデータを画像に保存するための標準であり、シャッター速度、絞り、およびその他の撮影パラメーターに関する情報を保存するためにデジタルカメラで使用されます。 EXIFメタデータは、JPEG、TIFF、およびRIFF WAVファイルに保存できます。 標準に従って、説明(説明タグ)とコメント(ユーザーコメントタグ)のみがユーザー記述メタデータからEXIFに保存できますが、Windowsエクスプローラーはいくつかの追加タグ(XPTitle、XPSubject、XPAuthor、XPComment、XPKeywords)も使用します。 Windowsエクスプローラーは、標準のDescriptionタグを持つXPTitleタグを無視します。
- IPTC(International Press Telecommunications Council)-むしろ標準を開発した組織の名前。 メタデータ標準自体はIIM(情報交換モデル)と呼ばれます。 説明されている最も古い規格。 標準の初期バージョンでは、メタデータが保存されていたため、IPTCの存在を認識していないソフトウェアは、そのようなメタデータを含む画像ファイルを処理できませんでした。 ただし、アドビは後にメタデータをJPEGファイルのAPP13ブロックに転送することで標準を拡張し、未知のメタデータを無視して、標準を知らないソフトウェアがJPEGファイルを正常に読み取れるようにしました。 ObjectName(タイトル)、キーワード(キーワード)、キャプション(説明、複数のタグバリエーション)などの説明フィールドをIPTCメタデータに保存できます。
- XMP(eXtensible Metadata Platform)は、アドビが開発した標準です。 メタデータはRDFモデルに格納され、XML形式で提示されるため、必要な情報を画像ファイルに含めることができます。 Windows Vista / 7でWIC(Windows Imaging Component)を使用することを好むのは、この形式です。
WPFメタデータの原則
WPFでメタデータを操作するには、BitmapEncoder、BitmapDecoder、BitmapSource、BitmapFrame、BitmapMetadata、InPlaceMetadataWriterクラスが使用されます。
BitmapEncoderクラスとBitmapDecoderクラスには、特定の画像形式で作業できる子孫があります。 私の場合、JpegBitmapEncoderとJpegBitmapDecoder。
InPlaceMetadataWriterクラスは、ファイルをトランスコードせずに、メタデータを直接インプレースで変更するために使用されます。
データの読み取りと書き込みには2つの方法があります。階層メタデータタグ名を操作するGetQuery / SetQuery関数を使用するか、BitmapMetadataクラスのフィールドを使用してメタデータに簡単にアクセスします。
BitmapMetadataクラスのフィールドを介してメタデータにアクセスする場合、WICは、異なる標準のメタデータで、最初のXMP、次にIPTCおよびEXIFの順序で対応するフィールドを見つけようとします。 BitmapMetadataクラスのフィールドを介してタグを記述する場合、WICはそれらをXMP形式で書き込みます。
メタデータの読み取り
次に、ファイルからメタデータを読み取ることができる既製の関数の例を示します。
FileStream f = File .Open( "test.jpg" , FileMode.Open); BitmapDecoder decoder = JpegBitmapDecoder.Create(f, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.Default); BitmapMetadata metadata = (BitmapMetadata)decoder.Frames[ 0 ].Metadata; // string title = metadata.Title; // XMP string xmptitle = ( string )metadata.GetQuery( @"/xmp/<xmpalt>dc:title" ); // EXIF string exiftitle = ( string )metadata.GetQuery( @"/app1/ifd/{ushort=40091}" ); // IPTC string iptctitle = ( string )metadata.GetQuery( @"/app13/irb/8bimiptc/iptc/object name" );
FileStream f = File .Open( "test.jpg" , FileMode.Open); BitmapDecoder decoder = JpegBitmapDecoder.Create(f, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.Default); BitmapMetadata metadata = (BitmapMetadata)decoder.Frames[ 0 ].Metadata; // string title = metadata.Title; // XMP string xmptitle = ( string )metadata.GetQuery( @"/xmp/<xmpalt>dc:title" ); // EXIF string exiftitle = ( string )metadata.GetQuery( @"/app1/ifd/{ushort=40091}" ); // IPTC string iptctitle = ( string )metadata.GetQuery( @"/app13/irb/8bimiptc/iptc/object name" );
FileStream f = File .Open( "test.jpg" , FileMode.Open); BitmapDecoder decoder = JpegBitmapDecoder.Create(f, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.Default); BitmapMetadata metadata = (BitmapMetadata)decoder.Frames[ 0 ].Metadata; // string title = metadata.Title; // XMP string xmptitle = ( string )metadata.GetQuery( @"/xmp/<xmpalt>dc:title" ); // EXIF string exiftitle = ( string )metadata.GetQuery( @"/app1/ifd/{ushort=40091}" ); // IPTC string iptctitle = ( string )metadata.GetQuery( @"/app13/irb/8bimiptc/iptc/object name" );
FileStream f = File .Open( "test.jpg" , FileMode.Open); BitmapDecoder decoder = JpegBitmapDecoder.Create(f, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.Default); BitmapMetadata metadata = (BitmapMetadata)decoder.Frames[ 0 ].Metadata; // string title = metadata.Title; // XMP string xmptitle = ( string )metadata.GetQuery( @"/xmp/<xmpalt>dc:title" ); // EXIF string exiftitle = ( string )metadata.GetQuery( @"/app1/ifd/{ushort=40091}" ); // IPTC string iptctitle = ( string )metadata.GetQuery( @"/app13/irb/8bimiptc/iptc/object name" );
FileStream f = File .Open( "test.jpg" , FileMode.Open); BitmapDecoder decoder = JpegBitmapDecoder.Create(f, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.Default); BitmapMetadata metadata = (BitmapMetadata)decoder.Frames[ 0 ].Metadata; // string title = metadata.Title; // XMP string xmptitle = ( string )metadata.GetQuery( @"/xmp/<xmpalt>dc:title" ); // EXIF string exiftitle = ( string )metadata.GetQuery( @"/app1/ifd/{ushort=40091}" ); // IPTC string iptctitle = ( string )metadata.GetQuery( @"/app13/irb/8bimiptc/iptc/object name" );
FileStream f = File .Open( "test.jpg" , FileMode.Open); BitmapDecoder decoder = JpegBitmapDecoder.Create(f, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.Default); BitmapMetadata metadata = (BitmapMetadata)decoder.Frames[ 0 ].Metadata; // string title = metadata.Title; // XMP string xmptitle = ( string )metadata.GetQuery( @"/xmp/<xmpalt>dc:title" ); // EXIF string exiftitle = ( string )metadata.GetQuery( @"/app1/ifd/{ushort=40091}" ); // IPTC string iptctitle = ( string )metadata.GetQuery( @"/app13/irb/8bimiptc/iptc/object name" );
FileStream f = File .Open( "test.jpg" , FileMode.Open); BitmapDecoder decoder = JpegBitmapDecoder.Create(f, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.Default); BitmapMetadata metadata = (BitmapMetadata)decoder.Frames[ 0 ].Metadata; // string title = metadata.Title; // XMP string xmptitle = ( string )metadata.GetQuery( @"/xmp/<xmpalt>dc:title" ); // EXIF string exiftitle = ( string )metadata.GetQuery( @"/app1/ifd/{ushort=40091}" ); // IPTC string iptctitle = ( string )metadata.GetQuery( @"/app13/irb/8bimiptc/iptc/object name" );
FileStream f = File .Open( "test.jpg" , FileMode.Open); BitmapDecoder decoder = JpegBitmapDecoder.Create(f, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.Default); BitmapMetadata metadata = (BitmapMetadata)decoder.Frames[ 0 ].Metadata; // string title = metadata.Title; // XMP string xmptitle = ( string )metadata.GetQuery( @"/xmp/<xmpalt>dc:title" ); // EXIF string exiftitle = ( string )metadata.GetQuery( @"/app1/ifd/{ushort=40091}" ); // IPTC string iptctitle = ( string )metadata.GetQuery( @"/app13/irb/8bimiptc/iptc/object name" );
FileStream f = File .Open( "test.jpg" , FileMode.Open); BitmapDecoder decoder = JpegBitmapDecoder.Create(f, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.Default); BitmapMetadata metadata = (BitmapMetadata)decoder.Frames[ 0 ].Metadata; // string title = metadata.Title; // XMP string xmptitle = ( string )metadata.GetQuery( @"/xmp/<xmpalt>dc:title" ); // EXIF string exiftitle = ( string )metadata.GetQuery( @"/app1/ifd/{ushort=40091}" ); // IPTC string iptctitle = ( string )metadata.GetQuery( @"/app13/irb/8bimiptc/iptc/object name" );
FileStream f = File .Open( "test.jpg" , FileMode.Open); BitmapDecoder decoder = JpegBitmapDecoder.Create(f, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.Default); BitmapMetadata metadata = (BitmapMetadata)decoder.Frames[ 0 ].Metadata; // string title = metadata.Title; // XMP string xmptitle = ( string )metadata.GetQuery( @"/xmp/<xmpalt>dc:title" ); // EXIF string exiftitle = ( string )metadata.GetQuery( @"/app1/ifd/{ushort=40091}" ); // IPTC string iptctitle = ( string )metadata.GetQuery( @"/app13/irb/8bimiptc/iptc/object name" );
FileStream f = File .Open( "test.jpg" , FileMode.Open); BitmapDecoder decoder = JpegBitmapDecoder.Create(f, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.Default); BitmapMetadata metadata = (BitmapMetadata)decoder.Frames[ 0 ].Metadata; // string title = metadata.Title; // XMP string xmptitle = ( string )metadata.GetQuery( @"/xmp/<xmpalt>dc:title" ); // EXIF string exiftitle = ( string )metadata.GetQuery( @"/app1/ifd/{ushort=40091}" ); // IPTC string iptctitle = ( string )metadata.GetQuery( @"/app13/irb/8bimiptc/iptc/object name" );
ここのすべては非常にシンプルで透明なので、すぐに録音に行きます。
メタデータの記録
- BitmapMetadata md = new BitmapMetadata ( "jpg" );
- md.SetQuery( @ "/ xmp / <xmpalt> dc:title" 、xmptitle);
- md.SetQuery( @ "/ app1 / ifd / {ushort = 40091}" 、exiftitle);
- md.SetQuery( @ "/ app13 / irb / 8bimiptc / iptc /オブジェクト名" 、iptctitle);
- BitmapFrame frame = BitmapFrame.Create(decoder.Frames [ 0 ]、decoder.Frames [ 0 ] .Thumbnail、md、decoder.Frames [ 0 ] .ColorContexts);
- BitmapEncoderエンコーダー= 新しい JpegBitmapEncoder ();
- encoder.Frames.Add(フレーム);
- FileStream of = File .Open( "test2.jpg" 、FileMode.Create、FileAccess.Write);
- encoder.Save(of);
- of.Close();
コードは、メタデータを読み取るフラグメントの続きとして機能します。 3つのすべてのメタデータ形式でメタデータにタイトルを書き込むことにより、元のファイルのコピーを作成します。
オンサイトのメタデータ編集
これまで、私は一般的に非常によく文書化された簡単なことを言ってきましたが、ここではすべてがすでにより複雑です。 公式文書(MSDN)の例は間違っており、一般的に物事の実際の状態とは意味が反対です。
インプレースメタデータを編集するには、InPlaceBitmapMetadataWriterクラスのオブジェクトを作成する必要があります。
- InPlaceBitmapMetadataWriterライター;
- writer = decode.Frames [ 0 ] .CreateInPlaceBitmapMetadataWriter();
その後、通常のBitmapMetadataと同様に、SetQueryを呼び出して必要なメタデータを設定します。
変更を保存するには、TrySave()メソッドを呼び出して、変更を元のストリームに保存しようとします。 記録しようとしても成功する場合としない場合があります。 成功した場合、メソッドはtrueを返し、そうでない場合はfalseを返します。
変更の書き込みを妨げる可能性のある最も一般的な間違いは、メタデータに十分な空き領域がないことです。 原則として、新たに撮影されたすべての写真にはメタデータに十分なスペースが含まれていないため、メタデータの編集をその場で使用し始めるには、ファイルのコピーを作成し、特別なパディングフィールドを使用してメタデータを追加し、その後の変更のために空きスペースを残してください。 これを行うには、ファイルを開き、目的のフレームとそのメタデータのクローンを作成し、いくつかのリクエストを実行します。
- BitmapFrame frame =(BitmapFrame)decode.Frames [ 0 ] .Clone();
- BitmapMetadataメタデータ=(BitmapMetadata)decode.Frames [ 0 ] .Metadata.Clone();
- metadata.SetQuery( "/ app1 / ifd / PaddingSchema:Padding" 、 2048 );
- metadata.SetQuery( "/ app1 / ifd / exif / PaddingSchema:Padding" 、 2048 );
- metadata.SetQuery( "/ xmp / PaddingSchema:Padding" 、 2048 );
- BitmapFrame newframe = BitmapFrame.Create(frame、frame.Thumbnail、metadata、original.Frames [ 0 ] .ColorContexts);
その後、エンコーダーでフレームをエンコードし、目的のストリームに書き込むだけで十分です。その結果、後でメタデータを編集するための空きスペースが画像に表示されます。
通常、2048バイトのパディング値で十分です。 さらに必要な場合は、より大きな値を指定できます。
クエリ文字列
SetQuery / GetQueryメソッドを研究する誰もが合理的な質問を持っていると思います-シンプルで直感的とは言えないこれらのクエリ文字列のすべてをどこで入手できるのでしょうか?
MSDNで広範囲に検索した結果、 リストが見つかりました。 おそらくすべての必要な要求があります。 不足しているものは、原則として、類推によってコンパイルすることができ、多くの例があります:)
微妙さと落とし穴
- JpegBitmapEncoder.Save()を呼び出すスレッドにSTAThread属性がない場合、Windows XPおよびWindows VistaのWICバージョンは失敗する可能性があります(デフォルトでは、特に指定がない限り、アプリケーションで作成されたすべてのスレッドはMTAThread属性を受け取ります)。
- Windows 7のWICバージョンはデフォルトのEXIF UserCommentタグをUnicodeで保存しますが、Windows XPおよびWindows Vistaでは現在のシステム言語(ロシア語ではCP1251)でエンコードされます。 UTF-8パラメータを書き込むための形式は次のとおりです。タグ値自体は文字列としてではなく、バイトの配列として保存されます。 最初の7バイトはASCII文字列「UNICODE」で、その後にUnicodeエンコードされたタグ文字のシーケンスが始まります。
- BitmapCacheOptionsパラメーターは慎重に扱う必要があります。 OnLoad値は、すべての画像データを非圧縮形式でRAMにキャッシュするため、このオプションで20個の大判JPEGを開くと、空きメモリがすぐに消費されます。 このメモリは、イメージクラス自体(BitmapFrame、BitmapDecoderなど)を削除し、ガベージコレクターで処理するときに解放されません 。 さらに、InPlaceBitmapMetadataWriterを使用するには、BitmapCacheOptions = OnDemandまたはDefaultで画像を開く必要があります。
- 例では、IgnoreColorProfileフラグを使用して画像を開きます。 それなしでは、BitmapDecoderは一部の画像で例外をスローします。
おわりに
一般に、WPFを使用したメタデータの操作はかなり複雑で混乱しているように見えました。 記載されているほとんどすべての落とし穴は、デバッグとグーグルで数時間かかり、これに関する情報はどこにもなく、症状は時々非常に奇妙です。 公式文書(MSDN)はこの質問をうまくカバーしておらず、一部の場所では完全に間違っています。
この収集された情報が、WPFを介してメタデータを操作し、時間を節約する必要がある人々に役立つことを願っています:)
PSコメント(どこかで間違いを犯した場合)と、私が会ったことのない、または言及するのを忘れた落とし穴の説明を見て喜んでいます。
PPS WPFについて書き続けるべきですか、それとも古くから知られているものを書いていますか?