开发踩坑 #2:我一直以为 32 位 BMP 有 alpha 通道
2026-06-23
我有一个信了好多年的常识:32 位 BMP,每像素 4 字节,BGRA,第 4 个字节就是 alpha。 PNG 是 RGBA,BMP 不过是字节序反过来——清楚、干净、天经地义。
直到我做一个 ICO 工具,透明度以一种完全说不通的方式坏掉,我才翻开 BMP 规范——发现自己信了个谎。
一个所有人都「知道」的事实
故事的开头非常正常。我想要一个能拆开 ICO、预览每一帧、并保住透明度的工具。逻辑简单得不能再简单:
- 读出 ICO 里某一帧的 BMP 数据
- 解码成位图
- 显示出来
第一帧加载出来:颜色对,形状对,透明的地方全变成了黑色实心块。
我的第一反应是——这 ICO 文件坏了。换一个,还是黑。再换一个,还是黑。
不对劲。这些图标在 Windows 资源管理器里明明透明得好好的。
规范是怎么说的
我去翻了 BMP 规范。经典的 BITMAPINFOHEADER 是 40 字节,biBitCount = 32 表示每像素 32 位。然后我看到那关键的一句——
当 biCompression 为 BI_RGB 时,每个像素 DWORD 的高位字节是 unused(未使用)。
未使用。不是 alpha,不是透明度,是「我们也不知道这字节该放啥,先空着」。
换句话说:在最基础、最常见的 32 位 BMP 格式里,规范压根没定义 alpha 通道。每个像素的第 4 个字节,按规矩只是个凑数的 padding。
这跟我「知道」了多年的事,完全相反。
那 alpha 又是怎么「有」的
BMP 后来其实补过课。BITMAPV4HEADER(108 字节)引入了 BI_BITFIELDS,允许你显式声明一组位掩码——红、绿、蓝,还有 alpha。到了这一版,alpha 才第一次在 BMP 里名正言顺。
问题是:绝大多数 BMP 文件,包括 ICO 里嵌着的那一帧帧,用的还是老的 40 字节头、BI_RGB、不带掩码。按规范,它们的第 4 字节就是 padding。
那为什么图标在 Windows 里看着是透明的?
因为整个 Windows 图形栈在 XP 时代达成了一个不成文的默契:「得了,大家都假装那个 padding 字节是 alpha 吧。」GDI 的混合函数暗示可以这么用,各家渲染器心照不宣地照做。于是「32 位 BMP 有 alpha」就变成了一个人人都信、却没人写进规范的「事实」。
ICO 其实有两套透明度
更妙的是,ICO 根本不指望 BMP 的那一个字节。它有自己官方的透明度机制:AND 掩码。
一帧 ICO 的 BMP 数据长这样:
- 像素数据(XOR 掩码)——就是 BGR 外加那个身份不明的第 4 字节
- 紧跟着一块 1 位单色掩码(AND 掩码)——每个像素 1 个 bit,1 表示透明,0 表示不透明
这才是规范白纸黑字认可的 ICO 透明度。那个 32 位的 alpha,是后来叠加上去的「增强项」。
Windows 渲染时的取舍也很现实:
- 32 位 alpha 里有任意非零值 → 用 alpha,做平滑混合
- alpha 全是 0 → 当作不透明,退回 AND 掩码
两套系统并存,谁好用用谁。听起来挺周到——直到你自己写代码去解析它。
我到底踩进了什么坑
回到那个黑底图标。真相是:
我的解码器(无论是 WinUI 的 BitmapDecoder,还是浏览器里的 BMP 解码)都在老老实实遵守 BMP 规范——它们看到 BI_RGB + 32 位,就把第 4 字节当 padding 丢了。透明区域没了 alpha,剩下的颜色配上默认的不透明,就糊成了黑块。
它们没坏。是这格式在这件事上本身就不靠谱。
所以修法很反直觉:不是去「修好」ICO,也不是换个解码器——而是别再相信解码器。直接读那 4 个原始字节,自己把 BGRA 塞进画布;alpha 全零的时候,再退回去老老实实算 AND 掩码。
我后来在 ICO Unpacker 的网页版和桌面版里都是这么干的:绕开解码器,直接解析 DIB 的字节。这样无论那第 4 字节到底是 alpha 还是 padding,透明度都不会凭空消失。
ICO Unpacker 也提供免费的 Windows 桌面版——同样的逻辑,无需浏览器,完全离线。
所以到底该记住什么
- 「32 位 BMP 有 alpha 通道」是一个全行业默契,不是规范定义的事实。
- 在最经典的
BITMAPINFOHEADER+BI_RGB下,第 4 字节是 padding;alpha 要到BITMAPV4HEADER+BI_BITFIELDS才名正言顺。 - 一个工具「丢」了你的 BMP 透明度,未必是它的 bug——它可能只是在照规范办事。
- ICO 有自己官方的透明度(AND 掩码),32 位 alpha 是后来的增强;两者并存,Windows 见机行事。
- 真要保住透明度,就别绕路:直接读字节。
上一篇开发踩坑 #1 讲的是「在线转换工具会悄悄搞坏你的 BMP 帧」——那是别人把你的 alpha 写坏。这一篇讲的是另一面:就算 alpha 写对了,BMP 这格式本身也不保证有人认。
本文属于开发踩坑系列。