デジタルカメラで撮影した画像ファイルには、Exif(Exchangeable Image File Format)情報が含まれていることが多く、撮影したデジカメの名前やメーカー、撮影日時等々の情報が保存されています。ここではこのような画像に埋め込まれたExifの情報を取得する方法と、Exif情報を付加して保存する方法を紹介します。
ImageオブジェクトのPropertyItemsプロパティにより、そのイメージが持っているメタデータを取得することができます。Exifの情報はここに含まれています。PropertyItemsプロパティはPropertyItemオブジェクトの配列です。PropertyItemオブジェクトには、タグID、データの型、データの長さ、そしてデータといった情報が含まれています。
以下に簡単な例を示します。指定された画像ファイルのメタデータ情報(タグID、型、データの長さ)をすべて列挙しています。データがASCII文字の場合(PropertyItem.Typeが2の場合)は、文字列にして表示しています。
'画像ファイル名 Dim imgFile As String = "C:\test.jpg" '読み込む Dim bmp As New System.Drawing.Bitmap(imgFile) 'Exif情報を列挙する Dim item As System.Drawing.Imaging.PropertyItem For Each item In bmp.PropertyItems 'データの型を判断 If item.Type = 2 Then 'ASCII文字の場合は、文字列に変換する Dim val As String = System.Text.Encoding.ASCII.GetString(item.Value) val = val.Trim(New Char() {ControlChars.NullChar}) '表示する Console.WriteLine("{0:X}:{1}:{2}", item.Id, item.Type, val) Else '表示する Console.WriteLine("{0:X}:{1}:{2}", item.Id, item.Type, item.Len) End If Next item bmp.Dispose()
//画像ファイル名 string imgFile = "C:\\test.jpg"; //読み込む System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(imgFile); //Exif情報を列挙する foreach (System.Drawing.Imaging.PropertyItem item in bmp.PropertyItems) { //データの型を判断 if (item.Type == 2) { //ASCII文字の場合は、文字列に変換する string val = System.Text.Encoding.ASCII.GetString(item.Value); val = val.Trim(new char[] { '\0' }); //表示する Console.WriteLine("{0:X}:{1}:{2}", item.Id, item.Type, val); } else { //表示する Console.WriteLine("{0:X}:{1}:{2}", item.Id, item.Type, item.Len); } } bmp.Dispose();
補足:上記の例では、文字列に変換する時、'\0'を取っています。ヘルプなどのサンプルではGetStringメソッドでそのまま変換する例しか見ませんが、PropertyItem.Valueの最後には'\0'が付きますので、取ったほうが良いでしょう。
PropertyItemのIdとTypeについて詳しく説明しましょう。
PropertyItem.Idプロパティで取得できるタグIDからその意味を知るには、「EXIF.org」や「EXIF Tags」や「Exif TAG」などのページが参考になります。
PropertyItem.Typeプロパティは、Valueプロパティで取得できるデータの型が何であるかを示します。Typeプロパティの値とその意味(データがどのような型であるか)は、次の通りです(ヘルプからの抜粋)。
PropertyItem.Typeプロパティの値 | データの型 |
---|---|
1 | Byte |
2 | ASCII 形式でエンコードされた Byte オブジェクトの配列 |
3 | 16 ビット整数 |
4 | 32 ビット整数 |
5 | 有理数を表す 2 つの Byte オブジェクトの配列 |
6 | 未使用 |
7 | 未定義 |
8 | 未使用 |
9 | SLong |
10 | SRational |
もう少し実用的な例を紹介しましょう。次の例では、画像ファイルの作成日時を、Exif情報に記録された撮影日時(DateTimeOriginal)に設定しなおしています。
'画像ファイル名 Dim imgFile As String = "C:\test.jpg" '読み込む Dim bmp As New System.Drawing.Bitmap(imgFile) Dim item As System.Drawing.Imaging.PropertyItem For Each item In bmp.PropertyItems 'Exif情報から撮影時間を取得する If item.Id = &H9003 And item.Type = 2 Then '文字列に変換する Dim val As String = System.Text.Encoding.ASCII.GetString(item.Value) val = val.Trim(New Char() {ControlChars.NullChar}) 'DateTimeに変換 Dim dt As DateTime = DateTime.ParseExact( _ val, "yyyy:MM:dd HH:mm:ss", Nothing) 'ファイルの作成日時を変更 System.IO.File.SetCreationTime(imgFile, dt) End If Next item bmp.Dispose()
//画像ファイル名 string imgFile = "C:\\test.jpg"; //読み込む System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(imgFile); foreach (System.Drawing.Imaging.PropertyItem item in bmp.PropertyItems) { //Exif情報から撮影時間を取得する if (item.Id == 0x9003 && item.Type == 2) { //文字列に変換する string val = System.Text.Encoding.ASCII.GetString(item.Value); val = val.Trim(new char[] { '\0' }); //DateTimeに変換 DateTime dt = DateTime.ParseExact(val, "yyyy:MM:dd HH:mm:ss", null); //ファイルの作成日時を変更 System.IO.File.SetCreationTime(imgFile, dt); } } bmp.Dispose();
イメージにExif情報を設定するには、PropertyItem.SetPropertyItemメソッドを使います。しかし、PropertyItemクラスのコンストラクタは公開されていませんので、SetPropertyItemメソッドに指定するPropertyItemオブジェクトを0から作成することができません。
イメージのPropertyItemsプロパティからPropertyItemオブジェクトが取得できるのであれば、その内容を変更してからSetPropertyItemメソッドを使うことにより、Exif情報を編集できます。また、Exif情報を持つイメージからPropertyItemsプロパティでPropertyItemオブジェクトを取得することにより、Exif情報を持たないイメージにSetPropertyItemメソッドでExif情報を設定することもできます。しかし、全く0からExif情報を作成して設定することはできません。
以下に示す例では、画像ファイルのExifに作者名(Artist)情報が見つかれば、これを変更して、別名で保存しています。なお、PropertyItem.Typeが2(「EXIF Tags」で「Writable」が「string」)であるものには、基本的には2バイト文字は指定できません。
'読み込む画像ファイル名 Dim imgFile As String = "C:\1.jpg" '保存先 Dim saveFile As String = "C:\2.jpg" '変更する作者名 Dim artist As String = "DOBON" '画像を読み込む Dim bmp As New System.Drawing.Bitmap(imgFile) Dim i As Integer For i = 0 To bmp.PropertyItems.Length - 1 Dim pi As System.Drawing.Imaging.PropertyItem = bmp.PropertyItems(i) 'Exif情報から作者名を探す If pi.Id = &H13B And pi.Type = 2 Then '値を変更する pi.Value = System.Text.Encoding.ASCII.GetBytes( _ artist + ControlChars.NullChar) pi.Len = pi.Value.Length '設定する bmp.SetPropertyItem(pi) Exit For End If Next i '保存する bmp.Save(saveFile, System.Drawing.Imaging.ImageFormat.Jpeg) bmp.Dispose()
//読み込む画像ファイル名 string imgFile = "C:\\1.jpg"; //保存先 string saveFile = "C:\\2.jpg"; //変更する作者名 string artist = "DOBON"; //画像を読み込む System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(imgFile); for (int i = 0; i < bmp.PropertyItems.Length; i++) { System.Drawing.Imaging.PropertyItem pi = bmp.PropertyItems[i]; //Exif情報から作者名を探す if (pi.Id == 0x13B && pi.Type == 2) { //値を変更する pi.Value = System.Text.Encoding.ASCII.GetBytes(artist + '\0'); pi.Len = pi.Value.Length; //設定する bmp.SetPropertyItem(pi); break; } } //保存する bmp.Save(saveFile, System.Drawing.Imaging.ImageFormat.Jpeg); bmp.Dispose();
上記の例ではExif情報から作者名を探してそのPropertyItemを取得して変更しましたが、作者名の情報が見つからなかったとしても別のPropertyItemがあるならば、そのIdとTypeを変更してSetPropertyItemメソッドで設定することができます。
以下にこのような方法を用いた例を示します。ここでは、ユーザーコメント(UserComment)を設定しています。ユーザーコメントのTypeは7(undef)ですので、2バイト文字でも格納できます。ここでは、ユーザーコメントをShift JISでエンコードして格納しています。
'読み込む画像ファイル名 Dim imgFile As String = "C:\1.jpg" '保存先 Dim saveFile As String = "C:\2.jpg" 'ユーザーコメント Dim comment As String = "私の撮った写真です。" '画像を読み込む Dim bmp As New System.Drawing.Bitmap(imgFile) If bmp.PropertyItems Is Nothing OrElse bmp.PropertyItems.Length = 0 Then Return End If 'とにかくPropertyItemオブジェクトを取得する Dim pi As System.Drawing.Imaging.PropertyItem = bmp.PropertyItems(0) 'IdやType等を変更する pi.Id = &H9286 pi.Type = 7 'コメントの前に'\0'を8バイト入れる comment = New String(ControlChars.NullChar, 8) + comment pi.Value = System.Text.Encoding.GetEncoding("shift_jis").GetBytes(comment) pi.Len = pi.Value.Length '格納する bmp.SetPropertyItem(pi) '保存する bmp.Save(saveFile, System.Drawing.Imaging.ImageFormat.Jpeg) bmp.Dispose()
//読み込む画像ファイル名 string imgFile = "C:\\1.jpg"; //保存先 string saveFile = "C:\\2.jpg"; //ユーザーコメント string comment = "私の撮った写真です。"; //画像を読み込む System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(imgFile); if (bmp.PropertyItems == null || bmp.PropertyItems.Length == 0) return; //とにかくPropertyItemオブジェクトを取得する System.Drawing.Imaging.PropertyItem pi = bmp.PropertyItems[0]; //IdやType等を変更する pi.Id = 0x9286; pi.Type = 7; //コメントの前に'\0'を8バイト入れる comment = new string('\0', 8) + comment; pi.Value = System.Text.Encoding.GetEncoding("shift_jis").GetBytes(comment); pi.Len = pi.Value.Length; //格納する bmp.SetPropertyItem(pi); //保存する bmp.Save(saveFile, System.Drawing.Imaging.ImageFormat.Jpeg); bmp.Dispose();
補足:上記の例ではコメントの前に'\0'を8バイト入れていますが、この部分に文字列のエンコード情報が格納されます。UNICODEであれば、"UNICODE"、JISコードであれば、"JIS"が入ります。ASCIIやShift JISであれば、空白となります。
前述したとおり、PropertyItemクラスのコンストラクタは公開されていませんので、新しくインスタンスを作成することができません。よって、例えば、動的に作成した画像にExif情報を埋め込みたい場合には、困ったことになります。
このような時でも、先の例のように、どうにかしてPropertyItemsプロパティからPropertyItemオブジェクトを取得できれば、これを利用してSetPropertyItemメソッドで埋め込むことができます。そのために、Exif情報を持つイメージをリソースに埋め込んでおき、ここからPropertyItemオブジェクトを取得するという方法が考えられます。画像をリソースとして埋め込む方法は、「画像やテキストファイルを実行ファイルに埋め込む」などをご覧ください。