Формат данных BMP (bitmap)

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

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

BMP - bitmap - битовое отображение. Понятие "отображение" взято из математики. В математике отображение стоит очень близко к понятию функции. Для простоты считайте, что слово bitmap - это картинка (хотя это и не так).

Информация о файле BMP (bmp file header)

У каждого файла bitmap есть заголовок из 14 байт. Поля этого заголовка:

2 байта. Строка BM (в Windows).
4 байта. Размер файла в байтах.
2 байта. Зарезервированное поле. Нужно инициализировать нулём.
2 байта. Зарезервированное поле. Нужно инициализировать нулём.
4 байта. Адрес с которого начинается собственно изображение. Или по другому - смещение к началу изображения.

Давайте создадим изображение 100x100 пикселей. Каждый пиксель занимает 32 бита. Файловый заголовок будет выглядеть вот так:

BM
14+40+100*100*4
0
0
14+40

Важное замечание: на самом деле эти числа хранятся как последовательность байтов. Надеюсь, это понятно. Здесь (и в следующем примере) я расположил их в столбик для удобства восприятия.

Разберёмся со вторым полем. 14 - размер файлового заголовка. 40 - размер заголовка изображения (о нём ниже), 100*100 - количество пикселей. И кроме того, так как мы договорились, что каждый пиксель будет занимать 32 бита (4 байта), то нужно количество пикселей умножить на четыре.

Последнее поле: непосредственно изображение начинается сразу после файлового заголовка (14 байт) и заголовка изображения (40 байт).

Информация об изображении BMP (заголовок изображения)

Существует несколько версий BMP. Определить версию можно по размеру заголовка изображения. Мы будем пользоваться версией Windows V3, которая занимает 40 байт. Другие версии занимают 12, 64, 108, 124 байта.

В WinAPI для хранения bmp версии Windows V3 используется структура BITMAPINFOHEADER.

Поля заголовка Windows V3:

4 байта. Размер заголовка. Всегда задаётся 40 байт.
4 байта. Ширина изображения в пикселях.
4 байта. Высота изображения в пикселях.
2 байта. Данное поле всегда содержит единицу.
2 байта. Глубина цвета - количество битов в пикселе.
4 байта. Метод сжатия.
4 байта. Размер изображения. Здесь указывается размер непосредственно изображения - без учёта размера заголовков.
4 байта. Горизонтальное разрешение в пикселях на метр (количество пикселей в одном метре).
4 байта. Вертикальное разрешение в пикселях на метр (количество пикселей в одном метре).
4 байта. Количество цветов в палитре.
4 байта. Количество важных цветов в палитре.

Теперь посмотрим как будет выглядить заголовок изображения в нашем случае:

40
100
100
1
32
0
100*100*4
2795
2795
0
0

Для метода сжатия мы выбрали 0 - без сжатия. Возможны другие значения. Из интересных: BI_JPEG (значение - 4) - сжатие используемое в jpeg-изображениях и BI_PNG (значение - 5) - сжатие используемое в png-изображениях.

Горизонтальное и вертикальное разрешение мы задали равным 2795. В большинстве графических редакторов при создании изображения задаётся разрешение 71 пиксель на дюйм (ppi - pixel per inch)). Так вот, 71ppi это и есть 2795 пикселя на метр. Разрешение используется для придания изображению физической длины (для вывода на принтер например).

После заголовков расположена палитра цветов. Если её нету, то после заголовков сразу начинается изображение. Изображения с палитрами мы пока рассматривать не будем.

Данные изображения BMP

Изображение состоит из пикселей. Формат пикселей задаётся глубиной цвета (смотрите выше). В нашем примере мы использовали 32 бита на пиксель. 32-ух битный цвет обычно состоит из четырёх каналов: альфа (прозрачность), красный, зелёный, синий: ARGB (Alpha, Red, Green, Blue). Иногда альфа-канал не используется, в этом случае изображение всё равно может занимать 32 бита, просто при вычислениях не обращают внимания на значения одного канала. В этом случае названия каналов записываются так: XRGB.

