Perlからの画像ピクセルサイズ取得方法は?

[上に] [前に] [次に]
佐藤 2000/03/23(木) 08:47:55
GIF、JPEGと出来ればPNGのピクセルサイズを、Perlから取得する方法を探しています。

http://www.big.or.jp/~talk/t-club/soft/mini_120/imgsize.pl
↑上のような物も見つけたのですが、パッケージを使っていないため、変数の名前が衝突してしまったり、一部の画像(正常な画像なのに)でサイズが取得できないことがありましたので、他に方法があればぜひ教えてください。

もと。 2000/03/23(木) 09:07:42
http://theoryx5.uwinnipeg.ca/mod_perl/cpan-search?new=Search&filetype=%20module%20name&join=and&arrange=file&download=auto&stem=no&case=clike&site=ftp.funet.fi&age=&modinfo=2033
はいかがでしょう。
documentation を見る限りでは、
GIFやJPEG、PNGのいずれもサポートされているようです。

佐藤 2000/03/28(火) 12:31:42
レスが遅くなってしまいましたが、私はレンタルサーバー上でスクリプトを組んでいますので、パーミッションの関係でインストールがうまくいかないです。
というわけで、出来ればmakeする必要のないものが良いです。
折角教えてくださったのにすいません。

びーだま [E-Mail] 2000/03/29(水) 11:17:02
ちょっと興味があったので、それぞれファイル構造を調べてみました。

ヒントとしては、

GIFファイルの場合
 OFFSET 6 から 2 btyes に幅
 OFFSET 8 から 2 bytes に高さ

JPEG(ベースラインJPEGの場合)ファイルの場合
 SOFn マーカー FFC0 (Hex) を見つけてここから始まる
 SoF0 セグメント FF の位置を0 として相対オフセット
 位置 5 から、2Bytes に高さ 7 から 2Bytesに幅

PNG の場合
 OFFSET 16 から 4Bytes で幅
 OFFSET 20 から 4Bytes で高さ

GIF、PNG は、オフセット位置まで、seek して、値を取得して
unpack して処理すれば、比較的簡単にサイズが取れそうです。
JPEGは、マーカーを探すことからはじめる必要があるのが、
ちょっと面倒ですが、それができれば、あとは GIF, PNG と
さほど変わらぬ方法で取得できそうです。

GIFは、比較的簡単です。
とほほさんのページにも、GIF解析のページがあるようなので、
それもかなり参考になると思います。

PNGについては、チャンク順列は任意ということで、やっぱり
ファイル中探し回らなくてはならないかな?とおもって心配
しましたが、運良くも、サイズについては、必須チャンクで
しかも、最初にくることが決まっているみたいなので処理的
にはラッキーな位置にあります。

JPEGはもともと画像形式ではなくて圧縮形式を画像に応用した
ものなので、面倒くさそうです。ここに書ききれるほどのこと
ではないので、とりあえず、基本的なベースライン JPEG に
使われているマーカーだけ取り上げてみました。

マーカーの種類については、少し努力して探してみてください。
書籍ですと、グラフィックファイルフォーマットハンドブック
というのは、アスキー出版局から出ていて、ここには書いて
ありました。

もと。 2000/03/29(水) 14:07:00
../199911/99110255.htm
を参考にしてもダメでしょうか。

>びーだまさん
参考になります。
ありがとうございました。

びーだま [E-Mail] 2000/03/30(木) 02:21:42
JPEG ファイル中からの目的のセグメントマーカー FF C0 (Hex) <ベース
ラインJPEGの場合>をサーチする効率的な手段はないかな?と思って、
もう少しつっこんで調べてみました。

JPEG のセグメントマーカーは FF とセットになった 2Bytes です。

FFがデータ内に現れる場合は、FF 00 となり、セグメントマーカーと区別
が付くようになっています。(つまり、JPEGデコードする際は 00を破棄
する必要がある)

重要なのはここからです。

FF で始まるマーカーの次は必ず、そのマーカー分を除いた、セグメント内
データ長(Bytes数)を格納した 2Bytes が続きます。

従って、FF をみつけたら、次の1バイトを取って、それが目的のマーカー
でなければ、次の2バイトを取って、そこにある Bytes数だけ相対的に
seekして、そのセグメントを丸ごとスキップできます。

そして、スキップした後の直後の位置には必ず次のセグメントのマーカー
がありますから、あとは、目的のマーカーにたどり着くまで、同様の方法
でビュンビュンスキップして行ってしまえば良いです。

もし、目的のマーカー(FF C0)に到達したならば、そこから、相対的に 3
Bytes 分、(要するに、前の書き込みにしたように、FF から数えて5Bytes
目まで) seek します。そこから、2Bytes に高さ、続いて、2Bytes に幅
を取得できます。

