DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

画像のExif情報を取得する、設定する

デジタルカメラで撮影した画像ファイルには、Exif(Exchangeable Image File Format)情報が含まれていることが多く、撮影したデジカメの名前やメーカー、撮影日時等々の情報が保存されています。ここではこのような画像に埋め込まれたExifの情報を取得する方法と、Exif情報を付加して保存する方法を紹介します。

Exif情報を取得する

ImageオブジェクトのPropertyItemsプロパティにより、そのイメージが持っているメタデータを取得することができます。Exifの情報はここに含まれています。PropertyItemsプロパティはPropertyItemオブジェクトの配列です。PropertyItemオブジェクトには、タグID、データの型、データの長さ、そしてデータといった情報が含まれています。

以下に簡単な例を示します。指定された画像ファイルのメタデータ情報(タグID、型、データの長さ)をすべて列挙しています。データがASCII文字の場合(PropertyItem.Typeが2の場合)は、文字列にして表示しています。

VB.NET
コードを隠すコードを選択
'画像ファイル名
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()
C#
コードを隠すコードを選択
//画像ファイル名
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)に設定しなおしています。

VB.NET
コードを隠すコードを選択
'画像ファイル名
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()
C#
コードを隠すコードを選択
//画像ファイル名
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情報を編集する

イメージに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バイト文字は指定できません。

VB.NET
コードを隠すコードを選択
'読み込む画像ファイル名
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()
C#
コードを隠すコードを選択
//読み込む画像ファイル名
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情報を追加する

上記の例ではExif情報から作者名を探してそのPropertyItemを取得して変更しましたが、作者名の情報が見つからなかったとしても別のPropertyItemがあるならば、そのIdとTypeを変更してSetPropertyItemメソッドで設定することができます。

以下にこのような方法を用いた例を示します。ここでは、ユーザーコメント(UserComment)を設定しています。ユーザーコメントのTypeは7(undef)ですので、2バイト文字でも格納できます。ここでは、ユーザーコメントをShift JISでエンコードして格納しています。

VB.NET
コードを隠すコードを選択
'読み込む画像ファイル名
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()
C#
コードを隠すコードを選択
//読み込む画像ファイル名
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であれば、空白となります。

Exif情報を持たないイメージにExif情報を設定する

前述したとおり、PropertyItemクラスのコンストラクタは公開されていませんので、新しくインスタンスを作成することができません。よって、例えば、動的に作成した画像にExif情報を埋め込みたい場合には、困ったことになります。

このような時でも、先の例のように、どうにかしてPropertyItemsプロパティからPropertyItemオブジェクトを取得できれば、これを利用してSetPropertyItemメソッドで埋め込むことができます。そのために、Exif情報を持つイメージをリソースに埋め込んでおき、ここからPropertyItemオブジェクトを取得するという方法が考えられます。画像をリソースとして埋め込む方法は、「画像やテキストファイルを実行ファイルに埋め込む」などをご覧ください。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • このサイトで紹介されているコードの多くは、例外処理が省略されています。例外処理については、こちらをご覧ください。
  • Windows Vista以降でUACが有効になっていると、ファイルへの書き込みに失敗する可能性があります。詳しくは、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。