BMPファイルをHex Editorで開くと、最初の14バイトはこうなっている。
42 4D 46 00 00 00 00 00 00 00 36 00 00 00
これが BITMAPFILEHEADER の全体だ。42 4D はASCIIのシグネチャ「BM」、次の4バイトはファイルサイズを表す。46 00 00 00 はリトルエンディアンで 70 を意味する。その次の4バイトはReservedフィールドで常に0、最後の4バイトはピクセルデータへのオフセットだ。36 00 00 00 は 54 を意味し、ピクセル配列は54バイト目から始まる。
圧縮マーカーはない。カラープロファイルもない。アルファチャンネルもない。あるのはシグネチャ、サイズ、オフセットだけだ。BMPはその名が示すとおり、ビットをそのまま並べたシンプルなフォーマットだ。
BMPとは何か
BMPは Bitmap(ビットマップ) の略で、正式には Device Independent Bitmap(DIB、デバイス非依存ビットマップ) と呼ばれる。Microsoftは1990年にWindows 3.0で導入し、特定の表示デバイスに依存しない形でラスター画像を保存できるようにした。DIB以前、Windowsでは Device Dependent Bitmap(DDB、デバイス依存ビットマップ) が使われていた。DDBのピクセル形式は搭載されているグラフィックスカードに依存しており、あるマシンで保存したファイルが別のマシンでは読めないこともあった。
BMPは、どのデコーダーでも画像を再構成できるだけのメタデータをファイル内に保持することでこの問題を解決した。幅、高さ、ビット深度、カラーパレット、圧縮方式、そして生のピクセル配列だ。OSはヘッダーを読み込み、バッファを確保してピクセルをメモリにコピーする。24ビット非圧縮画像では、ピクセルデータは通常、下から上へと行ごとに書かれたRGBトリプレットで、4バイト境界にパディングされている。
このシンプルさが、BMPを10年以上にわたるWindowsのデフォルト画像フォーマットにした。Paintbrush、Windowsの壁紙エンジン、初期のスキャナー、そして数多くの社内ツールがBMPをネイティブに扱っていた。
ファイル構造
BMPファイルは4つの部分からなり、順に並んでいる。
BITMAPFILEHEADER (14バイト)
BITMAPINFOHEADER (クラシック版は40バイト)
Color Table (インデックスカラーモード用、オプション)
Pixel Data (実際の画像)
BITMAPFILEHEADER(14バイト)
Bytes 0-1: Signature "BM" (0x42 0x4D)
Bytes 2-5: FileSize (uint32, リトルエンディアン)
Bytes 6-9: Reserved (常に0)
Bytes 10-13: Offset (uint32, ファイル先頭からピクセルデータまでのオフセット)
シグネチャは必ずしも「BM」ではない。Windowsはカーソルやアイコン系の派生形として「BA」「CI」「CP」「IC」「PT」もサポートしているが、実際に目にするBMPはほぼすべて「BM」で始まっている。
BITMAPINFOHEADER(40バイト、BITMAPINFOHEADER型)
Bytes 0-3: HeaderSize (40)
Bytes 4-7: Width (int32, ピクセル)
Bytes 8-11: Height (int32, ピクセル; 負の場合はトップダウン)
Bytes 12-13: Planes (1)
Bytes 14-15: BitCount (1, 4, 8, 16, 24, または32)
Bytes 16-19: Compression (0 = なし, 1 = RLE8, 2 = RLE4, 3 = ビットフィールド)
Bytes 20-23: ImageSize (非圧縮時は0)
Bytes 24-27: XpixelsPerMeter (物理解像度)
Bytes 28-31: YpixelsPerMeter (物理解像度)
Bytes 32-35: ColorsUsed (0 は 2^BitCount を意味する)
Bytes 36-39: ColorsImportant (0 はすべて重要を意味する)
後のWindowsバージョンでは、アルファマスク、カラースペース、ICCプロファイルに対応するために、52バイト、56バイト、108バイト、124バイトの大きなヘッダーが追加された。しかし、40バイト版が今も最も一般的に見かけるバージョンだ。
高さは方向を示す
Heightフィールドが正の場合、画像はボトムアップ(下から上)で保存される。ファイル内の0行目が、画像の最下段の行に対応する。Heightフィールドが負の場合、画像はトップダウン(上から下)で保存される。これはBMPの特徴的な仕様の一つで、他のほとんどのフォーマットはデフォルトでトップダウンになっている。
カラーテーブル
1ビット、4ビット、8ビット深度では、BMPはインデックス付きパレットを使う。各パレットエントリは4バイトで、青、緑、赤、そして予約済みのアルファバイト(ほとんどの場合無視される)の順だ。256色BMPでは、ヘッダーの直後に1,024バイトのカラーテーブルが存在する。その後に続くピクセルデータは色そのものではなく、そのテーブルへのインデックスになっている。
ピクセルデータのパディング
各スキャンラインは4バイトの倍数でなければならない。幅5ピクセルの24ビット画像では、1行あたり15バイト必要だが、これは16バイトに切り上げられる。この余分なパディングバイトは無駄な領域であり、BMPが非効率であるもう一つの小さな理由だ。
以下は、2 x 2 24ビット非圧縮BMPの完全な例だ。
Offset 0x00: 42 4D 46 00 00 00 00 00 00 00 36 00 00 00 -- BITMAPFILEHEADER
Offset 0x0E: 28 00 00 00 02 00 00 00 02 00 00 00 01 00 18 00 -- BITMAPINFOHEADER
Offset 0x22: 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00
Offset 0x32: 00 00 00 00 00 00 00 00
Offset 0x36: FF 00 00 00 FF 00 00 00 FF -- 最下段の行: 赤、緑、青(パディング込み)
Offset 0x3F: 00 FF FF 00 FF FF 00 00 00 -- 最上段の行: シアン、マゼンタ、イエロー(パディング込み)
BMP登場時にすでに存在したフォーマット
BMPは孤立して登場したわけではない。1990年までに、すでに複数の画像フォーマットが存在していた。
| フォーマット | 圧縮方式 | 色深度 | 透過 | 特許リスク | 典型的な用途 |
|---|---|---|---|---|---|
| GIF | LZW | 最大256色 | 1ビット | あり(LZW) | Webグラフィック |
| JPEG | DCT + Huffman | 24ビット | なし | ほぼなし | 写真 |
| TIFF | 複数(オプション) | 最大48ビット | あり | オプション | 印刷、スキャン |
| PCX | RLE | 最大24ビット | なし | なし | DOSゲーム、クリップアート |
| PICT | RLE | 最大32ビット | あり | なし | クラシックMac OS |
| BMP | なしまたはRLE | 最大32ビット | なし | なし | Windows壁紙、アイコン |
GIFはすでにWebのデフォルトだったが、そのLZW特許の影により商用利用はリスクを伴っていた。JPEGは写真向けに設計され、データを不可逆的に破棄する。TIFFは強力だったが過剰設計で、汎用TIFFリーダーを書くこと自体が一つのプロジェクトだった。PCXはDOSとともに衰退していた。
BMPの強みはシンプルさだった。他のフォーマットより小さい、速い、高性能というわけではない。追加のライブラリなしにWindowsが理解できる、ただそれだけのフォーマットだった。
BMPが優れていた理由
BMPには、その文脈において確かな利点があった。
デコーディングの複雑さがゼロ。 24ビット非圧縮BMPは、固定サイズのヘッダーを読み込んでピクセルをフレームバッファに直接コピーするだけで描画できる。Huffmanテーブルも、DEFLATEステートマシンも、プログレッシブパスも不要だ。これにより、CPU性能に限りのある組み込みシステムに最適だった。
特許の枷がない。 GIFのLZWとは異なり、BMPの圧縮は「なし」か「RLE」のいずれかで、いずれも権利が絡まない。これは、UnisysのGIFライセンスを回避したいオープンソースツールや商用ソフトにとって重要だった。
Windowsとの直接的な統合。 Win32 APIには、DIBセクションを中心に設計されたLoadBitmap、BitBlt、StretchBltがあった。開発者はBMPをメモリに読み込み、数行のCコードで画面にblitできた。
予測可能なメモリレイアウト。 非圧縮BMPのピクセルは画面メモリに直接マッピングされるため、1990年代のゲームエンジンやグラフィックツールはBMPをテクスチャ交換フォーマットとして使っていた。ファイルをメモリマップし、ピクセルオフセットを生のバッファとして扱えた。
BMPの弱点
BMPの強みは、同時に弱点でもあった。フォーマットは産業全体と同じように進化しなかった。
有効な圧縮がない。 非圧縮BMPは巨大だ。1920 x 1080 24ビット画像は5.93 MBになる。同じ画像をJPEG品質90で保存すると約300–500 KBだ。インターネット速度やストレージコストが制約となる中で、BMPは時代遅れになった。
RLE圧縮は弱い。 BMPはRLE8とRLE4のランレングス圧縮をサポートしているが、RLEは大きな均一な領域を持つ画像にしか効かない。写真やグラデーションはうまく圧縮できない。RLE圧縮されたBMPは実際には稀だ。
実質的な透過がない。 標準的なBMPにはアルファチャンネルがない。32ビットBMP派生形にはアルファバイトを含むものもあるが、多くのツールはそれを無視するか予約済みとして扱う。Webグラフィックやゲームスプライトでは、これによりPNGと比較してBMPは実用にならなかった。
アニメーションもメタデータもカラーマネジメントもない。 BMPはピクセルとそれ以外のほんの少しの情報しか保存しない。EXIFもなく、オリジナルのヘッダーにはICCプロファイルのサポートもなく、アニメーションフレームもない。ワークフローが高度化する中で、BMPは原始的なままだった。
ボトムアップの保存。 デフォルトのボトムアップ行順はデコーダーを混乱させ、変換に無駄なサイクルを費やす。メリットのない小さな摩擦だ。
BMPを置き換えたフォーマット
BMPの限界が、その後に登場したフォーマットを直接形作った。
PNG(1996年) は透過と圧縮の問題を解決した。可逆圧縮、8ビットアルファ、そしてTIFFよりシンプルな仕様を提供した。PNGはWebやクロスプラットフォームアプリケーションのほぼあらゆる場所でBMPを置き換えた。
JPEG(1992年) は写真の問題を解決した。DCTベースの非可逆圧縮により、写真を非圧縮BMPの10–20分の1のサイズにした。連続階調の画像であれば、JPEGがデフォルトとなった。
WebP(2010年) は、非可逆・可逆モード、アニメーション、アルファを1つのコンテナにまとめた。可逆WebPは通常、PNGより20–30%小さく、PNG自体がBMPを圧倒的に上回る。
HEIC/HEIF(2013年) は、HEVCベースの圧縮を静止画にもたらした。JPEGより高画質で小さなファイルサイズを実現し、アルファや深度マップにも対応する。今やAppleデバイスのデフォルトとなっている。iPhoneが写真を保存する際、通常はHEICだ。
これらのフォーマットはそれぞれ、BMPが特定のユースケースで十分でなくなったために存在する。
BMPが今も使われる場所
BMPは消えたわけではない。ただ、そのシンプルさが非効率性より価値のあるニッチに移っただけだ。
Windows内部。 Windowsのブート画面、レガシーなダイアログ、一部のシステムリソースは今もBMPを使っている。Win32 GDI APIはDIBデータを期待しており、これらのコードパスを書き直す価値はMicrosoftにとってない。
組み込みシステムとマイクロコントローラー。 ArduinoやSTM32に搭載された小さなLCDには、PNGをデコードするRAMやCPUがない。16ビット非圧縮BMPは、ほとんどコードなしでフレームバッファに直接コピーできる。
医用・科学画像。 一部のレガシーDICOMビューワーやラボ機器は、あらゆるシステムで開けるためBMPをエクスポートする。フォーマットのシンプルさは相互運用性のリスクを下げ、規制の厳しい環境でそれは重要だ。
リバースエンジニアリングと教育。 BMPは今も、画像ファイル構造を教えるのに最適なフォーマットの一つだ。ヘッダーは小さく、ピクセルレイアウトは明確で、午後一つで動作するデコーダーを作れる。
ゲーム開発のレガシー。 古いゲームエンジンやテクスチャツールは、BMPを中間フォーマットとして使っていた。一部のModdingコミュニティでは、ツールがBMP中心に構築されているため、今もBMPアセットを扱っている。
シグネチャ読み取りによるBMPの検出
.bmp 拡張子を信頼するな。最初の2バイトを読め。
TypeScript
async function isBmp(file: File): Promise<boolean> {
const buffer = await file.slice(0, 2).arrayBuffer()
const bytes = new Uint8Array(buffer)
return bytes.length === 2 && bytes[0] === 0x42 && bytes[1] === 0x4d
}
Python
def is_bmp(path: str) -> bool:
with open(path, "rb") as f:
return f.read(2) == b"BM"
Go
func isBmp(path string) bool {
f, err := os.Open(path)
if err != nil {
return false
}
defer f.Close()
buf := make([]byte, 2)
if _, err := f.Read(buf); err != nil {
return false
}
return bytes.Equal(buf, []byte("BM"))
}
PHP
function isBmp(string $path): bool {
$header = file_get_contents($path, false, null, 0, 2);
return $header === "BM";
}
ImageMagick CLI
magick identify -verbose image.bmp | grep "Format:"
# Format: BMP (Microsoft Windows bitmap image)
またはシンプルに:
file image.bmp
# image.bmp: PC bitmap, Windows 3.x format, 1920 x 1080 x 24
BMPの未来
BMPは完成したフォーマットだ。コアとなる仕様はWindows 95時代以来、実質的に変更されていない。これは限界であり、同時に保証でもある:1995年に書かれたBMPは、今日でも正しく開く。
このフォーマットに新しい機能が追加されることはない。より良い圧縮、適切なアルファ処理、アニメーション対応を得ることもない。誰もBMPに投資しない。なぜなら、より良いBMPを必要としていないからだ。PNG、WebP、AVIF、HEICは、効率が重要なあらゆる文脈ですでに勝利している。
しかし、BMPはすでに使われている場所で使われ続けるだろう。Windows内部、組み込みディスプレイ、レガシー医療機器、教室。BMPは華美ではなく非効率だが、あまりにも多くのシステムが読み方を知っているため、取り除くことはできない。
レガシーなBMPファイルを現代のデバイスで使い続ける必要がある場合、実用的な選択は変換だ。写真はJPEGやHEICに変換することで容量を節約できる。透過を持つグラフィックにはPNGまたはWebPが適切な変換先だ。そしてAppleのエコシステムを橋渡しする必要がある場合、HEICをどのシステムでも読めるフォーマットに変換することが第一歩だ。
HEIC to JPGコンバーターは完全にブラウザ内で動作するため、ファイルがデバイスから外に出ることはない。透過を伴う可逆出力が必要ならHEIC to PNGを使うとよい。Web向けに最適化されたサイズが必要なら、HEIC to WebPが広いブラウザサポートと共により小さなファイルを提供する。
BMPは業界に貴重な教訓を与えた。あらゆる場所に存在するためには、最良のフォーマットである必要はない。十分にシンプルで誰もが実装し、十分に定着していて誰も削除できなければよいのだ。



