Формат BMP

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

Описание

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

Нам понадобятся только два подзаголовка: BMP Header и DIB Header. Первый подзаголовок содержит ID «BM«, размер файла, два зарезервированных поля и смещение, по которому расположен массив данных о цвете.

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

Учитывая, что числовые данные хранятся в формате little-endian, загрузить их в память можно одной командой fread. Порядок байт при этом менять не нужно.

Следует учесть ещё одну особенность хранения информации в BMP. Если представить информацию о цвете пикселей как упорядоченный набор строк из блоков по 24 бита (RGB) или по 32 бита (RGBA), то данные будут храниться построчно, начиная с последней строки и заканчивая первой.

Таким образом, в файле храниться отраженное по вертикали изображение. Причем байты каждого блока расположены в обратном порядке, то есть для модели RGB данные о цвете каждого пикселя будут сохранены в виде BGR, а для модели RGBA — в виде ABGR.

А вот последовательность пикселей в строке естественна: первый пиксель — крайний слева, последний пиксель — крайний справа.

В качестве примера представим изображение размера 3х2 в виде набора (n, rgb), где n — номер пикселя, а rgb — компоненты цвета этого пикселя. Тогда исходное изображение

(1, rgb) (2, rgb) (3, rgb)

(4, rgb) (5, rgb) (6, rgb)

будет храниться в BMP-файле в виде:

(4, bgr) (5, bgr) (6, bgr)

(1, bgr) (2, bgr) (3, bgr)

Исходники

bmp_header.h

/*
  bmp reader header

*/

#ifndef BMP_READER_H
#define BMP_READER_H

/*  Выравнивание для структур соответствует 1 байту,
    так как структуры считываются напрямую из файла.
    Между блоками данных не должно быть промежутков.
*/
#pragma pack(1)
typedef struct {
  unsigned char ID[2];
  unsigned int size;
  unsigned char unused[4];
  unsigned int offset;
} BMPHeader;

typedef struct {
  unsigned int bytes_in_header;
  unsigned int width;
  unsigned int height;
  unsigned short planes;
  unsigned short bpp;
  unsigned int comp;
  unsigned int size_raw;
  unsigned int pwidth;
  unsigned int pheight;
  unsigned int colors;
  unsigned int impcolors;
} DIBHeader;

typedef struct {
  unsigned int Red;
  unsigned int Green;
  unsigned int Blue;
  unsigned int Alpha;
} BITFields;
#pragma pop

/* Структура, полностью описывающая все необходимые данные BMP-файла */
typedef struct {
  BMPHeader hdr;
  DIBHeader dib;
  BITFields bf;
  char* data;
} BMPInfo;

BMPInfo* bmp_load(char* fname); /* Загрузка BMP-файла */
void bmp_free(BMPInfo* info); /* Освобождение памяти, соотвующей загруженному
                                файлу */
char* bmp_get_err(); /* Получение сообщения об ошибке чтения
                        (при возникновении) */

#endif

bmp_header.c

/*
  BMPReader source

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "bmp_reader.h"

#ifndef BMP_READER_C
#define BMP_READER_C

/* Указатель на сообщение об ошибке */
char* errmsg;

/* Варианты сообщений об ошибке */
char* errmsgs[] = {"Can't open file",
                    "Not BMP format",
                    "",
                    };

/* Вывод данных структуры BMP Header в терминал */
void printBmpHeader(BMPHeader* hdr) {
  printf("ID: %c%c\nsize: %d\noffset: %d\n", hdr->ID[0], hdr->ID[1],
    hdr->size, hdr->offset);
};

/* Вывод данных структуры DIB Header в терминал */
void printDibHeader(DIBHeader* dib) {

  printf("\nbytes_in_header: %d"
    "\nwidth: %d"
    "\nheight: %d"
    "\nplanes: %d"
    "\nbpp: %d"
    "\ncomp: %d"
    "\nsize_raw: %d"
    "\npwidth: %d"
    "\npheight: %d"
    "\ncolors: %d"
    "\nimpcolors: %d\n",
    dib->bytes_in_header, dib->width,
      dib->height, dib->planes,
      dib->bpp, dib->comp, dib->size_raw, dib->pwidth,
      dib->pheight, dib->colors, dib->impcolors);
};

/* Вывод масок битовых полей для компонентов цвета */
void printBITFields(BITFields* bf) {

  printf("\nRed: %08x"
    "\nGreen: %08x"
    "\nBlue: %08x"
    "\nAlpha: %08x\n",
    bf->Red, bf->Green,
      bf->Blue, bf->Alpha);
};

/* Чтение данных о цвете из файла, пользуясь данными прочитанных заголовков */
void bmp_read_raw(FILE* fp, BMPInfo* info) {
  unsigned int sz_pix = info->dib.bpp / 8; /* кол-во байт на 1 пиксель */

  /* Кол-во байт данных изображения */
  unsigned int sz_bytes = info->dib.width * info->dib.height * sz_pix;

  /* Выделяем соответствующий объем памяти */
  info->data = (char*)malloc(sz_bytes);

  /* Получаем указатель для записи данных */
  unsigned char* p = (unsigned char*)info->data;

  /* i - номер строки. Читаем строки от последней до первой */
  for(int i=info->dib.height-1; i>=0; i--) {
    /* Перемещаемся в начало строки */
    fseek(fp, info->hdr.offset + i*info->dib.width*sz_pix, SEEK_SET);
    /* Читаем строку слева на право, меняя порядок байт для каждого пикселя */
    for(int j=0; j<info->dib.width; j++) {
      for(int k=sz_pix-1; k>=0; k--)
        fread(p+k, 1, 1, fp);
      p += sz_pix;
    };
  };
};

/* Загружаем BMP файл с указанным именем */
BMPInfo* bmp_load(char* fname) {
  BMPInfo* info = (BMPInfo*)malloc(sizeof(BMPInfo));
  FILE* fp = fopen(fname, "rb");
  if(!fp) {
    errmsg = errmsgs[0];
    return NULL;
    free(info);
  };

  /* Читаем BMP Header */
  fread(&amp;info->hdr, 1, sizeof(BMPHeader), fp);
  /* Затем DIB Header */
  fread(&amp;info->dib, 1, sizeof(DIBHeader), fp);

  /* Выводим информацию подзаголовков в терминал */
  printBmpHeader(&amp;info->hdr);
  printDibHeader(&amp;info->dib);

  /* При наличии масок для компонент цвета, выводим эти маски */
  if(info->dib.comp == 3) {
    fread(&amp;info->bf, 1, sizeof(BITFields), fp);
    printBITFields(&amp;info->bf);
  };

  /* Выделяем память для цветов пикселей и заполняем её данными из файла */
  bmp_read_raw(fp, info);

  fclose(fp); /* Закрываем файл */
  return info; /* Возвращаем указатель на структуру BMPInfo */
};

/* Процедура освобождения памяти, занимаемой данными BMP-файла */
void bmp_free(BMPInfo* info) {
  if(info->data)
    free(info->data);
  free(info);
};

/* Функция для получения сообщения об ошибке */
char* bmp_get_err() {
  return errmsg;
};

#endif

Исходники можно доработать в плане обработки ошибок чтения файла с изображением. Данный вариант работает для корректных 24-битных (RGB) и 32-битных (RGBA) изображений.


33

Добавить комментарий

Ваш e-mail не будет опубликован.