Каждый канал занимает 8 бит (1 байт) и может принимать 256 значений: от нуля до 255 (от 0x00 до 0xff).

В bmp изображение хранится построчно снизу вверх, т.е. первыми записываются нижние строки, затем верхние. Удостоверьтесь в этом: загрузите одно из изображений из первого упражнения и сохраните только половину строк этого изображения в другой файл.

При 32-ухбитной глубине цвета каналы в bmp записываются так: BGRA. Именно в таком порядке: синий, зелёный, красный, альфа.

Размер строки данных в изображении bmp должнен быть кратен четырём (в байтах). Если это не так, то строка дополняется нулями. Это происходит если используется 1,2,4,8,16,24 бита на канал. Например, у нас есть изображение шириной в 3 пикселя и мы используем 16-битный цвет. Ширина строки: 16*3 = 48 (6 байт). Но длина строки должна быть кратной четырём, поэтому добавляются ещё два байта и длина строки в данном примере будет равна восьми байтам. Хотя в последних двух байтах каждой строки и не будет хранится полезной информации. Нужно учитывать условие кратности размера строки четырём при работе с не 32-ух битными изображениями.

Теперь продолжним с нашим примером и с помощью кода создадим изображение. Каждый пиксель будет инициализироваться случайным цветом:

std::ofstream os("temp.bmp", std::ios::binary);

unsigned char signature[2] = { 'B', 'M' };
unsigned int fileSize = 14 + 40 + 100*100*4;
unsigned int reserved = 0;
unsigned int offset = 14 + 40;

unsigned int headerSize = 40;
unsigned int dimensions[2] = { 100, 100 };
unsigned short colorPlanes = 1;
unsigned short bpp = 32;
unsigned int compression = 0;
unsigned int imgSize = 100*100*4;
unsigned int resolution[2] = { 2795, 2795 };
unsigned int pltColors = 0;
unsigned int impColors = 0;

os.write(reinterpret_cast<char*>(signature), sizeof(signature));
os.write(reinterpret_cast<char*>(&fileSize), sizeof(fileSize));
os.write(reinterpret_cast<char*>(&reserved), sizeof(reserved));
os.write(reinterpret_cast<char*>(&offset),   sizeof(offset));

os.write(reinterpret_cast<char*>(&headerSize),  sizeof(headerSize));
os.write(reinterpret_cast<char*>(dimensions),   sizeof(dimensions));
os.write(reinterpret_cast<char*>(&colorPlanes), sizeof(colorPlanes));
os.write(reinterpret_cast<char*>(&bpp),         sizeof(bpp));
os.write(reinterpret_cast<char*>(&compression), sizeof(compression));
os.write(reinterpret_cast<char*>(&imgSize),     sizeof(imgSize));
os.write(reinterpret_cast<char*>(resolution),   sizeof(resolution));
os.write(reinterpret_cast<char*>(&pltColors),   sizeof(pltColors));
os.write(reinterpret_cast<char*>(&impColors),   sizeof(impColors));

unsigned char x,r,g,b;

for (int i=0; i < dimensions[1]; ++i)
{
	for (int j=0; j < dimensions[0]; ++j)
	{
		x = 0;
		r = rand() % 256;
		g = rand() % 256;
		b = rand() % 256;
		os.write(reinterpret_cast<char*>(&b),sizeof(b));
		os.write(reinterpret_cast<char*>(&g),sizeof(g));
		os.write(reinterpret_cast<char*>(&r),sizeof(r));
		os.write(reinterpret_cast<char*>(&x),sizeof(x));
	}
}

os.close();

В результате выполнения данного кода в папке с вашим проектом (если вы запускали программу через отладчик (F5)) или в папке Debug решения (если вы запускали исполняемый файл .exe) будет создан файл temp.bmp, который можно открыть в любом простмотрщике картинок. Изображение состоит из цветных точек.

На этом всё.