ほげchの開発時にGIF、PNGとJPEGファイル(JFIFフォーマット)の縦横のドット数を得らなければいけなくなり、解析に少してこずったのでその方法を書き留めておきます。
ご存じのようにJPEG画像はJPEGを標準化しようという団体で決めたフォーマットで圧縮されています。しかし、この団体は圧縮フォーマットについては定めていますがそれを受け渡すためのファイルフォーマットを特に定めていません。そのためかどうか知りませんが(^^;現在、C-Cube Microsystems社が提案したJFIFフォーマット(JPEG File Interchange Fromat)が標準になりました。(デファクトスタンダードなんでしょうか?? その辺の歴史については調べていません)
まず、JFIFの基本から。
このあたりの構造はこちら(JPEGのファイル形式ではないですが、JFIF構造について記述されています)に詳しいです。
実際のデータは以下のようになっているようです。
| FF | FLAG | データ領域の開始を表すフラグ |
| D8 | MARKER(SOI) | JFIFファイルの始まりを表す特殊なマーカー この後にサイズ情報は付加されません |
| FF | FLAG | データ領域の開始を表すフラグ |
| E0 | MARKER | おそらくヘッダを現すマーカー |
| 00 | SIZE(H) | このデータ領域のサイズ(上位) |
| 10 | SIZE(L) | 同じく(下位) |
| 4A | "J" | |
| 46 | "F" | |
| 49 | "I" | |
| 46 | "F" | |
| FF | FLAG | データ領域の開始を表すフラグ |
| C0 | MARKER(SOF0) | 詳細は分かりませんが、最初に見つかったC0h~CFh(SOF0~SOF15)で、FIX08(仮称)のデータが08であるデータ領域の中に画像の縦横のサイズが格納されています。 |
| ?? | SIZE(H) | このデータ領域のサイズ(上位) |
| ?? | SIZE(L) | 同じく(下位) |
| 08 | FIX08 | なぜか08なのです(8bit precision(精度??)だそうです) |
| YY | Y-SIZE(H) | 縦のサイズ(上位) |
| YY | Y-SIZE(L) | 縦のサイズ(下位) |
| XX | X-SIZE(H) | 横のサイズ(上位) |
| XX | Y-SIZE(L) | 横のサイズ(下位) |
| FF | FLAG | |
| DA | MARKER(SOS) | JPEGイメージデータの開始を表すフラグ この後にはサイズ情報は続きません |
| ?? | DATA | ここにはサイズ情報は格納されません |
| ?? | DATA | ここにはサイズ情報は格納されません |
| FF | FLAG | データ領域の開始を表すフラグ |
| D9 | MARKER(EOI) | データの終了を表す特殊なフラグ これがデータの最後になります |
上記のようにマーカーがC0hでFIX08(いつも08なので、こう勝手に呼んでいます)が08hであるデータ領域にサイズ情報が格納されています。経験的に最初に見つかったマーカーC0hかC2に格納されているようです。
これを簡単に試せるように以下のようなPerlスクリプトを書いてみました。
標準入力にJPEGファイルを食わせてやると、マーカーを全部なめてコメントとサイズを表示します。
#!/usr/bin/perl
#
# JFIF(JPEG)File format
#
# MARKER
# D8 SOI
# D9 EOI
# C0..CF SOF0..15
# DA SOS
#
# FFD8 # SOI
# FFE0[SIZE] # HEADER
# 4A464946 # JFIF
# ---
# FF|MARKER(D8)|SIZE|SIZE-2(SIZE)Bytes DATA AREA|FF|MARKER|SIZE|SIZE-2(SIZE)Bytes DATA AREA...
# FF|MARKER(DA)|JPEG DATA
# FF|MARKER(D9)
# (EOF)
binmode STDIN;
undef $/; # read it all(no split "\n" character)
$data=<>;
$/="\n"; # default
$len=length($data);
print "Data length=$len bytes\n";
if ($data =~ /^\xff\xd8\xff\xe0..JFIF/){
for ($i=2; $i<$len;){
if (substr($data, $i)=~/\xff(.|\n)((.|\n){2})(.|\n)/){
$marker=ord $1;
$dsize=unpack("n", $2);
$flag=ord $4;
if ($marker==0xda){
print "MARKER(DA) --- DATA --->\nEOF\n";
last;
}
printf ("MARKER(%02X) %06d(%04X)Bytes\n", $marker, $dsize, $dsize);
if ($marker>=0xc0 && $marker<=0xcf && $flag==8){
$p=unpack("H20", substr($data, $i, 10));
print $p."\n";
$y=unpack("n", substr($data, $i+5, 2));
$x=unpack("n", substr($data, $i+7, 2));
print "JPEG Picture size = ${x}x$y\n";
}
if ($marker==0xfe){
print "comment:".substr($data, $i+4, $dsize-2)."\n";
}
$i+=$dsize+2;
} else {
print "JFIF Unknown format error.\n";
if ($i=index($data, "\xff", $i) == -1){last;}
}
}
}
GIFは簡単です。(説明は不要ですねWebページでは一番使われる機会が多いフォーマットですね。詳しくはないですが、現在のブラウザの原形とも言えるMosaicで標準として採用されたためと思います。)
ファイルの先頭が"GIF98a"や"GIF87a"で始まり、そのすぐあとにunsigned
shortで入っています。
こちらはJPEGと逆でリトルエンディアン(下位バイトが先)です。
PNG(Portable Network Graphics)も簡単でした。少し謎ですが(^^;
ファイルは「.PNG(CR)(LF)(EOF)」という文字列で始まります。
で、サイズ情報は0x10よりlong word(4bytes)でそれぞれ入っているようです。
あとで調べたらここに詳しく載っていました。
2000.01.13〜17 Misaki
3/18 リンクの修正
</HTML>