bmpファイルについて

Share
アプリケーションプログラムを作成する際に、bmpファイルを読み込む処理がしたいのであれば各種ライブラリ関数がある場合はそれを利用するのが一番確実な方法です。しかし、今回は勉強もかねて読み込み関数を自分で作成しましたので、このbmpファイルの読み込み処理について簡単に述べたいと思います。

bmpファイルはWindowsで広く使われているファイルフォーマット形式だと思います。もちろん、ファイルフォーマットなので、他のコンピュータで使うことも可能です。代表的な特徴としては、
  • 色深度が選べる:1,4,8,16,24,32
  • 圧縮が可能:RLE,JPEG、PNG
  • 色データをパレットデータで持つことが可能(色深度1,4,8の時)
  • パレットデータの色数を指定することができる
などがあると思います。
そのほかにも、このファイルフォーマットは多くのオプションを持っています。詳しくは、MSDNで調べていただくのがよいかと思います。

bmpファイルフォーマットの概要

まず、最初はbmpファイルの構造を見てみます。

ビットマップファイルの構造

ファイルの先頭から、図中の名称の構造体が書き込まれている構造になっています。また、BITMAPINFOHEADERの部分は、
  BITMAPCOREHEADER
  BITMAPV4HEADER
  BITMAPV5HEADER
といった他の構造体が入ることもあります。
(今回作成したプログラムではこれらのヘッダには対応していません。)

実際のビットマップファイルの例を示します。

bmpファイルの例
ビットマップファイルの例
幅205pixel、高さ154pixel、色深度8bit、圧縮なしファイルです。

bmpファイルのダンプリスト
ビットマップファイルの先頭のダンプリスト
BITMAPFILEHEADERおよびBITMAPINFOHEADERを含むファイルの先頭約100バイトのダンプリスト

上が画像データ、下がそのファイルのヘッダのダンプリスト(一部)です。ダンプリストの黄色の部分は、BITMAPFILEHEADERに該当し、緑色の部分はBITMAPINFOHEADERに該当しています。白色の部分はパレットデータに該当しますがリストが長いため途中で切れています。あとから説明する構造体のメンバの部分は赤や青などの枠で囲っています。ヘッダ部分と構造体のメンバを見比べれば、各データがどのように入っているか簡単にわかるかと思います。

パレットデータの部分は、この画像が使用している色を表すデータの配列です。色深度が1,4,8以下の場合はパレットデータが必ず存在します。16bit以上の色深度は直接画像データが色を表すためパレットデータはありません。また、パレットデータの個数は最大で2^n(nは色深度)個となります。

画像データは次のようなフォーマットになっています。
画像データの並び方

基本的には最下行の左端から始まり右端までデータを並べて、次に1行上に移動して、また左端からデータを並べる・・・という構造です。
ただ、ビットマップファイルの場合、1行のデータの終わりは必ず4バイト境界に一致するという制約があります。このため1行のデータの終わりに空白データが入れられる場合があります(詳しくは後述します)。
また、最下行からデータが格納されると書きましたが、これも設定により最上行から最下行へ向けての順序にすることもできます(必要な設定方法は後述します)。


bmpファイルフォーマットの詳細

次に、上記の各構造体とデータについて、読み込み処理に必要な箇所について説明します。

BITMAPFILEHEADER
bfType(ダンプリストの①)
ビットマップファイルの先頭は必ず"BM"(16進数であらわすとBが0x42、Mが0x4d)になっています。これを判定するためにこの構造体のbfTypeメンバを使います。
(なお、普通のWindowsのPCであれば、構造体単位でファイルからデータを読み込むとこのメンバの値は"MB"になると思います。これは、構造体のbfTypeメンバはWORD型で2バイトの大きさがあることに関係します。コンピュータの種類により、複数のバイトからなるデータの読み込み順/書き込み順が異なります。一般的に、リトルエンディアン/ビッグエンディアンと呼ばれるものです。Windowsの場合はリトルエンディアンであり、最小バイトが先に書かれる規則になっています。このため、ファイル内のバイト順とメモリに読み込んだときのバイト順が入れ替わるためです。)

bfOffBits(ダンプリストの②)
画像データの開始位置を示します。この値を使うことにより簡便に画像データへアクセスできます。

BITMAPINFOHEADER
bfSize(ダンプリストの③)
この構造体のサイズです。また、このメンバは上述した4種類の構造体で共通して定義されています。種類ごとにサイズが異なることを利用して、これでどのタイプの構造体かを判定します。
 構造体の種類大きさ(バイト) 
 BITMAPCOREHEADER 12
 BITMAPINFOHEADER  40
 BITMAPINFOV4HEADER 108
 BITMAPINFOV5HEADER 124

