home / uni / ps / bmp PostScript言語 / BMP画像

 マイクロソフトのビットマップ形式。 非圧縮のものと、RLE圧縮のものがあり、さらに圧縮形式の部分に4文字IDコードを埋めると他の形式のコンテナとしても使えるが、一般的ではない。 ここでは非圧縮のもののみ扱う。 規格はMSDNにあるが、どちらかというとファイル形式というより、内部形式をファイルに書き出してみましたー、っていう書き方で分かりづらい。 圧縮はされていないものの、エンディアンが逆なので、特にパレットやRGB24ビット形式がそのままでは使えない。

home / uni / ps / bmp BMPファイルフォーマット

 まずBITMAPFILEHEADER構造体があり、続いてBITMAPINFOHEADER構造体、その後にパレットが続く。 BITMAPINFOHEADER構造体には自身のサイズを格納するフィールドがある。 構造体のサイズなのでサイズフィールドも含んだサイズになる。 通常は40バイトだが、拡張されたBITMAPV4HEADER、BITMAPV5HEADERというのもあり、これらは40バイトよりも大きい。 先頭40バイトは互換性があるので、40「以上」であることを確認する必要がある。

 逆にBITMAPCOREHEADERという古いタイプの構造体もあり、これは12バイトしかない。 この構造体の場合もBitCountまでのメンバはあるので同様に表示することができるが、ここでは割愛する。

 数値はすべてリトルエンディアンだが、1バイトに満たない画素データはMSB側から詰められる。 また、BMPファイルは標準では下から上へデータが並ぶが、Heightフィールドが負の場合は上から下の順になる。 実装は簡単にできる(/Heightが負だったら正に直して、imgdictを調整する)が、テストデータ持ってないので略。 16bpp・32bppの場合はCompressionが3(BI_BITFIELDS)のことがあり、この場合はRGB各色成分がピクセルデータのどのビット範囲かを示すデータも入っている。 面倒なので略。
データサイズ [bytes]備考
"BM"2 バイト列で0x42 0x4D
filesize4 ファイル全体のサイズ
reserved4 未使用
OffBits4 ファイル先頭からビットマップ先頭へのバイト単位でのオフセット
Size4 BITMAPINFOHEADER構造体のサイズ。通常40以上
Width4 画像の幅
Height4 画像の高さ。正の場合はボトムアップ、負の場合はトップダウン
Planes2 プレーン数。通常は1
BitCount2 ピクセルあたりのビット数
Compression4 圧縮方式。非圧縮の場合は0
SizeImage4 画像データのサイズ。0の場合もある(のであてにならない)
XPelsPerMeter4 横方向の解像度(1メートルあたりのピクセル数)
YPelsPerMeter4 縦方向の解像度(1メートルあたりのピクセル数)
ClrUsed4 実際に使っている色数。0の場合は使用可能な最大の色数を使用
ClrImportant4 画像の再現に重要な色の数
SizeからのサイズがSizeバイトになるまで読み飛ばす
Blue(0)1 青成分
Green(0)1 緑成分
Red(0)1 赤成分
Reserved(0)1 未使用
ClrUsedだけ繰り返し

 Compressionが0の場合、画像の形式はBitCountで決まり、以下の通り。
BitCount意味
1白黒2階調。パレットあり。MSB側が左のピクセルになる。
44ビットインデックスカラー。パレットあり。MSB側が左のピクセルになる。
88ビットインデックスカラー。パレットあり。
16RGB555形式。ピクセルデータはリトルエンディアンで格納されており、b0〜b4が青、b5〜b9が緑、b10〜b14が赤。b15は使用されない。パレットが存在する場合もある。
2424ビットフルカラー。各ピクセルは青・緑・赤の順に並ぶ。パレットが存在する場合もある。
3224ビットフルカラー。各ピクセルは青・緑・赤の順に並び、その次に使われないバイトが1バイト入る。パレットが存在する場合もある。

 基本的にパレットの後ろに画像本体が続くが、16bpp以上でもパレットが存在する場合があるので、Offbitsを使ってシークしてしまった方がよい。 また、1ラインのデータは4バイトの倍数になっていなければならない。 なっていない場合は詰め物が入る。

home / uni / ps / bmp preparebmp

 以上を元に適当に作ったのがこれ。 適当すぎて24bppのときの横幅の判定が間違ってたので直しました(02 Dec 2013)。

% errobj errname [ err ] -
/err {
    errordict exch get exec
} def

% value obj key [ vokput ] -
/vokput { 3 -1 roll put } def

% file numbytes [ readbytesLE ] int
/readbytesLE {
    string readstring {
        [ 1 0 ] exch {
            % [ base sum ] val
            1 index 0 get mul
            1 index 1 get add
            1 index 1 vokput
            dup 0 get 256 mul
            1 index 0 vokput
        } forall
        1 get
    } {
        stop
    } ifelse
} def

% src num [ readbmppalette ] str
/readbmppalette {
    4 dict begin
    /num exch def
    /src exch def
    /idx 0 def
    /buf 4 string def
    num 3 mul string
    num {
        src buf readstring not { /readbmppalette /ioerror err } if
        1 index 2 idx add 2 index 0 get put
        1 index 1 idx add 2 index 1 get put
        1 index idx 2 index 2 get put
        pop
        /idx idx 3 add def
    } repeat
    end
} def

