Skip to content

Pièges de Dev #2 : j'ai toujours cru que le BMP 32 bits avait un canal alpha

2026-06-23

Tags: Windows · Pièges de Dev


J'ai cru à une chose pendant des années : le BMP 32 bits, 4 octets par pixel, BGRA, le 4ème octet c'est l'alpha. Le PNG c'est du RGBA, le BMP c'est juste l'ordre des octets inversé — clair, net, imparable.

Jusqu'à ce que je développe un outil ICO et que la transparence se casse d'une façon totalement incompréhensible. J'ai alors ouvert la spécification BMP — et j'ai découvert que je vivais dans le mensonge.

Un fait que tout le monde « connaît »

Le début de l'histoire est très banal. Je voulais un outil capable d'ouvrir un ICO, de prévisualiser chaque frame, et de préserver la transparence. La logique est simplissime :

  1. Lire les données BMP d'une frame dans le ICO
  2. Décoder en bitmap
  3. L'afficher

La première frame se charge : les couleurs sont bonnes, la forme est bonne, les zones transparentes sont toutes devenues des blocs noirs opaques.

Ma première réaction — ce ICO est cassé. J'en essaie un autre, toujours noir. Encore un autre, toujours noir.

Pas normal. Ces icônes sont parfaitement transparentes dans l'Explorateur Windows.

Ce que dit la spécification

Je suis allé lire la spécification BMP. Le classique BITMAPINFOHEADER fait 40 octets, biBitCount = 32 signifie 32 bits par pixel. Et là, je tombe sur la phrase clé —

Quand biCompression vaut BI_RGB, l'octet de poids fort du DWORD de chaque pixel est unused (inutilisé).

Inutilisé. Pas alpha, pas transparence — juste « on ne sait pas trop quoi mettre dans cet octet, on le laisse vide ».

Autrement dit : dans le format BMP 32 bits le plus basique et le plus courant, la spécification ne définit tout simplement pas de canal alpha. Le 4ème octet de chaque pixel, selon les règles, n'est qu'un padding de remplissage.

C'est exactement le contraire de ce que je « savais » depuis des années.

Alors, comment l'alpha est-il « apparu »

Le BMP a fini par rattraper son retard. Le BITMAPV4HEADER (108 octets) a introduit le BI_BITFIELDS, qui permet de déclarer explicitement un jeu de masques de bits — rouge, vert, bleu, et alpha. C'est seulement à partir de cette version que l'alpha devient officiel en BMP.

Le problème : la grande majorité des fichiers BMP, y compris les frames embarquées dans les ICO, utilisent toujours la vieille en-tête de 40 octets, BI_RGB, sans masque. Selon la spécification, leur 4ème octet n'est que du padding.

Alors pourquoi les icônes apparaissent-elles transparentes dans Windows ?

Parce que toute la pile graphique de Windows a conclu un accord tacite à l'époque de XP : « Bon, faisons tous comme si cet octet de padding était l'alpha. » La fonction de blending de GDI le sous-entendait, les moteurs de rendu ont suivi sans discuter. Et ainsi « le BMP 32 bits a un canal alpha » est devenu un « fait » que tout le monde croit, mais que personne n'a jamais écrit dans une spécification.

ICO a en réalité deux systèmes de transparence

Plus fort encore : ICO ne compte pas du tout sur cet octet BMP. Il possède son propre mécanisme de transparence officiel : le AND mask.

Les données BMP d'une frame ICO ressemblent à ça :

  • Données pixel (XOR mask) — soit du BGR plus ce fameux 4ème octet d'identité douteuse
  • Suivies d'un masque monochrome 1 bit (AND mask) — 1 bit par pixel, 1 signifie transparent, 0 signifie opaque

Ça, c'est la transparence ICO reconnue noir sur blanc par la spécification. L'alpha 32 bits, c'est une « amélioration » greffée par-dessus, plus tard.

Le compromis de Windows au moment du rendu est très pragmatique :

  • L'alpha 32 bits contient une valeur non-nulle quelconque → utiliser l'alpha, faire un blending doux
  • L'alpha est entièrement à 0 → traiter comme opaque, se rabattre sur le AND mask

Deux systèmes coexistent, Windows utilise celui qui marche. Ça a l'air prévoyant — jusqu'à ce que tu écrives ton propre code pour l'analyser.

Dans quel piège je suis tombé

Retour à mon icône à fond noir. La vérité :

Mon décodeur (qu'il s'agisse du BitmapDecoder de WinUI ou du décodeur BMP du navigateur) suivait scrupuleusement la spécification BMP — il voyait BI_RGB + 32 bits, et jetait le 4ème octet comme padding. Les zones transparentes se retrouvaient sans alpha, et avec l'opacité par défaut, le tout se transformait en blocs noirs.

Le décodeur n'était pas cassé. C'est le format lui-même qui n'est pas fiable sur ce sujet.

La solution est donc contre-intuitive : ce n'est pas « réparer » le ICO, ni changer de décodeur — c'est ne plus faire confiance aux décodeurs. Lire directement les 4 octets bruts, pousser le BGRA soi-même dans le canvas ; et quand l'alpha est entièrement à zéro, revenir bêtement calculer le AND mask.

C'est exactement ce que j'ai fait dans ICO Unpacker, version web comme version desktop : contourner les décodeurs, analyser directement les octets du DIB. Ainsi, peu importe que ce 4ème octet soit de l'alpha ou du padding, la transparence ne disparaît jamais.

ICO Unpacker est aussi disponible en application Windows gratuite — même logique, sans navigateur, entièrement hors-ligne.

Ce qu'il faut vraiment en retenir

  • « Le BMP 32 bits a un canal alpha » est un consensus de toute l'industrie, pas un fait défini par la spécification.
  • Dans le classique BITMAPINFOHEADER + BI_RGB, le 4ème octet est du padding ; l'alpha ne devient officiel qu'avec BITMAPV4HEADER + BI_BITFIELDS.
  • Si un outil « perd » ta transparence BMP, ce n'est pas forcément un bug — il applique peut-être juste la spécification à la lettre.
  • ICO possède sa propre transparence officielle (le AND mask) ; l'alpha 32 bits est une amélioration venue ensuite. Les deux coexistent, Windows s'adapte au cas par cas.
  • Si tu veux vraiment préserver la transparence, ne prends pas de détours : lis les octets directement.

L'article précédent Pièges de Dev #1 parlait des « convertisseurs en ligne qui cassent discrètement tes frames BMP » — c'était quelqu'un d'autre qui écrit mal ton alpha. Celui-ci traite de l'autre face : même si l'alpha est correctement écrit, le format BMP lui-même ne garantit pas que quelqu'un le reconnaisse.


Partie de la série Pièges de Dev.