Глубокие погружения

Устройство формата BMP: простейший графический формат Windows

koboshiCo-founder
·10 мин чтения
Устройство формата BMP: простейший графический формат Windows
Кратко

BMP появился в Windows в 1990 году. Он хранит сырые пиксели с минимальными заголовками, без сжатия и без альфа-канала. В посте разбираем формат от 14-байтового BITMAPFILEHEADER до ниш, где BMP до сих пор встречается — от встроенных систем до медицинской визуализации.

Откройте BMP в шестнадцатеричном редакторе — первые четырнадцать байтов будут выглядеть так:

42 4D 46 00 00 00 00 00 00 00 36 00 00 00

Это весь заголовок BITMAPFILEHEADER. 42 4D — сигнатура «BM» в ASCII. Следующие четыре байта задают размер файла: 46 00 00 00 в little-endian равно 70. Ещё четыре байта зарезервированы и всегда нулевые. Последние четыре байта — смещение к пиксельным данным: 36 00 00 00 равно 54, значит массив пикселей начинается с байта 54.

Никакого маркера сжатия, цветового профиля или альфа-канала — только сигнатура, размер и смещение. BMP, как и следует из названия, это прямолинейная карта битов.

Что такое BMP на самом деле

BMP расшифровывается как Bitmap, а формально — как Device Independent Bitmap (DIB, аппаратно-независимый битмап). Microsoft представила его вместе с Windows 3.0 в 1990 году как способ хранить растровые изображения без привязки к конкретному устройству вывода. До DIB Windows использовала Device Dependent Bitmaps (DDB, аппаратно-зависимые битмапы), чей пиксельный формат совпадал с установленной видеокартой. Файл DDB, сохранённый на одном компьютере, мог оказаться нечитаем на другом.

BMP решил эту проблему, сохраняя в файле достаточно метаданных для восстановления изображения на любом декодере: ширина, высота, глубина цвета, палитра, тип сжатия и сырой массив пикселей. Операционная система читает заголовок, выделяет буфер и копирует пиксели в память. Для 24-битных несжатых изображений пиксельные данные обычно представляют собой RGB-триплеты, записанные снизу вверх, строка за строкой, с выравниванием до границы в четыре байта.

Такая простота сделала BMP основным форматом изображений Windows на более десяти лет. Paintbrush, движок обоев Windows, ранние сканеры и бесчисленные внутренние инструменты работали с BMP напрямую.

Структура файла

Файл BMP состоит из четырёх частей, расположенных последовательно:

BITMAPFILEHEADER   (14 bytes)
BITMAPINFOHEADER   (40 bytes for the classic version)
Color Table        (optional, for indexed color modes)
Pixel Data         (the actual image)

BITMAPFILEHEADER (14 байт)

Bytes 0-1:   Signature    "BM" (0x42 0x4D)
Bytes 2-5:   FileSize     (uint32, little-endian)
Bytes 6-9:   Reserved     (always 0)
Bytes 10-13: Offset       (uint32, offset to pixel data from file start)

Сигнатура не всегда равна «BM». Windows также поддерживает «BA», «CI», «CP», «IC» и «PT» для вариантов курсоров и иконок, но на практике каждый BMP-файл начинается с «BM».

BITMAPINFOHEADER (40 байт)

Bytes 0-3:   HeaderSize        (40)
Bytes 4-7:   Width             (int32, pixels)
Bytes 8-11:  Height            (int32, pixels; negative means top-down)
Bytes 12-13: Planes            (1)
Bytes 14-15: BitCount          (1, 4, 8, 16, 24, or 32)
Bytes 16-19: Compression       (0 = none, 1 = RLE8, 2 = RLE4, 3 = bitfields)
Bytes 20-23: ImageSize         (0 if uncompressed)
Bytes 24-27: XpixelsPerMeter   (physical resolution)
Bytes 28-31: YpixelsPerMeter   (physical resolution)
Bytes 32-35: ColorsUsed        (0 means 2^BitCount)
Bytes 36-39: ColorsImportant   (0 means all)

В более поздних версиях Windows появились заголовки большего размера — 52, 56, 108 и 124 байта — для поддержки альфа-маски, цветовых пространств и ICC-профилей. Но 40-байтовая версия встречается чаще всего.

Высота задаёт направление строк

Если поле высоты положительное, изображение хранится снизу вверх: строка 0 в файле — это нижняя строка изображения. Если высота отрицательная, изображение хранится сверху вниз. Это одна из неприметных особенностей BMP — большинство других форматов по умолчанию используют порядок сверху вниз.

Цветовая таблица

Для глубины цвета 1, 4 или 8 бит BMP использует индексированную палитру. Каждая запись палитры занимает четыре байта: синий, зелёный, красный и зарезервированный альфа-байт, который почти всегда игнорируется. 256-цветный BMP имеет 1024-байтовую цветовую таблицу сразу после заголовка. Следующие за ней пиксельные данные — это не цвета, а индексы в этой таблице.

