BMP形式について、改めて書き直しています。以下を参照ください。

画像ファイルの扱い方 (5) -BMP(DIB)形式 (1)

作成:

BMP(DIB)形式

さて、前回は UNIX 系列の OS で標準的に用いられることの多い PPM 形式について説明しました。
しかし、世の中のコンシューマー OS はほとんどが Windows 、私がメインに使っているのも Windows です。
そこで、今回から Windows で標準的に用いられている BMP 形式、 いわゆるビットマップ形式のファイルフォーマットとその扱い方を書いてみたいと思います。 (たまたま知ってるだけですけど)

この形式はカラーパレット(もしくはカラーテーブルとかインデックスカラー)と呼ばれる画像内部で使う色を定義した領域や、 解像度情報などを持っているために、デバイスを超えて利用できる(らしい)ので、その意味を込めて DIB(device-independent bitmap デバイスに依存しないビットマップ) という名前で呼ばれたりもします。 (プログラム的にはこの呼び方の方が多いようで、BMPとかビットマップとかで検索しても扱い方はなかなか出てこない)
カラーパレットを持った形式を DIB(拡張子.dib)、持たない形式をデバイス依存ビットマップ(拡張子.bmp)と使い分けたりもするみたいですが、 一般にはビットマップ(拡張子.bmp)で両方をまとめて扱っているみたいです。 (私自身、 BMP は Windows を使い始めた頃から知っていましたが、 DIB については BMP のファイル形式を調べるまでは知りませんでした。 私だけ特別って訳じゃないですよね・・・)
ちなみに Paint Shop Pro 6.0 で .bmp で保存したファイルと .dib で保存したファイルを比較すると解像度情報以外は全く同じでした。

実はビットマップには様々なバリエーションがあって、例えば OS/2 形式と Windows 形式というものがあります。 また詳しくは知りませんが BITMAPV4、BITMAPV5 っていうのもあるらしいです。
全部は分からないので、一般に使われているであろう、 Windows 形式について説明します。 (とりあえず、私が自前関数で扱ってみたビットマップファイルはすべてこれでした。)
それと、この形式でも、画像のビット情報をそのまま格納するのではなく RLE(ランレングスエンコーディング)という圧縮をかける形式もありますが、(さらに RLE4 や RLE8 といった種類があるらしい) この形式のエンコード、デコードの方法を知りませんので説明できません。 RLE っていうのは繰り返しの情報を圧縮することによってデータ量を減らす古典的な方法らしいですが・・・ (さらにこの場合拡張子が .rle になることもあるらしい)

さて、先に言ったように Windows で標準的な画像形式ですので、 Windows 上の開発環境やライブラリにはこれを一発で読み込めるような関数や格納用構造体があらかじめ用意されていたり、 Window に表示するための手続きにしても、ファイルを読み込んだほとんどそのままのビット配列を渡せば 表示してくれるように設計されていたりします。

※参考までにRayさんから頂いたVisual C++のTipsを紹介しておきます。

・Bmpファイルのロード方法
    →参考  DirectXのDDUtil.cpp内DDLoadBitmap()

HBITMAP hbm;
/* ロードするファイル名をpFileNameに入れておく */
hbm = (HBITMAP)LoadImage( NULL, pFileName, IMAGE_BITMAP,
                          0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION );
/* hbmを利用 */
DeleteObject( hbm );

とはいえ、自分で扱えるようになると、何かと便利で融通が利くようになりますし、 ソースレベルでプラットホーム非依存のコードも書けます。 最終的には外部のライブラリを使うにしても、内部構造を知っていた方が応用の幅も広がります。

まあ、「扱えるようになるとうれしい」とか「外部のライブラリに依存するのは気に食わん」っていうだけかもしれませんが・・・
ともかく、いってみましょう。

予備知識

以下、 Visual C++ の構造体の形式でデータのフォーマットを示すことがありますが、それに必要な事項について説明しておきます。
Visual C++ のオリジナルデータ型が出てきますが、それは windef.h や winnt.h などのファイルで定義されていて、必要な部分だけ抽出すると

typedef unsigned char       BYTE;
typedef unsigned short      WORD;
typedef unsigned long       DWORD;
typedef long                LONG;

早い話、 BYTE は符号なし1バイト、 WORD は符号なし2バイト、 DWORD は符号なし4バイト、 LONG は符号あり4バイトです。 (long とか short とかのバイト数は処理系依存だが、 Windows 上でしか扱わないためこれでいい訳だ)

もう一つ、多倍長整数がファイルに出力されている場合、下位バイトが最初にくるように格納される。
つまり、0x11223344(蛇足:0xってのは16進数)という4バイトの整数があったとすると、 ファイルの先頭から、 0x44、0x33、0x22、0x11 という順に格納されている。
これはビット演算などでの右シフト、左シフトといった表現や概念とは反対になっているので注意が必要です。
とはいえ、別に変わったことをやってるわけではなく、 これは Intel 系アーキテクチャのメモリ上でのデータと同様の扱いです (というか、処理の簡単化のためにメモリをそのまま書き出した形式を採用したのだろう)。例えば、

include <stdio.h>
main(){
  char a[]={0x44,0x33,0x22,0x11};
  int *p=(int*)a;
  prinf("%#x\n",*p);
}

とすると、 0x11223344 と表示されます。 この手のテストはハードウェア的な実体を見ることができるので、 いろいろ試してみると面白いかもしれません。

