В начале 2000-х годов Microsoft пыталась сделать BMP-файлы основным видом графических файлов. В связи с этим была добавлена возможность хранения в этих файлах графических файлов других графических форматов (включая png, jpeg и др.). Но сейчас от этой идеи отказались и BMP-файлы используются лишь для хранения несжатых изображений.
Помимо базового варианта (3 байта на точку, BMP-24) может быть "серый" файл (1 байт на точку) и некоторые другие. Далее мы будем рассматриать только файлы формата BMP-24.
Строчки в bmp-файле хранятся в обратном порядке, то есть в начале идет самая нижняя строка изображения. Внутри строки байты имеют порядок
В начале bmp-файла идет заголовок следующей структуры:
typedef unsigned __int16 WORD;
typedef struct {
WORD bfType; // 0x4d42 | 0x4349 | 0x5450
int bfSize; // размер файла
int bfReserved; // 0
int bfOffBits; // смещение до поля данных,
// обычно 54 = 16 + biSize
int biSize; // размер струкуры в байтах:
// 40(BITMAPINFOHEADER) или 108(BITMAPV4HEADER)
// или 124(BITMAPV5HEADER)
int biWidth; // ширина в точках
int biHeight; // высота в точках
WORD biPlanes; // всегда должно быть 1
WORD biBitCount; // 0 | 1 | 4 | 8 | 16 | 24 | 32
int biCompression; // BI_RGB | BI_RLE8 | BI_RLE4 |
// BI_BITFIELDS | BI_JPEG | BI_PNG
// реально используется лишь BI_RGB
int biSizeImage; // Количество байт в поле данных
// Обычно устанавливается в 0
int biXPelsPerMeter;// горизонтальное разрешение, точек на дюйм
int biYPelsPerMeter;// вертикальное разрешение, точек на дюйм
int biClrUsed; // Количество используемых цветов
// (если есть таблица цветов)
int biClrImportant; // Количество существенных цветов.
// Можно считать, просто 0
} BMPheader;
Отметим, что в этом примере кода тип int предполагается 32-битовым.
Если в вашем случае это не так, то вместо int надо использовать
тип 4-х байтового целого числа. Аналогично, __int16 означает 16-битовое
целое число. Если ваш компилятор такого идентификатора не знает, то
его надо заменить на соответствующий.
Чтение файла формата BMP-24 можно осуществить следующей функцией. В переменных mx,my возвращаются размеры файла. Для графических данных функция выделяет память (new int[mx*my]). Именно этот указатель и возвращатся в качестве результата. Он указывает на одномерный массив целых чисел, в котором лежат последовательно цветовые значения всех пикселей изображения. В каждое целое 4-х байтовое число упаковано по 3 байта RGB в формате
int *loadBMP( const char *fname, int &mx, int &my )
{
mx = my = -1;
FILE *f = fopen( fname, "rb" );
if( !f ) return NULL;
BMPheader bh; // File header sizeof(BMPheader) = 56
size_t res;
// читаем заголовок
res = fread( &bh, 1, sizeof(BMPheader), f );
if( res != sizeof(BMPheader) ) { fclose(f); return NULL; }
// проверяем сигнатуру
if( bh.bfType!=0x4d42 && bh.bfType!=0x4349 && bh.bfType!=0x5450 ) { fclose(f); return NULL; }
// проверка размера файла
fseek( f, 0, SEEK_END);
int filesize = ftell(f);
// восстановим указатель в файле:
fseek( f, sizeof(BMPheader), SEEK_SET);
// проверим условия
if( bh.bfSize != filesize ||
bh.bfReserved != 0 ||
bh.biPlanes != 1 ||
(bh.biSize!=40 && bh.biSize!=108 && bh.biSize!=124)||
bh.bfOffBits != 14+bh.biSize ||
bh.biWidth <1 || bh.biWidth >10000 ||
bh.biHeight<1 || bh.biHeight>10000 ||
bh.biBitCount != 24 || // пока рассматриваем только полноцветные изображения
bh.biCompression != 0 // пока рассматриваем только несжатие изображения
)
{
fclose(f);
return NULL;
}
// Заголовок прочитан и проверен, тип - верный (BGR-24), размеры (mx,my) найдены
int mx = bh.biWidth;
int my = bh.biHeight;
int mx3 = (3*mx+3) & (-4); // Compute row width in file, including padding to 4-byte boundary
unsigned char *tmp_buf = new unsigned char[mx3*my]; // читаем данные
res = fread( tmp_buf, 1, mx3*my, f);
if( (int)res != mx3*my ) { delete []tmp_buf; fclose(f); return NULL; }
// данные прочитаны
fclose(f);
// выделим память для результата
v = new int[mx*my];
// Перенос данных (не забудем про BGR->RGB)
unsigned char *ptr = (unsigned char *) v;
for(int y = my-1; y >= 0; y--) {
unsigned char *pRow = tmp_buf + mx3*y;
for(int x=0; x< mx; x++) {
*ptr++ = *(pRow + 2);
*ptr++ = *(pRow + 1);
*ptr++ = *pRow;
pRow+=3;
ptr ++;
}
}
delete []tmp_buf;
return v; // OK
}
...
int mx, my; // для размеров изображения
int *v = loadBMP("picture.bmp", mx, my); // выделяем память и читаем туда файл
...
delete [] v; // освобождаем память
Для записи в BMP-файл можно использовать фунцию
int saveBMP( const char *fname, int *v, int mx, my ) // В каждом элементе упаковано все три RGB-байта
{
BMPheader bh; // Заголовок файла, sizeof(BMPheader) = 56
memset( &bh, 0, sizeof(bh) );
bh.bfType =0x4d42; // 'BM'
// Найдем длину строки в файле, включая округление вверх до кратного 4:
int mx3 = (3*mx+3) & (-4);
int filesize = 54 + my*mx3;
bh.bfSize = filesize;
bh.bfReserved = 0;
bh.biPlanes = 1;
bh.biSize = 40;
bh.bfOffBits = 14 + bh.biSize;
bh.biWidth = mx;
bh.biHeight = my;
bh.biBitCount = 24;
bh.biCompression= 0;
FILE *f = fopen( fname, "wb" );
if( !f ) return -1;
size_t res;
// пишем заголовок
res = fwrite( &bh, 1, sizeof(BMPheader), f );
if( res != sizeof(BMPheader) ) { fclose(f); return -1; }
// приготовим временный буфер
unsigned char *tmp_buf = new unsigned char[mx3*my];
// Перенос данных (не забудем про RGB->BGR)
unsigned char *ptr = (unsigned char *) v;
for(int y = my-1; y >= 0; y--) {
unsigned char *pRow = tmp_buf + mx3*y;
for(int x=0; x< mx; x++) {
*(pRow + 2) = *ptr++;
*(pRow + 1) = *ptr++;
*pRow = *ptr++;
pRow+=3;
ptr++;
}
}
// сбросим в файл
fwrite( tmp_buf, 1, mx3*my, f );
fclose(f);
delete []tmp_buf;
return 0; // OK
}