Выравнивание пиксельных данных

Каждая строка развёртки должна быть кратна четырём байтам. 24-битное изображение шириной 5 пикселей требует 15 байт на строку, которые округляются до 16. Эти лишние байты выравнивания — потраченное впустую место, ещё одна причина неэффективности BMP.

Полный пример 24-битного несжатого BMP размером 2 × 2:

Offset 0x00: 42 4D 46 00 00 00 00 00 00 00 36 00 00 00  -- BITMAPFILEHEADER
Offset 0x0E: 28 00 00 00 02 00 00 00 02 00 00 00 01 00 18 00  -- BITMAPINFOHEADER
Offset 0x22: 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00
Offset 0x32: 00 00 00 00 00 00 00 00
Offset 0x36: FF 00 00 00 FF 00 00 00 FF  -- bottom row: red, green, blue (with padding)
Offset 0x3F: 00 FF FF 00 FF FF 00 00 00  -- top row: cyan, magenta, yellow (with padding)

Форматы, существовавшие к 1990 году

BMP появился не в вакууме. К 1990 году несколько форматов изображений уже конкурировали за внимание:

FormatCompressionColor depthTransparencyPatent riskTypical use
GIFLZW256 colors max1-bitYes (LZW)Веб-графика
JPEGDCT + Huffman24-bitNoneMostly noФотографии
TIFFMultiple optionalUp to 48-bitYesOptionalПечать, сканирование
PCXRLEUp to 24-bitNoneNoDOS-игры, клипарт
PICTRLEUp to 32-bitYesNoКлассическая Mac OS
BMPNone or RLEUp to 32-bitNoneNoОбои Windows, иконки

GIF уже был стандартом в вебе, но патентная тень LZW делала коммерческое использование рискованным. JPEG был рассчитан на фотографии и необратимо отбрасывал данные. TIFF был мощным, но переусложнённым — написание универсального TIFF-ридера было отдельным проектом. PCX уходил вместе с DOS.

Главный аргумент BMP — простота. Он не был меньше, быстрее или функциональнее остальных. Это был формат, который Windows понимала без дополнительных библиотек.

Почему BMP работал

BMP имел реальные преимущества в своём контексте:

Нулевая сложность декодирования. 24-битный несжатый BMP можно отобразить, прочитав заголовок фиксированного размера и скопировав пиксели прямо во framebuffer. Никаких таблиц Хаффмана, никакого конечного автомата DEFLATE, никаких прогрессивных проходов. Это делало его удобным для встроенных систем с ограниченной вычислительной мощностью.

Отсутствие патентных ограничений. В отличие от LZW в GIF, сжатие в BMP либо отсутствовало, либо было RLE — оба варианта не были обременены патентами. Это имело значение для open-source инструментов и коммерческого ПО, которое хотело избежать лицензирования GIF от Unisys.

Прямая интеграция с Windows. Win32 API предоставляла LoadBitmap, BitBlt и StretchBlt, построенные вокруг DIB-секций. Разработчик мог загрузить BMP в память и вывести его на экран буквально в нескольких строках на C.

Предсказуемое расположение в памяти. Пиксели несжатого BMP напрямую соответствуют экранной памяти, поэтому игровые движки и графические инструменты в 1990-х использовали BMP как формат обмена текстурами. Можно было отобразить файл в память и использовать смещение пикселей как сырой буфер.

Где BMP уступал

Сильные стороны BMP одновременно были и его слабостями. Формат не успевал за развитием отрасли.

Нет эффективного сжатия. Несжатые BMP огромны. 24-битное изображение 1920 × 1080 занимает 5,93 МБ. То же изображение в JPEG качества 90 — примерно 300–500 КБ. Когда скорость интернета и стоимость хранения стали ограничениями, BMP превратился в анахронизм.

RLE-сжатие слабое. BMP поддерживает RLE8 и RLE4 — кодирование длин серий, но RLE помогает только изображениям с большими однородными областями. Фотографии и градиенты сжимаются плохо. BMP с RLE-сжатием на практике встречаются редко.

Нет настоящей прозрачности. Стандартный BMP не имеет альфа-канала. 32-битный вариант BMP включает альфа-байт, но многие инструменты игнорируют его или считают зарезервированным. Для веб-графики и игровых спрайтов это делало BMP бесполезным по сравнению с PNG.

Нет анимации, метаданных и управления цветом. BMP хранит пиксели и немногое другое. Никакого EXIF, поддержки ICC-профилей в оригинальном заголовке или кадров анимации. По мере усложнения рабочих процессов BMP оставался примитивным.

Хранение снизу вверх. Порядок строк по умолчанию снизу вверх сбивает с толку декодеры и тратит ресурсы на преобразование. Мелкая деталь, которая добавляет трения безо всякой пользы.

Форматы, которые заменили BMP

Ограничения BMP напрямую определили то, что появилось после.

