Armadilhas de Dev #2: Eu sempre achei que o BMP de 32 bits tinha um canal alpha
2026-06-23
Tags: Windows · Armadilhas de Dev
Eu carregava uma certeza há muitos anos: BMP de 32 bits, 4 bytes por pixel, BGRA, o 4.º byte é o alpha. PNG é RGBA, BMP é só a ordem dos bytes invertida — claro, limpo, óbvio.
Até que construí uma ferramenta de ICO e a transparência quebrou de um jeito que não fazia o menor sentido. Foi aí que abri a especificação do BMP — e descobri que acreditava numa mentira.
Um fato que todos "sabem"
A história começa de forma bem normal. Eu queria uma ferramenta que abrisse um ICO, mostrasse cada frame e preservasse a transparência. A lógica era o mais simples possível:
- Ler os dados BMP de um frame dentro do ICO
- Decodificar para um bitmap
- Exibir
O primeiro frame carregou: cores certas, forma certa, todas as áreas transparentes viraram blocos pretos sólidos.
Minha primeira reação: esse ICO está corrompido. Tentei outro — preto de novo. Mais um — preto de novo.
Não fazia sentido. Esses ícones eram exibidos perfeitamente transparentes no Explorer do Windows.
O que a especificação diz
Fui bater o olho na especificação do BMP. O clássico BITMAPINFOHEADER tem 40 bytes, biBitCount = 32 significa 32 bits por pixel. E então vi a frase decisiva —
Quando biCompression é BI_RGB, o byte mais significativo de cada DWORD de pixel é unused (não utilizado).
Não utilizado. Não é alpha, não é transparência — é "também não sabemos o que vai nesse byte, então deixamos vazio".
Em outras palavras: no formato BMP de 32 bits mais básico e mais comum, a especificação não define um canal alpha. O 4.º byte de cada pixel, segundo as regras, é só um padding de preenchimento.
Isso era exatamente o oposto do que eu "sabia" há anos.
Como o alpha acabou "existindo"
O BMP até teve uma correção depois. O BITMAPV4HEADER (108 bytes) introduziu o BI_BITFIELDS, permitindo declarar explicitamente um conjunto de máscaras de bits — vermelho, verde, azul, e alpha. Só a partir dessa versão o alpha foi oficialmente reconhecido no formato BMP.
O problema: a esmagadora maioria dos arquivos BMP, incluindo os frames embutidos em ICOs, ainda usa o cabeçalho antigo de 40 bytes, com BI_RGB e sem máscaras. Pela especificação, o 4.º byte deles é puro padding.
Então por que os ícones parecem transparentes no Windows?
Porque toda a pilha gráfica do Windows, na era do XP, fechou um acordo não escrito: "ah, vamos todos fingir que esse byte de padding é o alpha." A função de blending do GDI deixava essa interpretação passar, e os renderizadores seguiram o jogo silenciosamente. Assim, "BMP de 32 bits tem alpha" virou um fato em que todos acreditam — mas que não está em especificação nenhuma.
O ICO, na verdade, tem dois sistemas de transparência
O melhor de tudo: o ICO nem depende desse único byte do BMP. Ele tem sua própria transparência oficial: a AND mask.
Os dados BMP de um frame de ICO se parecem com isso:
- Dados de pixel (XOR mask) — ou seja, BGR mais aquele 4.º byte de identidade duvidosa
- Logo em seguida, uma máscara monocromática de 1 bit (AND mask) — 1 bit por pixel, 1 significa transparente, 0 significa opaco
Essa é a transparência de ICO reconhecida oficialmente pela especificação. O alpha de 32 bits é um "aprimoramento" adicionado depois.
A lógica do Windows na hora de renderizar também é pragmática:
- O alpha de 32 bits tem qualquer valor diferente de zero → usa o alpha, faz blending suave
- O alpha é todo zero → trata como opaco, recorre à AND mask
Os dois sistemas coexistem — usa o que funcionar. Parece diligente — até você escrever código para fazer o parse de tudo isso.
A armadilha em que eu caí, afinal
De volta ao ícone preto. A verdade é esta:
O meu decodificador (seja o BitmapDecoder do WinUI, seja o decodificador BMP do navegador) está seguindo a especificação do BMP direitinho — ele vê BI_RGB + 32 bits e descarta o 4.º byte como padding. As áreas transparentes perdem o alpha, e o que sobra são as cores combinadas com a opacidade padrão — daí os blocos pretos.
Ele não está quebrado. O formato, nesse aspecto, é que não é confiável.
Então a correção é contraintuitiva: não é "consertar" o ICO, nem trocar de decodificador — é parar de confiar nos decodificadores. Ler os 4 bytes crus diretamente, colocar o BGRA no canvas você mesmo; e quando o alpha for todo zero, voltar e calcular a AND mask honestamente.
Foi exatamente assim que fiz, tanto na versão web quanto na desktop do ICO Unpacker: contornar o decodificador e fazer o parse dos bytes do DIB diretamente. Assim, não importa se aquele 4.º byte é alpha ou padding — a transparência não desaparece do nada.
O ICO Unpacker também está disponível como um aplicativo de desktop gratuito para Windows — mesma lógica, sem navegador, totalmente offline.
O que vale lembrar
- "BMP de 32 bits tem canal alpha" é um acordo tácito da indústria, não um fato definido pela especificação.
- Sob o clássico
BITMAPINFOHEADER+BI_RGB, o 4.º byte é padding; o alpha só se torna oficial comBITMAPV4HEADER+BI_BITFIELDS. - Se uma ferramenta "perde" a transparência do seu BMP, não é necessariamente um bug dela — pode ser que ela esteja apenas seguindo a especificação.
- O ICO tem sua própria transparência oficial (AND mask); o alpha de 32 bits é um aprimoramento posterior. Ambos coexistem, e o Windows decide conforme a situação.
- Quem realmente quer preservar a transparência não dá voltas: lê os bytes direto.
No artigo anterior, Armadilhas de Dev #1, falei sobre como conversores online arruínam seus frames BMP silenciosamente — lá o culpado é outra pessoa escrevendo seu alpha errado. Este trata do outro lado: mesmo que o alpha esteja escrito direito, o formato BMP não garante que alguém vá levá-lo a sério.
Este artigo pertence à série Armadilhas de Dev.