Nyní je ten správný čas, abychom si vytvořily tzv. Off-screen surfaces neboli pomocné povrchy, do kterých si uložíme naše bitmapy. Tyto povrchy posléze vykreslujeme do zadního povrchu, který jsme vytvořili v minulé lekci. V dnešní lekci vytvoříme další dvě členské funkce třídy CControl. První z nich vytvoří Off-screen surface a vrátí ukazatel na něj a druhá do tohoto povrchu nahraje bitmapu ze souboru. Poté tento povrch můžete blittovat do zadního bufferu, jehož obsah se zobrazí na monitoru.
Off-screen surfaces jsou opět buffery, které ji6 nemusí být v grafické paměti (záleží na velikosti grafické paměti). Pokud vlastníte grafickou kartu, který na sobě nese méně než 8MB, je vhodné všechny pomocné povrchy vytvářet v systémové paměti.
Zpravidla vytváříme povrchy, které jsou velké jako bitmapa, kterou chceme do povrchu nahrát. Dále záleží na hloubce barev, se kterou právě pracujeme. Pochopitelně paměťové nároky se zvýší, pokud pracujete v 16-bitech (65 tis. barev) oproti 8-bitům (256 barev) (o 32-bitech nemluvě).
Povrchy vytváříme stejně jako v minulých lekcích, s tím rozdílem, že
strukturu DDSURFACEDESC
naplníme jinými daty a navíc přesně určíme
rozměry vytvářeného povrchu. Přidejte funkci
CreateSurface() do třídy CControl. Hlavičku funkce vidíte níže. Tato funkce vám
pouze alokuje paměť a vrátí ukazatel na prázdný povrch. Všimněte si, že funkce
má jako první parametr ukazatel na ukazatel, to proto, že mění hodnotu
ukazatele.
Jednoduchý kód jak by mohla vypadat vaše funkce pro vytvoření povrchu :
HRESULT
CControl::CreateSurface(DWORD dwWidth, DWORD dwHeight, LPDIRECTDRAWSURFACE7 * lpSurface) {
DDSURFACEDESC2 ddsd; // 1 ZeroMemory( &ddsd, sizeof(ddsd)); ddsd.dwSize = sizeof(ddsd); // 2 DWORD dwRet; ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH; // 3 ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; // 4 ddsd.dwHeight = dwWidth; // 5 ddsd.dwWidth = dwHeight; dwRet = m_lpDD->CreateSurface(&ddsd, lpSurface, NULL); // 6 if(dwRet != DD_OK) { TRACE("Cannot create surface due %d\n", dwRet); } return dwRet; }
Nyní si rozebereme každý řádek zvlášť:
DDSURFACEDESC2
. dwSize
. Tento atribut musí
být vždy inicializován dříve než začneme se strukturou pracovat (viz minulá
lekce). dwCaps
určuje typ povrchu. V našem
případě je to Off-screen surface. dwWidth
(šířka)
a dwHeight
(výška). Jednotky jsou pochopitelně pixely.
CreateSurface()
,
kterou již známe z minulé lekce, když jsme vytvářeli přední buffer. Funkce vrací hodnotu DD_OK pokud je úspěšná.
Nyní máme v paměti alokovanou oblast paměti, do které si můžete nahrát bitmapu. Tuto bitmapu můžete nahrát ze zdrojů přímo ve VC++, ale i z naprosto nezávislého souboru bitmapy. Osobně doporučuji druhý způsob, protože je bitmapa uchována mimo spustitelná soubor, který pak nenarůstá do obrovských velikostí. Následující funkce nahrává bitmapu mimo soubor aplikace. Jako první parametr jí předáváte cestu (absolutní nebo relativní) na bitmap. Druhý parametr určuje povrch, do kterého se bitmapa bude nahrávat.
HRESULT
CControl::CopyBitmap(CString csFile, LPDIRECTDRAWSURFACE7 Surface) { HDC hdcImage, hdc = NULL; BITMAP bm; DDSURFACEDESC2 ddsd; HBITMAP hbm; DWORD dwRet; // // Get handle to bitmap hbm = (HBITMAP)LoadImage(NULL, csFile, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE|LR_CREATEDIBSECTION);//1 if(!hbm) { dwRet = GetLastError(); TRACE("Cannot load image due %d\n", dwRet); return dwRet; } // // Get size of the bitmap GetObject(hbm, sizeof(BITMAP), &bm); //2 // // Make sure this surface is restored. Surface->Restore();//3 // // Create DC for our bitmap hdcImage = CreateCompatibleDC(NULL);//4 ASSERT(hdcImage); // // Select bitmap intoa a memoryDC so we can use it. SelectObject(hdcImage, hbm);//5 // // get size of surface. //6 ddsd.dwSize = sizeof(ddsd); ddsd.dwFlags = DDSD_HEIGHT | DDSD_WIDTH; Surface->GetSurfaceDesc(&ddsd); //blit bitmap to GDI surface dwRet = Surface->GetDC(&hdc);//7 if (dwRet != DD_OK) { TRACE("Cannot get DC of surface due %d\n", dwRet); return dwRet; } dwRet = StretchBlt( hdc, 0, 0, ddsd.dwWidth, ddsd.dwHeight, hdcImage, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);//8 if(dwRet == 0) { dwRet = GetLastError(); TRACE("Cannot blit bitmap to surface due %d\n", dwRet); } else { dwRet = ERROR_SUCCESS; } Surface->ReleaseDC(hdc);//9 // // Release handle to GDI object and handle to DC DeleteObject(hbm);//10 DeleteDC(hdcImage); return dwRet;
}
Výše uvedená funkce je trošku složitější, takže si ji pěkně vysvětlíme. Využíváme zde GDI, které je popsáno v lekci č.2.
LoadImage()
, ve které určujeme jméno bitmapy (strBMPFile
)
a další parametry, které nám určí, že bitmapu nahráváme ze souboru atd.
Detailně je tato funkce popsána v MSDN. GetObject()
naplní strukturu BITMAP
, která mimo jiné obsahuje šířku a výšku
bitmapy. Restore()
realokuje paměť určenou pro
povrch. Může se stát, že se povrch ztratí, například pří přepnutí rozlišení,
pak je třeba zavolat tuto funkci, aby nám realokovala paměť. My pak musíme
nahrát znovu bitmapu do povrchu !!! Problematika ztrácení povrchů bude
vysvětlena nejspíše v příští lekci.GetSurfaceDesc()
přijímá jako parametr
ukazatel ne strukturu DDSURFACEDESC2
. StretchBlt()
kopíruje kontext zařízení
bitmapy do kontextu povrchu. Problém se získáním DC našeho povrchu je v tom, že operace je časově náročná tzn. že pokud budete ve vašem programu stále přistupovat k DC, program se citelně zpomalí.
Poznámka:
Pokud používáte 8-bitovou barevnou hloubku, pravděpodobně bitmapa nebude
vypadat tak jak má, jelikož není nastavena žádná paleta. Pokud nepracujete na
úplně pomalém počítači, můžete nastavit 16-bitů a vše bude v pořádku.
Nejprve vytvoříme povrch pozadí, do kterého nahrajeme požadovanou bitmapu ve formátu .bmp. Nejlepší je, když bitmapa je stejně velká jako je nastavené rozlišení (viz minulá lekce), pak nedochází k deformaci bitmapy (i když teoreticky funkce CopyBitmap() umí bitmapu roztáhnout, zkuste si to a uvidíte, že bitmapa bude zkreslená). Ve třídě CControl deklarujte novou proměnnou typu ukazatel na povrch, který bude ukazovat na povrch našeho pozadí. Typ je stejný jako u zadního a předního bufferu tj. LPDIRECTDRAWSURFACE7 a proměnnou nazvěte třeba m_lpDDSBackground. Dále na konec funkce DDInit() přidejte následující kód:
//
// Jako prvni vytvorime povrch pro nase pozadi, velikost je stejna jako
rozliseni protoze pozadi pokryva celou obrazovku
dwResult = CreateSurface(RES_X, RES_Y, &m_lpDDSBackground);
if(dwResult != DD_OK) {
TRACE("Cannot create surface for
background due %d\n", dwResult);
return dwResult;
}
//
// Za druhe do vytvoreneho povrchu nakopirujeme zvolenou bitmapu
dwResult = CopyBitmap("background.bmp", m_lpDDSBackground);
if(dwResult != DD_OK) {
TRACE("Cannot copy bitmap to surface
due %d\n", dwResult);
return dwResult;
}
Nyní už můžeme vykreslit naší bitmapu do zadního bufferu. To provedeme ve funkci UpdateFrame(). Těsně před prohozením připište tento kód :
CRect rcBackground;
rcBackground.SetRect(0,0,RES_X, RES_Y);
m_lpDDSBack->Blt(&
rcBackground,
m_lpDDSBackground, &
rcBackground, DDBLT_WAIT ,
NULL);
Funkce Blt()
provede blitting do zadního bufferu.
Funkce přijímá dva obdélníky odkud a kam se bude kopírovat. V našem případě jsou
oba obdélníky stejné. Druhý
parametr je ukazatel na povrch, ze kterého data kopírujeme tzn. ukazatel na
náš pomocný povrch pozadí. Parametr DDBLT_WAIT
zajistí, že funkce počká až
se dokončí všechny předchozí blittovací operace. Funkci Blt()
budeme ještě dále rozebírat v další lekci.
A už je tu zase konec. Po dnešní lekci uvidíte na monitoru vaší bitmapu, které se pořád dokola překresluje z našeho pomocného povrchu. Prohazování není vidět, jelikož vykreslujeme pořád tu samou bitmapu. Kdybyste si nahráli dvě bitmapy a po té je střídali, viděli byste jak DirectDraw nedovolí žádné "šeredné" problikávání. Příklad z dnešní lekce si samozřejmě můžete stáhnou v sekci Downloads.
Těším se příště nashledanou.
© 2001
Vogel Publishing,
design by
ET NETERA