PNG (1996) решил проблемы прозрачности и сжатия. Он предложил сжатие без потерь, 8-битный альфа-канал и более простую спецификацию, чем TIFF. PNG заменил BMP почти везде в вебе и в кроссплатформенных приложениях.

JPEG (1992) решил проблему фотографий. Его DCT-кодирование с потерями делало фотографии в 10–20 раз меньше несжатого BMP. Для любого изображения с непрерывным тоном JPEG стал форматом по умолчанию.

WebP (2010) объединил в одном контейнере режимы с потерями и без потерь, анимацию и альфа-канал. Lossless WebP обычно на 20–30% компактнее PNG, который сам по себе значительно превосходит BMP.

HEIC/HEIF (2013) принёс HEVC-сжатие в статичные изображения. Он обеспечивает меньший размер файла, чем JPEG, при лучшем качестве, поддерживает альфа-канал и карты глубины, и сейчас является форматом по умолчанию на устройствах Apple. Когда iPhone сохраняет фото, обычно это HEIC.

Каждый из этих форматов появился потому, что BMP перестал быть достаточно хорошим для конкретного сценария.

Где BMP всё ещё используется

BMP не исчез. Он просто занял ниши, где его простота важнее неэффективности.

Внутренности Windows. Загрузочный экран Windows, устаревшие диалоги и некоторые системные ресурсы всё ещё используют BMP. Win32 GDI API ожидает данные DIB, и переписывание этих путей кода не стоит времени Microsoft.

Встроенные системы и микроконтроллеры. Маленький LCD на Arduino или STM32 не обладает ни ОЗУ, ни вычислительной мощностью для декодирования PNG. 16-битный несжатый BMP можно копировать прямо во framebuffer практически без кода.

Медицинская и научная визуализация. Некоторые устаревшие DICOM-просмотрщики и лабораторное оборудование экспортируют BMP, потому что его открывает любая система. Простота формата снижает риски интероперабельности, что важно в регулируемых средах.

Обратная разработка и образование. BMP остаётся одним из лучших форматов для обучения структуре файлов изображений. Заголовки небольшие, пиксельное расположение очевидно, а рабочий декодер можно собрать за один вечер.

Наследие игровой разработки. Старые игровые движки и инструменты для текстур использовали BMP как промежуточный формат. Некоторые моддерские сообщества до сих пор работают с BMP-ассетами, потому что инструменты были построены вокруг него.

Как определить BMP по сигнатуре

Не доверяйте расширению .bmp. Прочитайте первые два байта.

TypeScript

async function isBmp(file: File): Promise<boolean> {
  const buffer = await file.slice(0, 2).arrayBuffer()
  const bytes = new Uint8Array(buffer)
  return bytes.length === 2 && bytes[0] === 0x42 && bytes[1] === 0x4d
}

Python

def is_bmp(path: str) -> bool:
    with open(path, "rb") as f:
        return f.read(2) == b"BM"

Go

func isBmp(path string) bool {
	f, err := os.Open(path)
	if err != nil {
		return false
	}
	defer f.Close()

	buf := make([]byte, 2)
	if _, err := f.Read(buf); err != nil {
		return false
	}
	return bytes.Equal(buf, []byte("BM"))
}

PHP

function isBmp(string $path): bool {
    $header = file_get_contents($path, false, null, 0, 2);
    return $header === "BM";
}

ImageMagick CLI

magick identify -verbose image.bmp | grep "Format:"
# Format: BMP (Microsoft Windows bitmap image)

Или проще:

file image.bmp
# image.bmp: PC bitmap, Windows 3.x format, 1920 x 1080 x 24

Будущее BMP

BMP — законченный формат. Основная спецификация не менялась сколько-нибудь значимо со времён Windows 95. Это одновременно ограничение и гарантия: BMP, записанный в 1995 году, до сих пор открывается корректно.

Формат не обрастёт новыми возможностями. В нём не появится лучшего сжатия, корректной обработки альфы или поддержки анимации. Никто не инвестирует в BMP, потому что никому не нужен лучший BMP. PNG, WebP, AVIF и HEIC уже победили в каждом контексте, где важна эффективность.

Но BMP останется там, где он уже прижился: во внутренних механизмах Windows, встроенных дисплеях, устаревшем медицинском оборудовании и учебных курсах. Он непритязателен и неэффективен, но его невозможно убрать — слишком много систем умеют его читать.

Если у вас всё-таки оказались унаследованные BMP-файлы, которые нужны на современных устройствах, практичный выход — конвертация. Для фотографий конвертация в JPEG или HEIC экономит место. Для графики с прозрачностью правильной целью будут PNG или WebP. А когда нужно связаться с экосистемой Apple, конвертация HEIC во что-то универсально читаемое — первый шаг.

Если вы работаете с HEIC, попробуйте наш конвертер HEIC в JPG — он работает полностью в браузере, файл никогда не покидает ваше устройство. Для вывода без потерь с прозрачностью подойдёт HEIC в PNG, а для веб-оптимизированных размеров — HEIC в WebP.

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

Ещё посты в блоге