на школьную страницу...

Как прочитать BMP файл на C++

С.И.Хашин

BMP (BitMaP) - основной метод хранения несжатых графических файлов. Фотография с 6-ти мегапискельного фотоаппарата в этом формате будет иметь размер 18М байт.

В начале 2000-х годов Microsoft пыталась сделать BMP-файлы основным видом графических файлов. В связи с этим была добавлена возможность хранения в этих файлах графических файлов других графических форматов (включая png, jpeg и др.). Но сейчас от этой идеи отказались и BMP-файлы используются лишь для хранения несжатых изображений.

Помимо базового варианта (3 байта на точку, BMP-24) может быть "серый" файл (1 байт на точку) и некоторые другие. Далее мы будем рассматриать только файлы формата BMP-24.

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

BGR BGR ...
Кроме того, длина каждой строки выровнена до кратного четырех. То есть для изображения шириной 3 точки длина строки равна 12 байт (3 точки по 3 байта, всего 9 байт, затем увеличиваем длину строки до кратного 4).

В начале 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 в формате

R + 256*G + 65536*B.
В случае какой-либо ошибки память не выделяется, возвращается NULL.
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
}


Flag Counter