Okg Bmp Header

Как се записва видео информацията от OpenGL като картинка

Table of Contents

Интро

Тъй като OpenGL за съжаление няма рутина за записване на видео информация в user-friendly файлов формат трябва ние да си напишем такава. Поради леснотата на запис ще изберем файловият формат bitmap (.bmp). Повече информация можете да намерите на http://en.wikipedia.org/wiki/BMP_file_format .
Какво всъщност ще направим. Ще отворим нов файл за писане на binary информация, ще запишем така наречения BMP Header (информация в началото на файла, която указва, че той представлява BMP картинка, както и допълнителна информация като резолюция, големина на картинката, по колко бита на пиксел ще отделяме и тн.), и накрая след него ще запишем самата видео информация пиксел по пиксел, като за всеки от тях ще запишем информацията за всеки цвят. Обърнете внимание, че при BMP тя се записва в BGR формат, а не в RGB!

Code

Ето и кода, който ние е нужен. Първо ни трябва структура, в която да пазим BMP header-a (ще ползваме bitmap header v3, който е доста стандартен и се отваря на повечето места)

Структура:

#pragma pack (1)
struct BMPHeader
{
    // bmp file header
    unsigned char magicNum[2];
    unsigned int size;
    unsigned short reserved[2];
    unsigned int offset;

    // v3 header additional info
    unsigned int v3size;
    unsigned int width, height;
    unsigned short colorPlanes;
    unsigned short bitsPerPixel;
    unsigned int compressMethod;
    unsigned int imageSize;
    unsigned int horResolution;
    unsigned int verResolution;
    unsigned int colPalette;
    unsigned int impColours;

    BMPHeader(int xSize, int ySize)
    {
        // standard header values
        magicNum[0] = 'B'; // First BMP flag character
        magicNum[1] = 'M'; // Second BMP flag character
        size = sizeof(BMPHeader) + xSize * ySize * 3; // 24bpp
        offset = sizeof(BMPHeader);

        // v3 header values
        v3size = 40;                // 40 bytes for v3 header
        width = xSize;                // Width of image
        height = ySize;                // Height of image
        colorPlanes = 1;            // This must be set to one
        bitsPerPixel = 24;            // No Alpha Channel, smaller images
        compressMethod = BI_RGB;        // BI_RGB or 0, it is predefined so
        imageSize =
            xSize * ySize * 3;        // Only colour plane size is saved here
        horResolution = 2835;            // The horizontal resolution of the image (wikipedia)
        verResolution = 2835;            // The vertical resolution of the image    (wikipedia)
        colPalette = 0;                // Default (screen colours used)
        impColours = 0;            // Default (all colors are important)
    }
};

Искам да отбележа, че не съм виновен за indent-a. Какво ли не правих да го оправя…
Директивата #pragma pack(1) преди структурата е (най-вероятно) непонятна за повечето и за това ще обясня само накратко какво прави - това е указател към компилатора да пакетира структурата на байт (а не на най-голямата променлива, тоест на 4 байта в случая). С други думи - това, което Добрев и Бъчваров са говорели, че ако имаме 7 байтова структура, тя всъщност ще се представи като 8 байтова… тук отпада. Правим това с цел файлът да се запише без "дупки" в него между хедър-а и цветовата информация (което може да прецака картинката).

След като имаме тази симпатична структурка е достатъчно просто да викнем конструктура й BMPHeader hdr(100, 100); за да получим хедър за картинка с размер 100 на 100 пиксела. Вътрешно съм го направил сам да си смята размерите, които ще са му нужни, както и повечето магически числа като вертикална и хоризонтална резолюция. Като цяло 2-те числа, които подаваме на конструктора, са ширината и височината на картинката съответно.

    FILE* out = fopen("rasterLine.bmp", "wb");

След като сме отворили файл за писане не е лошо да вземем пикселите, които сме изчертали и да ги запишем временно някъде. Това ще направим на 3 етапа:

1. Ще намерим къде се намира прозореца ни и колко е голям.

    GLint viewport[4];
    glGetIntegerv(GL_VIEWPORT, viewport);

2. Ще създадем временно пространство някъде, където да запишем видео-информацията.

    int imageSize = viewport[2] * viewport[3] * 3;
    unsigned char* image = new unsigned char[imageSize];

3. Ще извикаме функция на OpenGL, която ни записва някъде видео информацията от подаден от нас участък на екрана.

    glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3], GL_RGB, GL_UNSIGNED_BYTE, image);
    for (int i = 0; i < imageSize; i += 3) swap(image[i], image[i+2]);

Питате се WTF прави последния ред? Точно това, което казах да обърнете внимание - swap-ва картинката от RGB към BGR (или обратно де… не съм сигурен (blush)… но във всеки случай ни трябва).

Следващата стъпка е да си създадем хедър за картинката. Поне това с горе-написаната структура е лесно (тъй като си имаме хубав конструктор)

    BMPHeader hdr(viewport[2], viewport[3]);

Така и не обясних какво записахме в масивчето viewport[]
viewport[0] - Долна лява Х координата на прозореца ни
viewport[1] - Долна лява У координата на прозореца ни
viewport[2] - Широчина (width) на прозореца ни
viewport[3] - Височина (height) на прозореца ни
Реално това, което подаваме на конструктора е width и height.

Сега остана само да пльоснем цялата тази информация във файла, който отворихме.

    fwrite(&hdr, sizeof(hdr), 1, out);
    fwrite(image, sizeof(unsigned char), viewport[2] * viewport[3] * 3, out);
    fflush(out); fclose(out);

fflush() ни указва да изпразни буферите, въпреки, че fclose() също трябва да го вика, но нека сме сигурни :)

Накрая все пак можем да ръгнем едно delete(image) за да нямаме memory-leak, че Мишо пак ще се радва и ще хули С/С++, че нямат garbage collector.

Ето и цялостен код на save() функцията ни:

void save(char* fileName)
{
    FILE* out = fopen(fileName, "wb");

    GLint viewport[4];
    glGetIntegerv(GL_VIEWPORT, viewport);

    int imageSize = viewport[2] * viewport[3] * 3;
    unsigned char* image = new unsigned char[imageSize];
    glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3], GL_RGB, GL_UNSIGNED_BYTE, image);
    for (int i = 0; i < imageSize; i += 3) swap(image[i], image[i+2]);

    BMPHeader hdr(viewport[2], viewport[3]);
    fwrite(&hdr, sizeof(hdr), 1, out);
    fwrite(image, sizeof(unsigned char), viewport[2] * viewport[3] * 3, out);
    fflush(out); fclose(out);

    delete(image);
    return;
}

Надявам се, че все на някого ще помогне това :)

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License