マイクロソフトのビットマップ形式。 非圧縮のものと、RLE圧縮のものがあり、さらに圧縮形式の部分に4文字IDコードを埋めると他の形式のコンテナとしても使えるが、一般的ではない。 ここでは非圧縮のもののみ扱う。 規格はMSDNにあるが、どちらかというとファイル形式というより、内部形式をファイルに書き出してみましたー、っていう書き方で分かりづらい。 圧縮はされていないものの、エンディアンが逆なので、特にパレットやRGB24ビット形式がそのままでは使えない。
まず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 | 
| filesize | 4 | ファイル全体のサイズ | 
| reserved | 4 | 未使用 | 
| OffBits | 4 | ファイル先頭からビットマップ先頭へのバイト単位でのオフセット | 
| Size | 4 | BITMAPINFOHEADER構造体のサイズ。通常40以上 | 
| Width | 4 | 画像の幅 | 
| Height | 4 | 画像の高さ。正の場合はボトムアップ、負の場合はトップダウン | 
| Planes | 2 | プレーン数。通常は1 | 
| BitCount | 2 | ピクセルあたりのビット数 | 
| Compression | 4 | 圧縮方式。非圧縮の場合は0 | 
| SizeImage | 4 | 画像データのサイズ。0の場合もある(のであてにならない) | 
| XPelsPerMeter | 4 | 横方向の解像度(1メートルあたりのピクセル数) | 
| YPelsPerMeter | 4 | 縦方向の解像度(1メートルあたりのピクセル数) | 
| ClrUsed | 4 | 実際に使っている色数。0の場合は使用可能な最大の色数を使用 | 
| ClrImportant | 4 | 画像の再現に重要な色の数 | 
| SizeからのサイズがSizeバイトになるまで読み飛ばす | ||
| Blue(0) | 1 | 青成分 | 
| Green(0) | 1 | 緑成分 | 
| Red(0) | 1 | 赤成分 | 
| Reserved(0) | 1 | 未使用 | 
| ClrUsedだけ繰り返し | ||
Compressionが0の場合、画像の形式はBitCountで決まり、以下の通り。
| BitCount | 意味 | 
|---|---|
| 1 | 白黒2階調。パレットあり。MSB側が左のピクセルになる。 | 
| 4 | 4ビットインデックスカラー。パレットあり。MSB側が左のピクセルになる。 | 
| 8 | 8ビットインデックスカラー。パレットあり。 | 
| 16 | RGB555形式。ピクセルデータはリトルエンディアンで格納されており、b0〜b4が青、b5〜b9が緑、b10〜b14が赤。b15は使用されない。パレットが存在する場合もある。 | 
| 24 | 24ビットフルカラー。各ピクセルは青・緑・赤の順に並ぶ。パレットが存在する場合もある。 | 
| 32 | 24ビットフルカラー。各ピクセルは青・緑・赤の順に並び、その次に使われないバイトが1バイト入る。パレットが存在する場合もある。 | 
基本的にパレットの後ろに画像本体が続くが、16bpp以上でもパレットが存在する場合があるので、Offbitsを使ってシークしてしまった方がよい。 また、1ラインのデータは4バイトの倍数になっていなければならない。 なっていない場合は詰め物が入る。
以上を元に適当に作ったのがこれ。 適当すぎて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
err・vokputはpreparejpegと共通。 数値はリトルエンディアンなので、getbytesbeはgetbytesLEとなる。 パレットは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 $