s.h.logさんのDirectShow.NETで静止画キャプチャ のサンプルプロジェクト”070104_1130_WebcamCaptureImage.zip”を元にして、動画カメラからJPEGを生成して、EXIF情報を付加するテストプログラムを作った。
元々EXIF情報が無いJPEGに、EXIFを追加する方法について、なかなか情報が少ないようなのだが、
画像のExif情報を取得する、設定するやEXIF情報の保存を参考に、元々EXIF情報が無い場合の付加方法を参考にしつつ、
Image.Saveメソッドで一度EXIF形式で保存、再度ファイルから読んで、別のタグを見つけ、IDを偽装して挿入、ファイルを別名保存する方法で成功した。
Image.Save(FileName,ImageFormat.Exif)で、Unknown(0301)、Unknown(0303)というのが付加されるので、これを
if *1
{
PropertyItem pi = img.PropertyItems[0];
なんていう感じで捕まえて、無理やり別のIDをねじこんじゃえばOK。
追加したEXIFタグは
メーカー(0x010F、ASCII)、ソフトウェア(0x0131、ASCII)、オリジナル撮影日時(0x9003、ASCII)、GPSタグバージョン(0x0000、BYTE)、北緯南緯(0x0001、ACSII)、緯度(0x0002、RATIONAL)、東経西経(0x0003、ASCII)、緯度(0x0004、RATIONAL)、測地系(0x0012、ASCII)
BYTEやRATIONALは相当に面倒くさい。泥臭く1バイトづつbyte配列に書き出していくという地味な感じ。
特に緯度経度は、度、分、秒ごとにintを6つ使って、分数表現。計24バイトのバイナリ配列だ。
■EXIFフォーマット
参考:F6EXIF
ID,TYPEなんかが分かりやすく説明されている。
■EXIFのデータTYPE(参考:画像のExif情報を取得する、設定する)
ID TYPE
1 Byte
2 ASCII 形式でエンコードされた Byte オブジェクトの配列
3 16 ビット整数
4 32 ビット整数
5 有理数を表す 2 つの Byte オブジェクトの配列
6 未使用
7 未定義
8 未使用
9 SLong
10 SRational
■ソースの抜粋(c#)
//そのままでは動きません。!!
//WindowsForm上に表示しているビットマップをExif形式で保存
pictureBox0.Image.Save(FileName,ImageFormat.Exif);
//新しいImageインスタンスをファイルから作成する
Image img = Image.FromFile(FileName);
if *2
{
PropertyItem pi = img.PropertyItems[0];
//加工用バイト配列
byte data;
byte buf;
//基本情報
//原画像データの生成日時
string date = DateTime.Now.ToShortDateString().Replace("/", ":") + " " + DateTime.Now.ToLongTimeString();
setExif(img, 0x9003, "ASCII", date);
//画像タイトル:0x10E ASCII
//メーカー名:0x010F ASCII
string company = "会社名なんかを英語で書く";
setExif(img, 0x010F, "ASCII", company);
//モデル名:0x0110 ASCII
//画像方向:0x0112 SHORT
//ソフトウェア:0x0131 ASCII
string software = "ソフト名を英数で書く";
setExif(img, 0x0131, "ASCII", software);
//GIS情報
//GPSタグ番号 2.0.0.0で固定
pi.Id = 0x0000;
pi.Type = 1;
data = new byte[4];
buf = new byte[1];
//泥臭く2,0,0,0をバイト配列に書き込む
buf = BitConverter.GetBytes(2);
Buffer.BlockCopy(buf, 0, data, 0, 1);
buf = BitConverter.GetBytes(0);
Buffer.BlockCopy(buf, 0, data, 1, 1);
buf = BitConverter.GetBytes(0);
Buffer.BlockCopy(buf, 0, data, 2, 1);
buf = BitConverter.GetBytes(0);
Buffer.BlockCopy(buf, 0, data, 3, 1);
pi.Value = data;
pi.Len = pi.Value.Length;
img.SetPropertyItem(pi);
//緯度NS Nで固定
setExif(img, 0x0001, "ASCII", "N");
//経度EW Eで固定
setExif(img, 0x0003, "ASCII", "E");
//測地系 WGS-84で固定
setExif(img, 0x0012, "ASCII", "WGS-84");
//緯度(lat):0x0002 RATIONAL
//[百分率の小数点以下] = [分] / 60 + [秒] / 3600
double lat = [百分率表記の緯度];
double lon = [百分率表記の経度];
data = new byte[24];
buf = new byte[4];
int i_do;
int i_fun;
int i_byo;
//百分率を度、分、秒にする
double d = (lat % 1)*3600;
i_do = (int)(lat/1);
i_fun= (int)(d/60);
i_byo = (int)((d%60)*1000);
pi.Id = 0x0002;
pi.Type = 5;
buf = BitConverter.GetBytes(i_do);
Buffer.BlockCopy(buf, 0, data, 0, 4);
buf = BitConverter.GetBytes(1);
Buffer.BlockCopy(buf, 0, data, 4, 4);
buf = BitConverter.GetBytes(i_fun);
Buffer.BlockCopy(buf, 0, data, 8, 4);
buf = BitConverter.GetBytes(1);
Buffer.BlockCopy(buf, 0, data, 12, 4);
buf = BitConverter.GetBytes(i_byo);
Buffer.BlockCopy(buf, 0, data, 16, 4);
buf = BitConverter.GetBytes(1000);
Buffer.BlockCopy(buf, 0, data, 20, 4);
pi.Value = data;
pi.Len = pi.Value.Length;
img.SetPropertyItem(pi);
//経度(lon)
pi.Id = 0x0004;
pi.Type = 5;
d = (lon % 1) * 3600;
i_do = (int)(lon / 1);
i_fun = (int)(d / 60);
i_byo = (int)((d % 60)*1000);
buf = BitConverter.GetBytes(i_do);
Buffer.BlockCopy(buf, 0, data, 0, 4);
buf = BitConverter.GetBytes(1);
Buffer.BlockCopy(buf, 0, data, 4, 4);
buf = BitConverter.GetBytes(i_fun);
Buffer.BlockCopy(buf, 0, data, 8, 4);
buf = BitConverter.GetBytes(1);
Buffer.BlockCopy(buf, 0, data, 12, 4);
buf = BitConverter.GetBytes(i_byo);
Buffer.BlockCopy(buf, 0, data, 16, 4);
buf = BitConverter.GetBytes(1000);
Buffer.BlockCopy(buf, 0, data, 20, 4);
pi.Value = data;
pi.Len = pi.Value.Length;
img.SetPropertyItem(pi);
}
//ファイル保存
string s = DateTime.Now.ToShortDateString().Replace("/", "") + DateTime.Now.ToLongTimeString().Replace(":", "").Replace(" ", "");
img.Save(s + "_" + FileName, FileFormat);
//内部で勝手に生成したイメージオブジェクトを破棄
img.Dispose();
//作業用に書き出したEXIFファイルを削除
File.Delete(FileName);