GrafickΘ aplikace ve Visual C++ (5.)

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.

6.1 Off-screen surfaces

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∞).

6.2 Vytvo°enφ povrchu

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ßÜ¥:
  1. Definice objektu ddsd typu DDSURFACEDESC2.
  2. Inicializece atributu dwSize. Tento atribut musφ b²t v₧dy inicializovßn d°φve ne₧ zaΦneme se strukturou pracovat (viz minulß lekce).
  3. Nynφ urΦφme, kterΘ atributy struktury jsou platnΘ, tedy majφ v²znam. Chceme Üφ°ku,  v²Üku a ₧e povrch je Off-screen.
  4. Atribut dwCaps urΦuje typ povrchu. V naÜem p°φpad∞ je to Off-screen surface.
  5. Dßle nßsledujφ rozm∞ry povrchu : dwWidth (Üφ°ka) a dwHeight (v²Üka). Jednotky jsou pochopiteln∞ pixely.
  6. Jako poslednφ krok zavolßme funkci CreateSurface(), kterou ji₧ znßme z minulΘ lekce, kdy₧ jsme vytvß°eli p°ednφ buffer.

Funkce vracφ hodnotu DD_OK pokud je ·sp∞Ünß.

6.3. Nahrßnφ bitmapy do povrchu

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.

  1. Jako prvnφ zφskßme handle na naÜi bitmapu. To nßm zajistφ funkce Win32 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.
  2. Dßle zφskßme rozm∞ry naÜφ bitmapy. Funkce GetObject() naplnφ strukturu BITMAP, kterß mimo jinΘ obsahuje Üφ°ku a v²Üku bitmapy.
  3. Funkce 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.
  4. Vytvo°φme pam∞¥ov² kontext za°φzenφ pro naÜφ bitmapu. (viz lekce Φ. 2)
  5. Vybereme bitmapu do tohoto kontextu.
  6. Nynφ zφskßme zp∞tn∞ parametry povrchu, do kterΘho chceme bitmapu nahrßt. Funkce GetSurfaceDesc() p°ijφmß jako parametr ukazatel ne strukturu DDSURFACEDESC2.
  7. Dßle zφskßme handle na kontext za°φzenφ naÜeho povrchu. Po tΘ m∙₧eme p°ekopφrovat bitmapu jako v b∞₧nΘm GDI. Jedin² problΘm, ₧e tento handle nem∙₧eme udr₧ovat dßle a musφme ho ihned uvolnit.
  8. Funkce StretchBlt() kopφruje kontext za°φzenφ bitmapy do kontextu povrchu.
  9. Uvoln∞nφ DC povrchu!!! Toto je velmi d∙le₧itΘ, proto₧e jinak byste nemohli dßle pracovat s povrchem.
  10. Jako poslednφ krok uvolnφme handle na bitmapu a DC bitmapy.

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.

6.4 Nastavenφ a vykreslenφ pozadφ

    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.

6.5 Zßv∞r

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.

                                                                                                                                                                                                                                                                                                                    Ji°φ Formßnek