Dev-Fallen #2: Ich dachte immer, 32-Bit-BMP hätte einen Alphakanal
2026-06-23
Tags: Windows · Dev-Fallen
Ich habe jahrelang an eine Selbstverständlichkeit geglaubt, die ich nie angezweifelt habe: 32-Bit-BMP, 4 Byte pro Pixel, BGRA, das 4. Byte ist Alpha. PNG ist RGBA, BMP ist lediglich die umgekehrte Byte-Reihenfolge — klar, sauber, selbstverständlich.
Bis ich ein ICO-Tool baute und die Transparenz auf eine völlig unerklärliche Weise kaputtging. Da schlug ich erst die BMP-Spezifikation auf — und merkte, dass ich einer Lüge aufgesessen war.
Eine Tatsache, die alle zu kennen glauben
Die Geschichte beginnt ganz normal. Ich wollte ein Tool, das ICO-Dateien zerlegt, jeden Frame einzeln anzeigt und dabei die Transparenz intakt hält. Die Logik war so einfach wie möglich:
- Die BMP-Daten eines Frames aus dem ICO auslesen
- Zu einer Bitmap dekodieren
- Anzeigen
Der erste Frame wurde geladen: Farben stimmen, Form stimmt, alle transparenten Bereiche waren zu soliden schwarzen Blöcken geworden.
Meine erste Reaktion: Das ICO ist kaputt. Ein anderes ausprobiert — immer noch schwarz. Noch eins — immer noch schwarz.
Das konnte nicht sein. Diese Symbole wurden im Windows-Explorer völlig korrekt und transparent angezeigt.
Was die Spezifikation sagt
Ich schlug die BMP-Spezifikation auf. Der klassische BITMAPINFOHEADER ist 40 Byte, biBitCount = 32 bedeutet 32 Bit pro Pixel. Und dann sah ich den entscheidenden Satz —
Wenn biCompression auf BI_RGB gesetzt ist, ist das höchstwertige Byte jedes Pixel-DWORD unused (ungenutzt).
Ungenutzt. Nicht Alpha, nicht Transparenz — sondern „wir wissen auch nicht, was in dieses Byte gehört, also lassen wir es leer".
Anders gesagt: Im grundlegendsten, am häufigsten verwendeten 32-Bit-BMP-Format definiert die Spezifikation keinen Alphakanal. Das 4. Byte jedes Pixels ist der Spezifikation gemäß bloß ein Füllbyte.
Das war das genaue Gegenteil von dem, was ich jahrelang „gewusst" habe.
Wie Alpha dann doch noch „entstand"
BMP wurde später tatsächlich nachgebessert. Der BITMAPV4HEADER (108 Byte) führte BI_BITFIELDS ein und ermöglichte es, eine Reihe von Bitmasken explizit zu deklarieren — Rot, Grün, Blau, und Alpha. Erst ab dieser Version wurde Alpha offiziell im BMP-Format anerkannt.
Das Problem: Die allermeisten BMP-Dateien, einschließlich der in ICO-Dateien eingebetteten Frames, verwenden weiterhin den alten 40-Byte-Header mit BI_RGB und ohne Masken. Der Spezifikation gemäß ist ihr 4. Byte schlichtes Padding.
Warum sehen die Symbole dann in Windows transparent aus?
Weil der gesamte Windows-Grafikstack sich in der XP-Ära auf eine ungeschriebene Verabredung einigte: „Ach was, wir tun einfach so, als sei dieses Padding-Byte der Alphakanal." Die Blending-Funktion von GDI ließ diese Interpretation zu, und die verschiedenen Renderer machten stillschweigend mit. So wurde „32-Bit-BMP hat Alpha" zu einer Tatsache, an die alle glauben, die aber in keiner Spezifikation steht.
ICO hat eigentlich zwei Transparenzsysteme
Das Beste daran: ICO verlässt sich gar nicht auf dieses eine BMP-Byte. Es hat sein eigenes, offizielles Transparenzsystem: die AND-Maske.
Die BMP-Daten eines ICO-Frames sehen so aus:
- Pixeldaten (XOR-Maske) — also BGR plus dem fragwürdigen 4. Byte
- Direkt danach eine 1-Bit-Monochrom-Maske (AND-Maske) — 1 Bit pro Pixel, 1 bedeutet transparent, 0 bedeutet opak
Das ist die von der Spezifikation offiziell anerkannte ICO-Transparenz. Das 32-Bit-Alpha ist eine später hinzugefügte „Erweiterung".
Die Logik von Windows beim Rendern ist ebenso pragmatisch:
- 32-Bit-Alpha enthält irgendeinen Wert ungleich null → Alpha verwenden, weiches Blending durchführen
- Alpha überall 0 → als opak behandeln, auf die AND-Maske zurückgreifen
Beide Systeme existieren parallel — was funktioniert, wird verwendet. Klingt gründlich — bis man selbst Code schreibt, um das alles zu parsen.
Worin ich eigentlich getappt bin
Zurück zu dem schwarzen Symbol. Die Wahrheit ist:
Mein Dekoder (egal ob WinUIs BitmapDecoder oder der BMP-Dekoder im Browser) befolgt gewissenhaft die BMP-Spezifikation — er sieht BI_RGB + 32 Bit und verwirft das 4. Byte als Padding. Die transparenten Bereiche verlieren ihr Alpha, und was bleibt, sind die Farben kombiniert mit Standard-Opazität — woraus die schwarzen Blöcke entstehen.
Er ist nicht kaputt. Das Format ist in dieser Hinsicht selbst unzuverlässig.
Die Lösung ist also kontraintuitiv: Nicht das ICO „reparieren", nicht den Dekoder wechseln — sondern aufhören, Dekodern zu vertrauen. Die 4 rohen Bytes direkt auslesen, BGRA selbst in die Canvas packen; und wenn das Alpha komplett null ist, anhand der AND-Maske die Transparenz sauber berechnen.
Genau so habe ich es später sowohl in der Web- als auch in der Desktop-Version von ICO Unpacker gemacht: Den Dekoder umgehen und die Bytes des DIB direkt parsen. Egal, ob das 4. Byte nun Alpha oder Padding ist — die Transparenz verschwindet nicht einfach so.
ICO Unpacker gibt es auch als kostenlose Windows-Desktop-App — gleiche Logik, kein Browser nötig, komplett offline.
Was man sich also merken sollte
- „32-Bit-BMP hat einen Alphakanal" ist eine branchenweite Verabredung, kein durch die Spezifikation definierter Fakt.
- Unter dem klassischen
BITMAPINFOHEADER+BI_RGBist das 4. Byte Padding; Alpha wird erst mitBITMAPV4HEADER+BI_BITFIELDSoffiziell anerkannt. - Wenn ein Tool deine BMP-Transparenz „verliert", ist das nicht zwangsläufig sein Bug — es macht möglicherweise einfach das, was in der Spezifikation steht.
- ICO hat seine eigene offizielle Transparenz (AND-Maske); das 32-Bit-Alpha ist eine spätere Erweiterung. Beide Systeme existieren parallel, und Windows entscheidet situationsabhängig.
- Wer Transparenz wirklich bewahren will, sollte keine Umwege gehen: Die Bytes direkt lesen.
Im vorherigen Artikel Dev-Fallen #1 ging es darum, wie Online-Konverter deine BMP-Frames unbemerkt ruinieren — dort ist jemand anderes schuld, dass dein Alpha falsch geschrieben wird. Dieser hier behandelt die andere Seite: Selbst wenn das Alpha korrekt geschrieben wurde, garantiert das BMP-Format nicht, dass es jemand ernst nimmt.
Teil der Dev-Fallen-Serie.