処理系依存っていうか、ハードウェアのアーキテクチャに依存するのでどこでも同じ結果であるとは限りません。 このように下位のバイトから格納する方式をリトルエンディアンといい、逆に格納されるものはビックエンディアンといいます (未確認ですが Mac の PowerPC はこれらしい、他にはネットワーク関係ではビックエンディアンが基準らしい)。 本当に移植性の高いプログラムを作成したい場合は、このような特徴に依存した記述を用いるべきではありません。

ちなみに、画像データの方で出てきますが、1バイト内部のビットの並びは 上位ビットが先頭に来ていると考えて格納していたりします。何となく一貫性がない様な気が・・・

ファイルヘッダ

ファイルヘッダをそのまま格納できる構造体が Visual C++ の wingdi.h などで定義されているので、まずそれを示します。

typedef struct tagBITMAPFILEHEADER {
        WORD    bfType;
        DWORD   bfSize;
        WORD    bfReserved1;
        WORD    bfReserved2;
        DWORD   bfOffBits;
} BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER{
        DWORD      biSize;
        LONG       biWidth;
        LONG       biHeight;
        WORD       biPlanes;
        WORD       biBitCount;
        DWORD      biCompression;
        DWORD      biSizeImage;
        LONG       biXPelsPerMeter;
        LONG       biYPelsPerMeter;
        DWORD      biClrUsed;
        DWORD      biClrImportant;
} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;

と、まあ、なぜか2つに分かれています。
では、各データの意味を説明します。ついでにバイト数も示しました。±がついているのは符号付きという意味です。

メンバサイズBITMAPFILEHEADER
bfType2ファイル識別子、"BM"というASCII文字列
bfSize4ファイル全体のサイズ(バイト数)、ヘッダ+画像データ
bfReserved12将来の拡張のための予約、通常0にします
bfReserved22将来の拡張のための予約、通常0にします
bfOffBits4ファイルの先頭から画像データが始まるまでのオフセット(バイト数)、ヘッダ+カラーパレット
メンバサイズBITMAPINFOHEADER
biSize4この構造体のサイズ、バリエーションにより違うため、この場合40に固定
biWidthビットマップの幅(ピクセル数)
biHeightビットマップの高さ(ピクセル数)
biPlanes2プレーン数(レイヤー構造に対応するつもりか?)、1に固定
biBitCount21ピクセルあたりのビット数、フルカラー:24、256色:8、16色:4、2色:1
biCompression4圧縮形式、無圧縮は0
biSizeImage4イメージのサイズ
biXPelsPerMeter横方向の解像度、単位は1メートルあたりの画素数(PelsPerMeter だからそうだと思うけど自信なし)
biYPelsPerMeter縦方向の解像度、単位は1メートルあたりの画素数
biClrUsed4使用している色の数(ビット数とかではなく具体的な数)、0にしておくと無難
biClrImportant4重要な色の数、通常0か最大色数のどちらか

このファイルフォーマットを見て思うことは「無駄」「冗長」「何に使うか分からん」っていうデータが多いことでしょう。 前に紹介したPPMのシンプルさとは比べものになりません。
まあ、処理の合理性と、遠い将来までの拡張性を考えた結果かもしれませんが・・・
一番謎なのが最後の重要な色数って、画像の中に重要でない色があるのか疑問ですし、あったとしてそれを何に役立てるんでしょう? Windows9xの起動ロゴlogo.sysは中身がビットマップで、 この重要な色数以外にある色をぐるぐる変化させてプログレス表示に使っていたみたいですが、まさかそのためだけとか・・・

カラーパレット

カラーパレット部分で色を表現するのに使われるデータも構造体で表現されています。 wingdi.hなどに定義されています。

typedef struct tagRGBQUAD {
        BYTE    rgbBlue;
        BYTE    rgbGreen;
        BYTE    rgbRed;
        BYTE    rgbReserved;
} RGBQUAD;

この構造体1つで1色を表します。 4バイト用意してあるのでαチャネルにも対応しているのかなと思いきや、 4バイト目は予約領域で使われていません・・・
このカラーパレット領域に書かれた順に0から番号が振られ、 カラーパレットを利用する形式のデータはこの番号を指定する形で画素情報が格納されます。

画像データ

画像データですが、こいつもなんだかくせ者です。
画素情報は左から右へ走査した横方向のデータが、下から上の順で格納されています。 ほとんどの画像ファイルのファイルフォーマットは左から右の横方向のデータを上から下へ格納していますが、こいつは上下が逆です。 (ネットでダウンロードしつつ徐々に表示するという類のことがやりにくい形式だ)
ただし、上から下へ向かって格納していくタイプもあるそうです。(これをトップダウンDIBというらしい) この場合は縦方向のサイズが負の数(2の補数かな?)として格納されているそうです。

また、横方向のデータは4バイトで区切られるようになっていないといけません。 データが足りない場合は適当な値(たいてい0)がパディングされます。

そして、画素の情報ですが、フルカラーのカラーパレットがない場合は、BGRの順でそのまま格納されます。 (これはカラーパレットの色情報と同じ順序、ただしリザーブ領域はありません)
パレットがある場合、格納されたパレットの先頭を0とした番号で指定します。
16色、2色の場合は、ビット単位で連続して格納しますが、 1バイトの内部においては上位ビットから埋めていきます。(なんてややこしい)

まあ、対応するタイプを限定しているとはいえ、格納しているデータはシンプルなくせに、 かなりややこしいルールのあるフォーマットです。
ああ、なんだかサンプルコード作るがめんどくさくなってきた・・・
次に更新されるのはいつのことやら(笑)



2686877 Today: 728 Yesterday: 881
Twitter
広告