開発の落とし穴 #2:32 ビット BMP にはアルファチャンネルがあると思っていた
2026-06-23
長年信じてきた常識があります:32 ビット BMP は、1 ピクセルあたり 4 バイト、BGRA、そして第 4 バイトがアルファだ。 PNG が RGBA なら、BMP はバイトオーダーが逆なだけ——明快、クリーン、当然のことだと思っていました。
ICO ツールを作っていたとき、透明度がまったく説明のつかない壊れ方をしたので、初めて BMP 仕様書を開きました。そこで気づいたのは、自分が嘘を信じていたということでした。
誰もが「知っている」事実
物語の始まりはごく普通でした。ICO を展開し、各フレームをプレビューし、透明度を保ったまま保存できるツールが欲しかったのです。ロジックはこれ以上ないほどシンプルです:
- ICO 内の某一フレームの BMP データを読み出す
- ビットマップにデコードする
- 表示する
最初のフレームが読み込まれました:色は合っている、形も合っている、でも透明な部分がすべて黒いベタ塗りブロックになっていた。
私の最初の反応は——この ICO ファイルが壊れているのだろう、というものでした。別のファイルに変えても黒い。さらに別のファイルに変えても黒い。
おかしい。これらのアイコンは Windows エクスプローラーではちゃんと透明に表示されているのに。
仕様書はどう言っているか
BMP 仕様書を調べてみました。古典的な BITMAPINFOHEADER は 40 バイトで、biBitCount = 32 は 1 ピクセルあたり 32 ビットを意味します。そこで私はその重要な一文を見つけました——
biCompression が BI_RGB の場合、各ピクセル DWORD の上位バイトは unused(未使用) である。
未使用。アルファではない、透明度ではない、「このバイトに何を入れるべきかわからないので、とりあえず空けておく」という意味です。
つまり:最も基本的で最も一般的な 32 ビット BMP フォーマットでは、仕様はアルファチャンネルをまったく定義していないのです。各ピクセルの第 4 バイトは、決まりごととしてはただの埋め草 padding に過ぎません。
これは私が何年も「知っている」と思っていたことと、完全に正反対でした。
ではアルファはどうやって「ある」ことになったのか
実は BMP も後から反省しました。BITMAPV4HEADER(108 バイト)が BI_BITFIELDS を導入し、ビットマスクのセットを明示的に宣言できるようになりました——赤、緑、青、そしてアルファです。このバージョンに至って初めて、アルファは BMP において正式な地位を得ました。
問題は:圧倒的多数の BMP ファイル、ICO に埋め込まれた各フレームを含め、依然として古い 40 バイトヘッダー、BI_RGB、マスクなしを使っていることです。仕様上、それらの第 4 バイトは padding です。
では、なぜアイコンは Windows では透明に見えるのでしょうか?
Windows のグラフィックスタック全体が、XP 時代にある暗黙の了解に達したからです:「まあ、その padding バイトをアルファだと皆で思い込むことにしよう。」GDI のブレンド関数がそういう使い方を匂わせ、各レンダラーは暗黙の了解に従いました。こうして「32 ビット BMP にはアルファがある」は、誰もが信じているのに、誰も仕様に書いていない「事実」になってしまったのです。
ICO は実は二つの透明度システムを持っている
さらに素晴らしいことに、ICO はそもそも BMP のあの 1 バイトに頼っていません。ICO には独自の公式の透明度メカニズムがあります:AND mask です。
ICO の 1 フレームの BMP データは次のような構造です:
- ピクセルデータ(XOR mask)—— BGR に加えて、身元不明のあの第 4 バイト
- その直後に続く 1 ビットモノクロマスク(AND mask)—— 1 ピクセルあたり 1 ビット、1 は透明、0 は不透明
こちらこそが仕様が明文で認めている ICO の透明度です。あの 32 ビットのアルファは、後から上乗せされた「拡張機能」に過ぎません。
Windows がレンダリング時に行う判断も非常に現実的です:
- 32 ビットアルファに何らかの非ゼロ値がある → アルファを使い、スムーズなブレンドを行う
- アルファがすべて 0 → 不透明とみなし、AND mask にフォールバックする
二つのシステムが共存し、使いやすい方が使われます。聞こえは至極周到——自分でコードを書いて解析するまでは。
結局どこにハマったのか
あの黒背景アイコンに戻りましょう。真相はこうです:
私のデコーダー(WinUI の BitmapDecoder であれ、ブラウザの BMP デコーダーであれ)は、忠実に BMP 仕様に従っていました——BI_RGB + 32 ビットを見ると、第 4 バイトを padding として捨てていたのです。透明領域のアルファが失われ、残った色がデフォルトの不透明値と組み合わさって、黒い塊になっていました。
デコーダーは壊れていません。このフォーマット自体が、この件に関しては当てにならないだけなのです。
だから修正方法は直感に反しています:ICO を「直す」ことでも、デコーダーを変えることでもありません——デコーダーを信じるのをやめることです。生の 4 バイトを直接読み込み、自分で BGRA をキャンバスに詰め込む。アルファが全ゼロのときは、改めて AND mask を地道に計算する。
私はその後 ICO Unpacker のウェブ版でもデスクトップ版でも同じやり方を採用しました:デコーダーをバイパスし、DIB のバイトを直接解析する。これで、あの第 4 バイトがアルファであれ padding であれ、透明度が消えてしまうことはありません。
ICO Unpacker はWindows デスクトップアプリも提供しています——同じロジックで、ブラウザ不要、完全オフラインで動作します。
結局何を覚えておくべきか
- 「32 ビット BMP にはアルファチャンネルがある」は業界全体の暗黙の了解であり、仕様で定義された事実ではない。
- 最も古典的な
BITMAPINFOHEADER+BI_RGBのもとでは、第 4 バイトは padding である。アルファが正式な地位を得るのはBITMAPV4HEADER+BI_BITFIELDSから。 - あるツールが BMP の透明度を「失った」としても、それは必ずしもバグではない——仕様に従っているだけかもしれない。
- ICO には独自の公式透明度(AND mask)があり、32 ビットアルファは後からの拡張。両者は共存し、Windows は状況に応じて使い分ける。
- 本当に透明度を保ちたいなら、遠回りをせず:直接バイトを読め。
前回の開発の落とし穴 #1 は「オンライン変換ツールがこっそり BMP フレームを壊す」という話でした——あちらは他人がアルファを書き間違える問題です。今回はその裏側:アルファが正しく書かれていても、BMP というフォーマット自体がそれを認めてくれる保証はないということです。
この記事は開発の落とし穴シリーズの一部です。