Kopírování OpenGL okna do DIBu

Občas potřebujeme sejmout obrazovku v OpenGL a poté s ní pracovat jako s obyčejnou bitmapou. V tomto článku vám ukáži získání obsahu OpenGL okna a jeho uložení do DIBu ve formě nekomprimované bitmapy. Jediný omezením může být 24 bitová barevná hloubka obrazovky.

Celý kód článku pojmeme jako jedinou funkci, která bude zajišťovat všechny potřebné operace.

HANDLE COpenGLView::CreateDIB()// Sejme OpenGL obrazovku a vytvoří z ní DIB, jehož handle vrátí

{

BeginWaitCursor();// Přepne kurzor na přesýpací hodiny

V první fázi potřebujeme získat rozměry klientské oblasti okna. Tj. velikost zobrazované scény.

CRect rect;// Obdélník

GetClientRect(&rect);// Grabování velikosti klientské oblasti

CSize size(rect.Width(), rect.Height());// Šířka a výška okna

TRACE("Klientská plocha: (%d; %d)\n", size.cx, size.cy);// Pro ladění

Řádky musejí být zarovnávány na 32 bytů, za předpokladu 24 bitů na pixel, takže co je navíc, odstřihneme.

size.cx -= size.cx % 4;

TRACE("Konečná klientská plocha: (%d; %d)\n", size.cx, size.cy);// Pro ladění

Nyní potřebujeme alokovat paměť pro jednotlivé pixely. Počet pixelů získáme velice jednoduše. Vynásobíme výšku obrázku se šířkou. Abychom dostali potřebnou paměť musíme ještě násobit třemi, protože má každý pixel 3 byty (RGB).

int NbBytes = 3 * size.cx * size.cy;// Velikost paměti

unsigned char *pPixelData = new unsigned char[NbBytes];// Alokace paměti

Pomocí funkce glReadPixels() zkopírujeme pixely z obrazovky do právě alokované paměti. Předávané parametry vyjadřují x, y souřadnice levého dolního rohu, šířku a výšku kopírované oblasti, barevnou hloubku (GL_RGB = 24 bitů, GL_RGBA = 32 bitů), typ pixelových dat a ukazatel do paměti, kam se mají data nakopírovat.

::glReadPixels(0, 0, size.cx, size.cy, GL_RGB, GL_UNSIGNED_BYTE, pPixelData);// Kopírovaní pixelů

Deklarujeme a vyplníme hlavičku DIBu.

BITMAPINFOHEADER header;// Hlavička DIBu

header.biWidth = size.cx;// Šířka

header.biHeight = size.cy;// Výška

header.biSizeImage = NbBytes;// Počet bytů obrázku

header.biSize = sizeof(BITMAPINFOHEADER);// Velikost této struktury

header.biPlanes = 1;// Vždy jedna

header.biBitCount = 24;// Barevná hloubka

header.biCompression = BI_RGB;// Typ komprese -> nekomprimovaná

header.biXPelsPerMeter = 0;

header.biYPelsPerMeter = 0;

header.biClrUsed = 0;

header.biClrImportant = 0;

Vygenerujeme handle globální paměti pro DIB o velikosti hlavičky sečtené s počtem bytů.

HANDLE handle = (HANDLE)::GlobalAlloc(GHND, sizeof(BITMAPINFOHEADER) + NbBytes);

Pokud se ukazatel nerovná NULL byla alokace úspěšná. V takovém případě uzamkneme handle a tím na něj zároveň získáme ukazatel. Potom zkopírujeme hlavičku i data a odemkneme handle.

if(handle != NULL)// OK

{

char *pData = (char *) ::GlobalLock((HGLOBAL) handle);// Uzamkne handle

memcpy(pData, &header, sizeof(BITMAPINFOHEADER));// Zkopíruje hlavičku

memcpy(pData + sizeof(BITMAPINFOHEADER), pPixelData, NbBytes);// Zkopíruje data

::GlobalUnlock((HGLOBAL)handle);// Odemkne handle

delete [] pPixelData;// Uvolnění paměti pro data

Nastavíme původní tvar kurzoru a vrátíme právě vytvořené handle DIBu.

EndWaitCursor();// Původní tvar kurzoru

return handle;// Vrátí handle DIBu

}

Při neúspěchu vrátíme NULL

delete [] pPixelData;// Uvolnění paměti pro data

EndWaitCursor();// Původní tvar kurzoru

return NULL;// DIB nebyl nahrán

}

Až přestaneme DIB používat musíme jej smazat pomocí funkce GlobalFree(). Jedinou výjimkou by bylo, kdybychom tento DIB předali do schránky, která by se o smazání postarala sama.

A nakonec ukázka použití této funkce (uvnitř nějaké funkce třídy okna).

HANDLE hDib = CreateDIB();// Vytvoří DIB

if (hDib)// Byl vytvořen?

{

// Práce s DIBem

::GlobalFree(hDib);// Nakonec ho smažeme

}

Pokud nepotřebujete získat handle DIBu, ale stačí vám mít jen DC zobrazované OpenGL scény, je to velmi jednoduché. Získejte DC okna ve kterém se OpenGL scéna vykresluje pomocí funkce GetDC().

napsal: Milan Turek - Woq <nalim.kerut (zavináč) email.cz>

Zdrojové kódy