Skip to content

Ловушки разработчика #2: я всегда думал, что у 32-битного BMP есть альфа-канал

2026-06-23

Tags: Windows · Ловушки разработчика


Я годами верил в одну очевидную вещь: 32-битный BMP, 4 байта на пиксель, BGRA, 4-й байт — это альфа. PNG — это RGBA, BMP — то же самое, только байты в обратном порядке. Просто, чисто, само собой разумеется.

Пока я не делал инструмент для ICO и прозрачность не сломалась так, что этому не было никакого объяснения. Тогда я открыл спецификацию BMP — и обнаружил, что верил во ложь.

Факт, который «все знают»

Всё началось совершенно нормально. Мне нужен был инструмент, который распаковывает ICO, предпросматривает каждый кадр и сохраняет прозрачность. Логика проста до безобразия:

  1. Прочитать BMP-данные одного кадра из ICO
  2. Декодировать в растровое изображение
  3. Показать на экране

Первый кадр загрузился: цвета правильные, форма правильная, а прозрачные области превратились в сплошные чёрные блоки.

Моей первой реакцией было — этот ICO-файл сломан. Заменил другим — опять чёрный. Третьим — снова чёрный.

Что-то не так. Эти же иконки в Проводнике Windows отлично прозрачные.

Что говорит спецификация

Я пошёл читать спецификацию BMP. Классический BITMAPINFOHEADER — 40 байт, biBitCount = 32 означает 32 бита на пиксель. А потом я увидел ту самую ключевую фразу —

Когда biCompression равен BI_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. У него есть свой официальный механизм прозрачности: AND-маска.

BMP-данные одного кадра ICO выглядят так:

  • Пиксельные данные (XOR-маска) — это BGR плюс тот самый загадочный 4-й байт
  • Сразу за ним — 1-битная монохромная маска (AND-маска) — 1 бит на пиксель, 1 означает прозрачность, 0 — непрозрачность

Вот это — прозрачность ICO, прописанная в спецификации чёрным по белому. А 32-битная альфа — это более поздняя «надстройка».

Логика Windows при рендеринге тоже весьма прагматична:

  • В 32-битной альфе есть любое ненулевое значение → используется альфа, выполняется плавное смешивание
  • Альфа полностью равна 0 → считается непрозрачной, используется AND-маска

Две системы сосуществуют, работает та, что полезнее. Звучит заботливо — пока вы сами не начнёте писать код для разбора этого формата.

В какую ловушку я попал

Вернёмся к чёрной иконке. На самом деле:

Мой декодер (неважно, WinUI BitmapDecoder или BMP-декодер в браузере) послушно следует спецификации BMP — он видит BI_RGB + 32 бита и выбрасывает 4-й байт как padding. Прозрачные области остаются без альфы, а оставшиеся цвета в сочетании с непрозрачностью по умолчанию превращаются в чёрные блоки.

Декодеры не сломаны. Сам формат ненадёжен в этом вопросе.

Поэтому исправление весьма контринтуитивно: не нужно «чинить» ICO и не нужно менять декодер — нужно перестать доверять декодерам. Читать 4 сырых байта напрямую, самому упаковывать BGRA на холст; а если альфа полностью нулевая — возвращаться и честно вычислять AND-маску.

Именно так я и сделал в веб-версии и десктопной версии ICO Unpacker: обойти декодеры и напрямую разбирать байты DIB. Теперь, независимо от того, является ли 4-й байт альфой или padding, прозрачность не исчезнет.

ICO Unpacker также доступен в виде бесплатного десктопного приложения для Windows — та же логика, без браузера, полностью офлайн.

Что нужно запомнить

  • «В 32-битном BMP есть альфа-канал» — это отраслевой консенсус, а не факт, прописанный в спецификации.
  • В классическом BITMAPINFOHEADER + BI_RGB 4-й байт — это padding; альфа становится официальной только в BITMAPV4HEADER + BI_BITFIELDS.
  • Если инструмент «потерял» прозрачность вашего BMP — это не обязательно баг. Возможно, он просто следует спецификации.
  • У ICO есть собственная официальная прозрачность (AND-маска); 32-битная альфа — более позднее дополнение; обе системы сосуществуют, и Windows использует ту, что выгоднее в данный момент.
  • Чтобы действительно сохранить прозрачность — не ищите обходных путей: читайте байты напрямую.

В предыдущей статье Ловушки разработчика #1 речь шла о том, что «онлайн-конвертеры тихо ломают ваши BMP-кадры» — там кто-то другой записывает вашу альфу с ошибками. Эта статья — обратная сторона медали: даже если альфа записана правильно, сам формат BMP не гарантирует, что кто-то её распознает.


Эта статья входит в серию Ловушки разработчика.