実は、ベースラインJPEG に絞って、話を進めてきましたが、マーカー
からサイズ情報までの相対位置は、プログレッシブJPEGでも同じ様です。

プログレッシブJPEG ではどんなマーカーが使われているのか、実際に
画像をつくってバイナリエディタで除いてみたら、FF C2 とありました。

画像情報を格納するための SOFn セグメントについては、C0、C2 の他、
C1 C3 C5 C6 C7 C9 CA CB CD CE CF が値として予約されている様です。

いろいろ、これらの種別はハフマンなんたらとか、算術なんたらとか、
難しげですね。

とりあえず、サイズ情報はベースライン、プログレッシブ問わずにこの
方法でうまく取得できました。

ごめんなさい、サンプルコードは事情によりお分けできませんが。。
とりあえず、私がやってみた処理は上記に書いた通りの手順です。

これらの情報を元に挑戦してみて下さい。

びーだま [E-Mail] 2000/03/30(木) 02:30:15
> FF で始まるマーカーの次は必ず、そのマーカー分を除いた、セグメント内
> データ長(Bytes数)を格納した 2Bytes が続きます。

ごめんなさい、必ずと書きましたが、例外が2つだけあります。
それは、ファイルの先頭に必ず来る SOI セグメントマーカー
FF D8 と終わりに来る、EOI セグメントマーカー FF D9 で、
いずれも、この 2Bytes でおしまいです。

従って、実際の処理の際はこれら(特にSOI)分はあらかじめ
スキップしてしまう方が(わずかながら)効率的ですね。

もと。 2000/03/30(木) 07:47:29
GIFのサイズの取得はこんな感じでいいんでしょうか。

open(IN, 'xxx.gif');
seek(IN, 6, 0);
read(IN, $width, 2);
read(IN, $height, 2);
close(IN);

$width = ord($width);
$height = ord($height);

print "$width * $height";

JPEGは後でやってみます。

正直言ってunpackの使い方がよくわかりません。
今まで使う必要がなかったというのもあるのですが。
ラクダ本読んでても「んん?」って感じです。
解説されているWebサイトはないのかな。

高校行ったらこの辺のことも教えてくれるかな。
少なくとも中学校はWindows触るだけで終わりました。

ところで、プログラマって、何歳ぐらいの方が多いのでしょうか。
# 元の質問の内容からずいぶんそれてしまった。すみません。

びーだま [E-Mail] 2000/03/31(金) 20:36:50
いくつか問題があります。

> $width = ord($width);
> $height = ord($height);

GIFの場合はテスト画像が255ピクセル以下だとこの方法でもたまたま
うまく表示されてしまうと思いますので、もう少し大きなピクセル数
のテスト画像を用意して下さい。

ord だと、複数バイトあっても、先頭の1バイト分だけしか処理しま
せんね。実行してみれば判りますが、これでは正しいが表示できない
と思います。

つぎに、unpack の件ですが、

ord($width)



unpack("C", $width)

は、等価の結果をもたらします。

ただ、GIFのサイズ解析においては上記の様に $width は、2バイト
の情報ですから、1 バイトしか扱わないのでは不都合が出てきます。

($w1, $w2) = unpack("C2", $width)

とすれば、$w1, $w2 にはそれぞれ、10進数で表したキャラクタコード
が入ります。

ここまで出来たら、あとは、$w1, $w2 は一体なにを表す数字なのか?

ということですが、GIFの場合は、例えば、1234 ( 04 D2 <Hex>) ピク
セルの幅のある画像の場合は、バイト順が小さい位から大きい位の順、
D2 04 <Hex> となっています。

つまり、$w1 に 210 (D2 <Hex>) $w2 に 4 (04 <Hex>) が入っている
ので、

print $w2 * 256 + $w1;

とすれば、ピクセル数を計算することが出来ます。ただ、折角 unpack
を使うならもう 256 を掛けるとかをしないでもう少しスマートに書き
たいと思いますよね。(実際にはどっちでも良いかもしれないですが・・)

そんなときは unpack("H*", $width) を使うと良いです。こうすると、
16進数表現の文字列を得ることが出来ますから、後はこれを hex() 関数
で10進数に変換してやれば大丈夫です。

ただし、GIFの場合は、上記のようにピクセル情報のバイト順の都合があ
りますから単純に

$w = unpack("H*", $width);
print hex($w);

としてしまうと、変な値が得られるとおもいます。
つまり、正しく処理するには、$width に入っているバイトを逆順にして
扱わなければならないわけです。

と、いうことで、これらの考えを盛り込んだしたサンプル処理は次のよう
になると思います。

