Формат 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(&info->hdr, 1, sizeof(BMPHeader), fp); /* Затем DIB Header */ fread(&info->dib, 1, sizeof(DIBHeader), fp); /* Выводим информацию подзаголовков в терминал */ printBmpHeader(&info->hdr); printDibHeader(&info->dib); /* При наличии масок для компонент цвета, выводим эти маски */ if(info->dib.comp == 3) { fread(&info->bf, 1, sizeof(BITFields), fp); printBITFields(&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) изображений.
Добавить комментарий