biWidth(ダンプリストの④)
画像幅です。

biHeight(ダンプリストの⑤)
この値の絶対値が画像高さです。また、このbiHeightの正負によって、データの縦軸方向の並び順が決まります。
 正:データは下から上への順番で並びます(ボトムアップ)
 負:データは上から下への順番で並びます(トップダウン)
それぞれ、原点を左下にとる/左上にとる と表現する場合もあります。

biBitCount(ダンプリストの⑥)
1画素をあらわすためのビット数。色深度と呼んだりもします。ビットマップの読み込みが複雑になるのも、これが可変だからではないでしょうか?ビット数によりその値の意味は若干異なります。

 1,4,8ビット パレットデータ配列のインデックスをあらわします。 
 16,24,32ビット R,G,B画素値毎の値をあらわします。
  16bit:各色5bit
  24bit:各色8bit
  32bit:各色8bit

また、ファイル内の色データの並び方は、B,G,Rの順番です。
なお、16/32ビットが取りうる各色のbit数は上記の値以外へも可変です(デフォルトは上述のRGBが各5bit、各8bitです)。たとえば、32bitビットマップでは、各色に10bitを割り当てて高精細画像を表現したりすることも可能です。この場合はbiCompressionメンバでビット数を変更していることを示す値(BI_BITFIELDS)を設定する必要があります。
biCompression(ダンプリストの⑦)
圧縮の有無を表します。今回のプログラムでは圧縮なしのみへ対応します。
VS2005付属のMSDNによると、画像データとしてJPEG/PNGを用いた場合にもこのメンバに適切な値を設定するようです。

biClrUsed
パレットデータを使っている場合、このファイルが持っている色数です。
今回のプログラムでは、パレットデータは持ちうるパレット数の全てを持つことを前提にしています。
もし、パレットデータで使う色数が最大数より少なくなるようでしたらこの値を設定する必要があります(たとえば、8bitビットマップで256個未満のパレットしか用意しないような場合です)。

パレットデータ
前述のように色深度に応じた個数の色データが入ります。1個のデータは、B,G,R,未使用 の並びの4バイト(1色1バイト)で構成されています(ダンプリストの⑧)。Windowsであれば、RGBQUAD構造体で表現されています。なお、今回は未対応ですが、BITMAPCOREHEADERの場合、パレットデータは、RGBTRIPLE構造体で表現されます。

画像データ
画像データのフォーマットは前述のとおりです。また、既に述べたように、1行のデータの終わりは必ず4バイト境界(1行のバイト数が4で割り切れることを意味します)に一致する必要があるということです。これは色深度が何ビットであっても適応されます。
たとえば、1bitビットマップの場合で、画像幅が35pixelとすると、1行のデータは、
 35×1=35bit=4byteと3bit
となり、端数を切り上げると、5バイト になります。これを4バイト境界に合わせる必要があるので、1行を表すデータ数は8バイトとなります。したがって、読み込み時には、この不要な3バイトと5ビット(8バイト=64ビット、64-35=29ビット、29ビット=3バイト5ビット)を読み飛ばす処理が必要です。
同じことが他の4,8,16,24ビットにも発生します。ただし32bitビットマップは、1画素が常に4バイト(32bit)で表現されているため、必ずこの条件をみたしています。
ビットマップのフォーマットは複雑ですので、全てに対応しようとするとなかなか大変だと思います。それでも、これぐらいのヘッダ情報がわかれば、典型的なビットマップファイルの読み込み関数を作ることが可能ではないかと思います。


読み込みの手順

今回は下記に示すような手順でbmpファイルの読み込み関数を作成しました。

bmpファイルの読み込みの流れ図

上記の流れ図のなかの『bit数に応じた画像データ読み込み関数』を呼ぶという部分では、1行のデータが4バイト境界に一致することを踏まえて、横幅の画素数とbitCountから次の値を算出して読み込み処理に使っています。
  • 1行のデータ数
  • 有効な画像データ数
また、1画素あたりのbit数が4bit/16bitについては、必要なデータを取り出すために共用体を利用しました(1bitの場合はビットシフトで対応)。
このあたりの処理の詳細を知りたい方は、設計書とソースコード(Image.cppの部分)を参照してください。


参考

今回プログラムを作るために参考にさせていただいたサイトなどを紹介しておきます。

[1] BMPファイル仕様-ルーチェ's Homepage 
[2] DIB(デバイス独立ビットマップ)の作成 
[3] MSDN