戻る

JPEG ファイルのサイズを得る


ほげchの開発時にGIF、PNGとJPEGファイル(JFIFフォーマット)の縦横のドット数を得らなければいけなくなり、解析に少してこずったのでその方法を書き留めておきます。

ご存じのようにJPEG画像はJPEGを標準化しようという団体で決めたフォーマットで圧縮されています。しかし、この団体は圧縮フォーマットについては定めていますがそれを受け渡すためのファイルフォーマットを特に定めていません。そのためかどうか知りませんが(^^;現在、C-Cube Microsystems社が提案したJFIFフォーマット(JPEG File Interchange Fromat)が標準になりました。(デファクトスタンダードなんでしょうか?? その辺の歴史については調べていません)

まず、JFIFの基本から。

  1. JFIFはFFD8で始まるファイルである
  2. 「FF|マーカー|サイズ|データ・・・」の繰り返しでファイルを構成する
  3. ただし、マーカーがSOS(DAh)の場合はそれ以降をJPEGデータとし、サイズ情報を含めない
  4. データの終わりはマーカーEOS(D9h)である

このあたりの構造はこちら(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の場合

GIFは簡単です。(説明は不要ですねWebページでは一番使われる機会が多いフォーマットですね。詳しくはないですが、現在のブラウザの原形とも言えるMosaicで標準として採用されたためと思います。)

ファイルの先頭が"GIF98a"や"GIF87a"で始まり、そのすぐあとにunsigned shortで入っています。
こちらはJPEGと逆でリトルエンディアン(下位バイトが先)です。


PNGの場合

PNG(Portable Network Graphics)も簡単でした。少し謎ですが(^^;

ファイルは「.PNG(CR)(LF)(EOF)」という文字列で始まります。
で、サイズ情報は0x10よりlong word(4bytes)でそれぞれ入っているようです。

あとで調べたらここに詳しく載っていました。

 

2000.01.13〜17 Misaki
3/18 リンクの修正

</HTML>