Skip to content

開発の落とし穴 #2:32 ビット BMP にはアルファチャンネルがあると思っていた

2026-06-23

Tags: Windows · 開発の落とし穴


長年信じてきた常識があります:32 ビット BMP は、1 ピクセルあたり 4 バイト、BGRA、そして第 4 バイトがアルファだ。 PNG が RGBA なら、BMP はバイトオーダーが逆なだけ——明快、クリーン、当然のことだと思っていました。

ICO ツールを作っていたとき、透明度がまったく説明のつかない壊れ方をしたので、初めて BMP 仕様書を開きました。そこで気づいたのは、自分が嘘を信じていたということでした。

誰もが「知っている」事実

物語の始まりはごく普通でした。ICO を展開し、各フレームをプレビューし、透明度を保ったまま保存できるツールが欲しかったのです。ロジックはこれ以上ないほどシンプルです:

  1. ICO 内の某一フレームの BMP データを読み出す
  2. ビットマップにデコードする
  3. 表示する

最初のフレームが読み込まれました:色は合っている、形も合っている、でも透明な部分がすべて黒いベタ塗りブロックになっていた

私の最初の反応は——この ICO ファイルが壊れているのだろう、というものでした。別のファイルに変えても黒い。さらに別のファイルに変えても黒い。

おかしい。これらのアイコンは Windows エクスプローラーではちゃんと透明に表示されているのに。

仕様書はどう言っているか

BMP 仕様書を調べてみました。古典的な BITMAPINFOHEADER は 40 バイトで、biBitCount = 32 は 1 ピクセルあたり 32 ビットを意味します。そこで私はその重要な一文を見つけました——

biCompressionBI_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 というフォーマット自体がそれを認めてくれる保証はないということです。


この記事は開発の落とし穴シリーズの一部です。