Exif データにアクセスするコードを自作してみる
先日ちょっとしたアプリを書いていた折に JPEG ファイル内の Exif データを参照する処理が必要になりました。初めての機会だったのでやり方を調べたところ 開発環境向けに用意されたこれこれのライブラリを使うのが常道とのことで、リファレンスを読みながらそういうコードを書きました。プログラムは期待通りに動作しその時はそれで特にどうと言うこともなかったのですが、後日別のプラットフォームで Exif データを利用するアイディアを思いつき、その環境でまた別のライブラリの使い方など調べているうちに何だか面倒になりました。
ここでのターゲットはあくまでも一介のデータ形式です。既存のソフトウェア資産を利用することは生産性の上でも効率の面でも間違った選択ではないものの、「普通のファイルに記録されたデータ」にアクセスするための道具立てにいろいろ左右されることが煩わしく感じられたのです。Exif の規格は公開されているため仕様を確認して必要なコードを自作すれば今後この形式を既存のライブラリごしに「ブラックボックス」として扱う必要はなくなるはずで、また、自作のコードであれば拡張や応用の融通が利きやすく必要なら別のプログラム言語への移植も楽でしょう。
デジタルカメラという今日ではごく身近な道具を通じてその存在を知りながら中身をずっと知らずにいた Exif のしくみに触れることはそれ自体も興味ぶかい経験でした。その一端をご紹介します。C 言語で試作したソースコード一式を記事の最後に掲載しています。
Exif の仕様
まず Exif のデータ形式の仕様をできるだけコンパクトに整理してみます。もっとも詳しく正確なのはもちろん公式の規格書ですから不明な点があればそこでの記述を参照して下さい。
規格書
電子情報技術産業協会 (JEITA) ホームページ上の「JEITA 規格総合検索」フォームから、「規格名」に "Exif" を指定して検索を実行すると「JEITA CP-3451C デジタルスチルカメラ用 画像ファイルフォーマット規格 Exif 2.3」を電子文書として閲覧できます。 ※ 2013年8月31日現在
全体の構造
下の図は規格書をもとに JPEG ファイル中の Exif データの構造をまとめてみたものです。要点をピックアップしてみます。
タグフィールドの扱い方
どこにどのような情報が配置されているかは上の図を追えば把握できるでしょう。構造を順に辿って最後に到着するのは具体的なデータの格納された末端のタグフィールドです。もちろんこれらが最も重要な要素であり、ここではタグの値にアクセスする方法を説明します。
フィールドの構成
一件のタグフィールドには以下の内容が含まれます- タグ 番号 2 バイト 所定のタグを識別するための番号
- タイプ 2 バイト タグの値のデータタイプを示す
- カウント 4 バイト 値の個数
- オフセット 4 バイト 値の格納された位置
タグ名称 Field Name タグ番号(Hex) タイプ カウント
Exif バージョン ExifVersion 9000 UNDEFINED 4
色空間情報 ColorSpace A001 SHORT 1
: : : : :
■ Exif バージョン ExifVersion
本規格での対応バージョンを示す。このフィールドが存在しなければ,本規格に準拠していない
と判断される。本規格に準拠する場合には,4Byte の ASCII "0230" を記録しなければならない。
Type が UNDEFINED のため最後に NULL は記録してはならない。
Tag = 36864 (9000.H)
Type = UNDEFINED
Count = 4
Default = "0230"
■ :
:
タイプについて
タグの値には以下のタイプがあります。値の取得方法は後述のカウント・オフセットのルールとの組合せで決まります。
カウントについて
カウントは「値の個数」を示します。「バイト数の合計」を表すものではありません。以下に例を示します。
オフセットについて
4 バイトのオフセット領域にはタグの値が記録されている位置(上図中の★が起点)が保持されています。ただし、値が 4 バイト以内で表現できる場合はその値がオフセット領域に直書きされます。4 バイト以内の値の例を以下に示します。
さらに、値の長さが 4 バイト未満の場合、値はオフセット領域に「左詰め」で格納されます。以下の要領です。
■ BYTE タイプの値 1
0 1 2 3 ┌─┬─┬─┬─┐ │01│00│00│00│ データのバイトオーダーがビッグエンディアンの場合 └─┴─┴─┴─┘ ┌─┬─┬─┬─┐ │01│00│00│00│ データのバイトオーダーがリトルエンディアンの場合 └─┴─┴─┴─┘
■ SHORT タイプの値 1, 3
0 1 2 3 ┌─┬─┬─┬─┐ │00│01│00│03│ データのバイトオーダーがビッグエンディアンの場合 └─┴─┴─┴─┘ ┌─┬─┬─┬─┐ │01│00│03│00│ データのバイトオーダーがリトルエンディアンの場合 └─┴─┴─┴─┘
データの実例
図は Exif セグメントを含むある JPEG ファイル冒頭部分の HEX ダンプイメージです。上述のデータ構造とデータ形式を踏まえてここから読み取られる 0th IFD の内容を併記しています。
試作したコード
C 言語で書いたソースコード一式を以下で公開しています。
Windows 用実行形式 (x86)
- 現在のバージョンには以下の機能があります
- 所定の JPEG ファイルの Exif セグメント中の一部またはすべての IFD の内容をダンプ表示
- 指定された IFD タイプ+タグ番号に該当するタグフィールドの情報を取得
- 所定の JPEG ファイルから Exif セグメントを除去した新しい JPEG ファイルを出力
- exif.c, exif.h の二本が実体で sample_main.c は関数呼び出しのサンプルです。
サンプルプログラムのビルドは以下の要領です- gcc の場合: gcc -o exif sample_main.c exif.c
- Microsoft Visual C++ の場合: cl.exe /o exif sample_main.c exif.c
- 標準ライブラリ関数のみを使っているため多くの環境での利用が可能です。手元では以下の組合せでの動作を確認しました
- Windows XP 32bit + Microsoft Visual C++ でビルドした 32bit バイナリ
- Windows 7 64bit + Microsoft Visual C++ でビルドした 64bit バイナリ
- Redhat Linux 32bit + 32bit 版 gcc でビルドしたバイナリ
- Mac OS X 64bit + 64bit 版 gcc でビルドしたバイナリ
- 動作確認用の test.jpg をサンプルプログラムで処理した結果を以下に引用します
$ ./exif test.jpg
[test.jpg] createIfdTableArray: result=4
{0TH IFD}
- Make: [Apple]
- Model: [iPod touch]
- Orientation: 1
- XResolution: 72/1
- YResolution: 72/1
- ResolutionUnit: 2
- Software: [6.1.4]
- DateTime: [2013:09:01 09:49:00]
- YCbCrPositioning: 1
- ExifIFDPointer: 206
- GPSInfoIFDPointer: 576
{EXIF IFD}
- ExposureTime: 1/30
- FNumber: 12/5
- ExposureProgram: 2
- PhotographicSensitivity: 400
- ExifVersion: 0 2 2 1
- DateTimeOriginal: [2013:09:01 09:49:00]
- DateTimeDigitized: [2013:09:01 09:49:00]
- ComponentsConfiguration: 0x01 0x02 0x03 0x00
- ShutterSpeedValue: 4035/821
- ApertureValue: 4845/1918
- BrightnessValue: 2234/1113
- MeteringMode: 5
- Flash: 32
- FocalLength: 77/20
- FlashPixVersion: 0 1 0 0
- ColorSpace: 1
- PixelXDimension: 960
- PixelYDimension: 720
- SensingMethod: 2
- ExposureMode: 0
- WhiteBalance: 0
- FocalLengthIn35mmFormat: 32
- SceneCaptureType: 0
{GPS IFD} -- GPS 経由で得られた撮影地点の座標情報
- GPSLatitudeRef: [S]
- GPSLatitude: 69/1 17/100 0/1
- GPSLongitudeRef: [E]
- GPSLongitude: 39/1 35/100 0/1
- GPSAltitudeRef: 0
- GPSAltitude: 6151/470
- GPSTimeStamp: 0/1 48/1 3921/100
{1ST IFD} -- 埋め込みサムネイルの情報 〜 サムネイル画像データへ続く
- Compression: 6
- XResolution: 72/1
- YResolution: 72/1
- ResolutionUnit: 2
- JPEGInterchangeFormat: 840
- JPEGInterchangeFormatLength: 8648
0th IFD : Model = [iPod touch]
Exif IFD : DateTimeOriginal = [2013:09:01 09:49:00]
GPS IFD : GPSLatitude = 69/1 17/100 0/1
removeExifSegmentFromJPEGFile: result=1
$ ./exif _noexif.jpg
[_noexif.jpg] does not seem to contain the Exif segment.
(tanabe)
