Как се записва видео информацията от OpenGL като картинка
Интро
Тъй като 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;
}
Надявам се, че все на някого ще помогне това :)