% int base [ roundup ] int
/roundup {
    1 sub exch 1 index add exch not and
} def

% int [ bmppaddig ] int
/bmppadding {
    dup 4 roundup exch sub
} def

/preparebmp {
    9 dict begin
    /src exch def
    /imgdict <<
        /ImageType 1
        /Width 0
        /Height 0
        /ImageMatrix matrix
        /MultipleDataSources false
        /DataSource null
        /BitsPerComponent 8
        /Decode [ 0 1 0 1 0 1 ]
        /Interpolate false
    >> def
    {
        src 2 string readstring not { stop } if (BM) ne { stop } if
        src 8 string readstring not { stop } if pop
        /offbits src 4 readbytesLE def
        /bisize src 4 readbytesLE def
        bisize 40 lt { stop } if
        src 4 readbytesLE imgdict /Width vokput
        src 4 readbytesLE imgdict /Height vokput
        src 2 readbytesLE 1 ne { stop } if
        /bitcount src 2 readbytesLE def
        src 4 readbytesLE 0 ne { stop } if
        src 12 string readstring not { stop } if pop
        /clrused src 4 readbytesLE def
        src 4 string readstring not { stop } if pop
        src dup fileposition bisize add 40 sub setfileposition

        bitcount 24 eq {
            % 24bpp, RGB888
            imgdict /BitsPerComponent 8 put
            imgdict /Decode [ 0 1 0 1 0 1 ] put
            /padding imgdict /Width get 3 mul bmppadding string def
            /buf 3 string def

            /DeviceRGB setcolorspace
            /pixcount 0 def
            [ currentdict { begin
                pixcount imgdict /Width get ge { % fixed on 02 DEC 2013
                    padding length 0 ne {
                        src padding readstring
                        not { /bmpproc /ioerror err } if
                        pop
                    } if
                    /pixcount 0 def
                } if
                src buf readstring not { /bmpproc /ioerror err } if
                dup 0 get 1 index 2 get
                2 index 0 vokput 1 index 2 vokput
                /pixcount pixcount 1 add def
            end } /exec cvx ] cvx
            imgdict /DataSource vokput
        } { bitcount 8 eq {
            % 8bpp, IDX8
            imgdict /BitsPerComponent 8 put
            imgdict /Decode [ 0 255 ] put
            imgdict /Width get  % linesize in bytes
            dup string /buf exch def
            bmppadding string /padding exch def

            % read palette
            clrused 0 eq { /clrused 256 def } if
            src clrused readbmppalette
            [ /Indexed /DeviceRGB clrused 1 sub 5 -1 roll ] setcolorspace

            [ currentdict { begin
                src buf readstring
                not { /bmpproc /ioerror err } if
                padding length 0 ne {
                    src padding readstring pop pop
                } if
            end } /exec cvx ] cvx
            imgdict /DataSource vokput
        } { bitcount 4 eq {
            % 4bpp, IDX4
            imgdict /BitsPerComponent 4 put
            imgdict /Decode [ 0 15 ] put
            imgdict /Width get 1 add 2 idiv % linesize in bytes
            dup string /buf exch def
            bmppadding string /padding exch def

            % read palette
            clrused 0 eq { /clrused 16 def } if
            src clrused readbmppalette
            [ /Indexed /DeviceRGB clrused 1 sub 5 -1 roll ] setcolorspace

            [ currentdict { begin
                src buf readstring
                not { /bmpproc /ioerror err } if
                padding length 0 ne {
                    src padding readstring pop pop
                } if
            end } /exec cvx ] cvx
            imgdict /DataSource vokput
        } {
            stop
        } ifelse } ifelse } ifelse

        src offbits setfileposition
        imgdict /ImageMatrix [
            imgdict /Width get 0 0 imgdict /Height get 0 0
        ] put
        imgdict
    } stopped {
        $error /newerror get {
            stop
        } {
            /preparebmp /ioerror err
        } ifelse
    } if
    end
} def

 errvokputpreparejpegと共通。 数値はリトルエンディアンなので、getbytesbegetbytesLEとなる。 パレットは4バイトずつ読み込んで、そのうち3バイトを入れ替えながらsetcolorspace設定用の文字列に設定する。 roundupは指定された値の単位への切り上げ、bmppaddingはラインを読み込んだあとに必要なパディングのバイト数を計算する。 実際の画像データの読み込みはピクセルあたりのビット数によって若干異なる。

 8bppの場合が一番簡単で、ラインの後、4バイトごとに整合を取るだけである。 4bppの場合は読み込むバイト数を計算するときに切り上げが必要で、さらに4バイトごとに整合を取る必要がある。 ピクセルの順はMSBが先なので、ビット単位での入れ替えは不要。 24bppの場合はラインを4バイト単位に整合する他に、データが青・緑・赤の順に並んでいるので、これを赤・緑・青の順に並び替えて渡す必要がある。 このため、24bppだけはプロシージャを別にする必要があるだろう。 ここでは3つのパターンに分けて処理しているが、4bppと8bppの読み込みプロシージャはまったく同じである。

 使い方は例によってpreparejpegと同じ。


Copyright (C) 2012 You SUZUKI

$Id: ps-bmp.htm,v 1.3 2013/12/01 16:34:48 you Exp $