$gif = 'xxx.gif';

open(GIF, "$gif") || die;
seek(GIF, 6, 0);
read(GIF, $w1, 1);
read(GIF, $w2, 1);
read(GIF, $h1, 1);
read(GIF, $h2, 1);
close(GIF);

$w      = unpack("H*", $w2.$w1);
$width  = hex($w);

$h      = unpack("H*", $h2.$h1);
$height = hex($h);

print "$width * $height";

さて、最後に、JPEG , PNG についてですが、これは、GIFと違って、
大きい位から小さい位を表す順にバイトが並んでいますからサイズ
情報の位置さえ判ってしまえば、GIFのように1バイトづつとって
逆順に処理するようなことをする必要はなくそれぞれ必要なバイト
数を read してそのまま、unpackして hex しまえば問題ないようです。

# ちなみに、プログラマーって・・については、私の職場ではいわゆる
# 現役プログラマはおそらくほとんど20代ですね。30代は前半がチラホラ
# の様です。それより上はマネジメントが主になるって感じでしょうか。
# 会社や専門業種によって年齢層は変わる気はします。
# 10代については会社が大卒を望むようなところだとほとんど居ない気
# がします。

ween 2000/03/31(金) 20:53:36
16進数の文字列を取得する必要が無ければ、

open(GIF, "$gif") || die;
seek(GIF, 6, 0);
read(GIF, $size, 4);
close(GIF);
($width,$height)=unpack("vv",$size);

…で取得できるような気もするんですが。

びーだま [E-Mail] 2000/03/31(金) 21:08:57
ween さんへ。

それで、うまくゆきますね。
私、TEMPLATE の v あたりはプラットフォームのエンディアンに
依存するものだと勝手に主って使わず嫌いで避けて通っていました。

いま、ちょっと確かめていたら実は、v とか、n ってエンディアン
依存にならないんですね。

勉強になりました。

びーだま [E-Mail] 2000/03/31(金) 22:07:55
ween さんに教えてもらった方法の感動しました。
こんなに、便利だったんですね! n v N V って。使わず嫌いでかなり
損をしていた様な気がします。(^^;

早速これを使って、このスレッドをきっかけに作り始めた Perl で
(ファイルの種類も判別して)サイズを取り出すパッケージを n v N V
を使って作り直しました。 かなりスッキリしたので非常に嬉しかったです。

お礼と言うのもなんですが、(^^;
課題の3種類以外に BMP にも対応させたので、おまけとしてその
サイズ位置とそのルーチンを公開します。

BMP ファイルは
オフセット値 18 から 4 バイト リトルエンディアン で 幅
オフセット値 22 から 4 バイト リトルエンディアン で 高さ

sub _getBmpSize {

    my $bmp = shift;
    my($pixels, $width, $height);

    seek(FILE, 18, 0);
    read(FILE, $pixels, 8);
    ($width, $height) = unpack("V2", $pixels);

    return($width, $height);
}

びーだま [E-Mail] 2000/03/31(金) 22:17:19
あ、上のルーチン FILE open させてませんね。
これ、このルーチンは内部処理用で単独では動かないんです。 (^^;

あらかじめオープンしてこのルーチン呼ぶか、そういうのが気持ち悪い
なら、グロブでファイルハンドル渡すようにすればよいと思います。

びーだま [E-Mail] 2000/03/31(金) 22:25:03
ほんとにごめんなさい。
イロイロボロボロとゴミが出てきます。

my $bmp = shift; の行は無くて良いです。
デバッグ入力の名残でした。(^^; 

佐藤 2000/04/03(月) 14:10:13
[[解決]]
ここの情報を参考に、自分でスクリプトを書き、動くようになりました。
みなさんのアドバイスのおかげで、どうにか解決できました。

もとさん、びーだまさん、weenさん、ありがとうございました。

もと。 2000/04/03(月) 19:41:17
#解決してるのに申し訳ないですけど・・・
びーだまさん、weenさん、参考になりました。
ありがとうございました。
なるほど、unpackについていろいろ理解が深まった感じです。
具体的な例を見せていただけたので、わかりやすかったです。

># ちなみに、プログラマーって・・については、私の職場ではいわゆる
># 現役プログラマはおそらくほとんど20代ですね。30代は前半がチラホラ
># の様です。それより上はマネジメントが主になるって感じでしょうか。
やっぱりそうなんですね。
#僕も早く有能な人間になりたいです。
unpackの使い方についてはさっぱりだったので、
わからなかったのが1つ減って良かったです。
#高専の教科書があまりにショボいので落胆気味。
#1年だからだろうけど。

[上に] [前に] [次に]