Вернуться наверх
aco.ifmo.ru photonic
вернуться в оглавление предыдущая глава предыдущий параграф следующий параграф следующая глава


Работа с файлами в формате BMP

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

Структура данных файла в формате BMP имеет следующий вид:

BITMAPFILEHEADER
BITMAPINFOHEADER
RGBQUAD array
Color-index array

В начале идет структура заголовка файла (BITMAPFILEHEADER), имеющая следующий вид:

typedef struct tagBITMAPFILEHEADER
{
    WORD bfType;
    DWORD bfSize;
    WORD bfReserved1;
    WORD bfReserved2;
    DWORD bfOffBits;
} BITMAPFILEHEADER, *PBITMAPFILEHEADER;

bfType
Тип файла. Должен быть "BM".
bfSize
Размер файла в байтах.
bfReserved1, bfReserved2
Зарезервированные поля.
bfOffBits
Смещение битового массива относительно начала файла.

Далее следует структура информационного заголовка (BITMAPINFOHEADER), имеющая следующий вид:

typedef struct tagBITMAPINFOHEADER
{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;

biSize
Размер структуры.
biWidth, biHeight
Ширина и высота изображения в пикселах соответственно.
biPlanes
Количество плоскостей. Использовалось ранее при небольшой глубине фвета. Сейчас, при числе цветов 256 и больше, оно всегда равно 1. Сохранено для совместимости.
biBitCount
Глубина цвета в битах на пиксель.
biCompression
Тип сжатия. Если компрессия не используется, то флаг имеет значенине BI_RGB. Возможны варианты BI_RLE8, BI_RLE4, BI_BITFIELDS и BI_JPEG.
biSizeImage
Размер изображения в байтах. Если изображение не сжато (то есть поле biCompression установлено в BI_RGB), то здесь может быть записан 0.
biXPelsPerMeter, biYPelsPerMeter
Горизонтальное и вертикальное разрешение (в пикселях на метр) соответственно. Важно в первую очередь для устройства, на которое будет выводиться битовый массив.
biClrUsed
Количество используемых цветов кодовой таблицы. Если значение поля равно 0, то используется максимально возможное количество цветов, которые разрешены значением поля biBitCount.
biClrImportant
Количество основных цветов. Определяет число цветов, необходимых для отображения изображения. Если значение поля равно 0, то используются все цвета.

За информационным заголовком следует таблица цветов, представляющая собой массив структур RGBQUAD (4-байтовых полей). Каждое поле соответствует своему цвету в палитре, а три байта из четырех – синей, зеленой и красной компонентам этого цвета. Последний байт каждого поля зарезервирован и должен быть равен 0.

После таблицы цветов находятся данные изображения, которое по строкам растра записано снизу вверх, а внутри строки – слева направо. Длина каждой строки выровнена на границу в 4 байта (при длине строки, некратной четырем, она дополняется нулями).

Теперь рассмотрим пример реализации чтения графических файлов в формате BMP приложением, построенным на основе MFC с использованием библиотеки IPL:

//------------------------------------------------------------------------------
// Чтение файла в формате BMP

BITMAPINFOHEADER* ipLoad(const char* fname)
{
    if(!fname)
        return NULL;

    BITMAPINFOHEADER* infohdr = NULL;
    ifstream fsrc;

    try
    {
        BITMAPFILEHEADER filehdr;
        BITMAPINFOHEADER bmphdr;
    // Открыть файл
        fsrc.open(fname, ios::in | ios::binary);
        if(fsrc.fail())
            throw runtime_error("Ошибка чтения графического файла");
    // Чтение структуры BITMAPFILEHEADER
        fsrc.read((char*)&filehdr, sizeof(BITMAPFILEHEADER) );
        if(fsrc.fail())
            throw runtime_error("Ошибка чтения заголовка файла BMP");

    // Проверка типа файла
        if(filehdr.bfType != 0x4d42)
            throw runtime_error("Неверный тип исходного файла");

    // Чтение структуры BITMAPINFOHEADER
        fsrc.read((char*)&bmphdr, sizeof(BITMAPINFOHEADER));
        if(fsrc.fail())
            throw runtime_error("Ошибка чтения информационного заголовка файла BMP");
    // Проверка типа файла
        if(bmphdr.biSize != 0x28)
            throw runtime_error("Неверный тип исходного файла");

    // Определение размера массива цветов
        int colorbytes = filehdr.bfOffBits - sizeof(BITMAPFILEHEADER) - sizeof(BITMAPINFOHEADER);
    // Определение размера BITMAPINFOHEADER
        int totalbytes = filehdr.bfSize - sizeof(BITMAPFILEHEADER);
    // Размещение BITMAPINFOHEADER
        infohdr = (BITMAPINFOHEADER*) new unsigned char[totalbytes];
        if(!infohdr)
            throw runtime_error("Ошибка выделения памяти для размещения изображения");
    // Скопировать BITMAPINFOHEADER
        memcpy(infohdr, &bmphdr, sizeof(bmphdr) );
    // Определение смещения массива цветов
        char* quads = (char*)infohdr + sizeof(BITMAPINFOHEADER);
    // Определение смещения массива изображения
        char* pixels = (char*)quads + colorbytes;
    // Чтение массива цветов
        fsrc.read((char*)quads, colorbytes);
    // Чтение массива изображения
        fsrc.read((char*)pixels, totalbytes - colorbytes - sizeof(BITMAPINFOHEADER));
    // Закрыть файл
        fsrc.close();
    }
    catch(runtime_error e)
    {
        TRACE0(e.what());
        if(infohdr)
        {
            free( infohdr );
            infohdr = 0;
        }
    }
    return infohdr;
}


//------------------------------------------------------------------------------

// Чтение и размещение изображения

void CTestDoc::Serialize(CArchive& ar)
{
    if(ar.IsStoring())
    {
    }
    else
    {
    // Если в памяти уже имеется изображение, то ее необходимо освободить
        if(m_img != NULL)
        {
            iplDeallocate(m_img, IPL_IMAGE_ALL);
        }

    // Чтение структуры данных файла BMP
        BITMAPINFOHEADER* bmphdr = ipLoad(ar.GetFile()->GetFilePath());

    // Размещение изображения в памяти
        if(bmphdr)
        {
        // Создание структуры заголовка изображения
            m_img = iplCreateImageHeader(3, 0, IPL_DEPTH_8U, "RGB", "BGR",
                       IPL_DATA_ORDER_PIXEL, IPL_ORIGIN_BL, IPL_ALIGN_DWORD, bmphdr->biWidth,
                       bmphdr->biHeight, NULL, NULL, NULL, NULL);
        // Преобразование прочитанного из файла изображения
            iplConvertFromDIB(bmphdr, m_img);

        // Обновление отображения
            UpdateAllViews(NULL);
        }
        else
        {
            TRACE0("Ошибка при чтении файла BMP");
        }
    }
}