Grafické aplikace ve Visual C++ (6.)


V této lekci se posuneme o velký krok kupředu, protože vám ukážu zdrojové kódy, které vám velmi usnadní práci s Direct Draw objekty. Veškerý kód je součástí SDK. Dále se blíže podíváme na funkci Blt().

6.1.Třídy CDisplay a CSurface

Bylo napsáno pár jednoduchých funkcí, které vám však velmi usnadní život s DirectDraw. Zdrojové kódy těchto funkcí jsou součástí SDK, které jste instalovali na začátku našeho povídání. Konkrétně byste je měli najít v adresáři SDK na disku. Pokud je tedy chcete používat, je asi nejlepší si do projektu zkopírovat soubory ddutil.hddutil.cpp a dxutil.h. Jako celé DD i tyto soubory prošly celkem radikálním vývojem. Na počátku obsahovaly pouze globální funkce, nyní obsahují dvě třídy, které se kompletně starají o grafický interface a povrchy.

Vložte tedy oba soubory do vašeho projektu, nejlépe ve FileView přes popup menu, kde vyberte položku Add files to project. V ClassView by se měli objevit dvě nové třídy: CDisplay a CSurface. Aby šel projekt zkompilovat, musíte vypnout pro soubor ddutil.cpp předkompilované hlavičky, nebo do souboru vložit stdafx.h vašeho projektu. Doporučuji udělat to první. To uděláte v menu Project Settings..., kde v levém okénku najdete požadovaný soubor a vpravo vyberete druhou kartu, kde zvolíte Precompiled header, kde zvolíte první volbu Not using precompiled header. Pro snazší orientaci jsem vám stáhl tento obrázek:

Nyní si zkuste, zdali vám půjde projekt zkompilovat.

Celý systém je velice jednoduchý. Stačí do souboru control.h vložit hlavičkový soubor  ddutil.h a můžete začít. Vložte tedy následující řádek někam na začátek souboru control.h:
#include "ddutil.h"

A nyní již budete jen mazat kód, který jsme vytvořili v minulých lekcích, protože vše obstará třída CDisplay a CSurface.

1. Vytvořte objekt m_theDisplay (typu CDisplay) ve třídě CControl. Poté vložte do funkce DDInit() volání funkce CreateFullScreenDisplay(), jenž je členská funkce třídy CDisplay. Funkce přijímá celkem 4 parametry. za prvé je to HANDLE našeho okna, který přijímá i naše funkce DDInit(), dále jsou to rozměry obrazovky v pixelech, vy můžete zadat konstanty definované výše. Jsou to RES_X, RES_Y a RES_BITDEPTH. To je vše, zbytek funkce až na poslední řádek vymažte.

2. Vytvoříme pozadí. Ve třídě CControl deklarujte proměnnou typu ukazatel CSurface m_surBackground. Ve funkce DDInit() vytvořte dynamicky objekt CSurface a zavolejte funkci CreateSurfaceFromFile()  objektu m_theDisplay. Funkce má opět 4 parametry. První je ukazatel na ukazatel na objekt povrchu, druhý je řetězec bitmapy, která se nahraje do povrchu a poslední dva parametry jsou požadované rozměry povrchu, pokud zde zadáte 0, vytvoří se povrch o velikosti bitmapy. Dejte pozor aby na konci funkce DDInit() zůstal řádek, který aktivuje smyčku zpráv!

Funkce DDInit() vypadá po úpravě takto:


HRESULT CControl::DDInit(HWND hWnd)
{
    HRESULT dwResult;
   
// 
    // Inicializace grafickeho rozhrani DD

    dwResult = m_theDisplay.CreateFullScreenDisplay(hWnd, RES_X, RES_Y, RES_BITDEPTH);
    if(dwResult != DD_OK) {
        TRACE("Cannot init direct draw system due %d\n", dwResult);
        return dwResult;
    }
  
 // 
    // Vytvorime povrch pozadi

    m_surBackground = new CSurface;
    dwResult = m_theDisplay.CreateSurfaceFromBitmap(&m_surBackground, "background.bmp", 0, 0);
    if(dwResult != DD_OK) {
        TRACE("Cannot create background due %d\n", dwResult);
        return dwResult;
    }
  
 //
    // Spustime smycku zprav

    m_bReady = TRUE;

    return dwResult;
}



3. Přepíšeme funkci UpdateFrame(). Tuto funkci upravte následujícím způsobem:

void CControl::UpdateFrame()
{
    if(m_bReady) {
      
//...
       CRect rcBackground;
       rcBackground.SetRect(0,0,RES_X, RES_Y);

       m_theDisplay.Blt(0, 0, m_surBackground, rcBackground);
      
//...
       //...

       m_theDisplay.Present();
    }
}


Vidíte, že se od původní funkce moc neliší, ale využívá nové třídy CDisplay.

6.2 Další možnosti třídy CDisplay

6.2.1 Paleta

Nyní si povíme něco o paletách. Třídy CDisplay samozřejmě podporuje i palety. Nejprve musíte deklarovat objekt LPDIRECTDRAWPALETTE, který může být lokální. Poté zavoláte funkci CreatePaletteFromBitmap(), která má pouze dva parametry. První je objekt LPDIRECTDRAWPALETTE, který jsme již deklarovali a druhý je řetězec bitmapy, ze které se paleta vytvoří.

Důležité je, že funkce vytvoří objekt palety podle palety bitmapy a ne podle barev bitmapy! Často se totiž stává, že si vytvoříme bitmapu, která obsahuje všechny barvy, které potřebujeme, ale paleta této bitmapy zůstane standardní tzn. paleta základních 256 barev (jako ve Windows). Takže musíte vytvořit bitmapu v nějakém externím rastrovém editoru (např. Malování, Photoshop) a uložit s 256 barevnou paletou. Tento externí editor pak vytvoří adaptivní paletu, která přesně odpovídá vaší bitmapě. Když ho pak importujete do Visual C++, můžete klidně bitmapu palety zmenšit na 1x1 pixel s libovolnou barvou, aby nezabíral cenné kB v .exe souboru. Taková bitmapa si ovšem uchová svojí paletu (vytvořenou externím editorem) a ta se také použije v naší funkci.

Dále musíte zavolat členskou funkci rozhraní SetPalette(), která přijímá přesně tento objekt. A to je vše - paleta je nastavena a pokud je správně vytvořena, všechny vaše bitmapy se zobrazí ve správných barvách. Nejlepší je udělat jakousi koláž ze všech použitých bitmap a pak ji uložit podle výše vysvětleného postupu v externím editoru.

Následuje jednoduchý příklad nastavení bitmapy:

// Musite mit predem zvolanou funkci CreateFullScreenDisplay()
LPDIRECTDRAWPALETTE lpDDPalette;

m_theDisplay.CreatePaletteFromFile(&lpDDPalette, "paleta.bmp");
m_theDisplay.SetPalette(lpDDPalette);

Pokud pracujete v 16-bitech (65 tis. barev), je nastavení palety zbytečné. To znamená, že paletu má smysl nastavovat jen v 8-bitech (256 barev) - nižší barevnou hloubku ani neuvádím, kdo by chtěl pracovat s 16 barvami. Naopak pokud pracujete v 8-bitech je paleta nutná, protože jinak se použijí základní barvy Windows a bitmapa se dost znehodnotí (pokud se zrovna netrefíte).
 

6.2.2. Nastavení průhledné barvy neboli nastavení Color Key

Nastavení průhledné barvy je velmi důležitá vlastnost povrchů DirectDraw. Color key neboli barevný klíč, označí barvu v povrchu, kterou nechceme vykreslovat to znamená, že výsledný obrázek bude v tomto místě průhledný. Color key nastavujete pro každý vytvořený povrch zvlášť. My zatím nemáme žádný obrázek, který bychom potřebovali vykreslovat transparentně. Princip color key spočívá v označení průhledné barvy pomocí makra RGB. Ve třídě CSurface objevíte funkci SetColorKey(), která přijímá jeden parametr právě jako barvu.
 

// Musite mit predem zvolanou funkci CreateFullScreenDisplay()
//definice objektu povrchu

CSurface *lpMySurface = new CSurface;

//vytvoreni povrchu a prirazeni ukazatele, povrch je inicializovan bitmapou

m_theDisplay.CreateSurfaceFromFile(&lpMySurface, "pokus.bmp", 0, 0);

// Prirazeni barevne hodnoty CK // Takto priradime jako pruhlednou barvu cernou

lpMySurface->SetColorKey(RGB(0, 0, 0);

N
yní když zavoláte funkci
Blt() s tím to povrchem, funkce pozná, že povrch má definovaný CK a použije blit s CK a černá barva bude průhledná.

 

6.3. Funkce Blt()

Jistě jste  si všimli, že funkce Blt() je i ve třídě CDisplay, to je ale funkce, která je zjednodušená a jen málo využívá možnosti pravé funkce Blt(). O této funkci jsem se již několikrát zmiňoval, ale nikdy jsem pořádně neřekl, co všechno vlastně umí. Už je vám asi jasné, že slouží ke kopírování - blittování - dat (bitů) mezi povrchy. V souboru "ddraw.h" má následující deklaraci:

HRESULT Blt(
  LPRECT lpDestRect,
  LPDIRECTDRAWSURFACE7 lpDDSrcSurface, 
  LPRECT lpSrcRect,
  DWORD dwFlags,
  LPDDBLTFX lpDDBltFx
);

Jak vidíte má spoustu parametrů a my si je teď konečně všechny probereme.
 

  1. LPRECT lpDestRect - ukazatel na objekt obdélníku (může být i CRect), který slouží jako cílový obdélník.

  2. LPDIRECTDRAWSURFACE7 lpDDSrcSurface - ukazatel na zdrojový povrch

  3. LPRECT lpSrcRect - ukazatel na objekt obdélníku (může být i CRect), který slouží jako zdrojový obdélník.

  4. DWORD dwFlags - další parametry viz. níže

  5. LPDDBLTFX lpDDBltFx - další parametry parametrů viz. níže

Funkci vám osvětlí následující obrázek:

Tak už je vám to aspoň trošku jasné? Doufám, že ano. Nyní si podrobně vysvětlíme poslední dva záhadné parametry.

1. dwFlags

Tento parametr může nabývat mnoha hodnot, které se navíc dají kombinovat. Následující tabulka vysvětlí, co která hodnota dělá:
 

Hodnota

Funkce

DDBLT_COLORFILL

Tento parametr použijeme, pokud chceme vyplnit obdélníkovou oblast jednou barvou. Pak dosadíme místo druhého a třetího parametru NULL a barvu definujeme v posledním parametru viz níže.

DDBLT_DDFX

Tímto parametrem řekneme funkci, že chceme použít speciální efekty definované v posledním parametru.

DDBLT_KEYDEST

Tímto parametrem říkáme, že chceme použít color key, který je ovšem nastavený na cílovém povrchu.

DDBLT_KEYSRC

Stejné jako předchozí jen pro zdrojový povrch. Častěji používáno než DDBLT_KEYDEST.

DDBLT_WAIT

Tímto parametrem říkáme, že funkce Blt() má čekat, až se dokončí všechny předchozí blittovací akce a teprve potom se provede a skončí. Používáme ho velmi často.

Hodnot je samozřejmě ještě víc, ale nám budou tyto bohatě stačit. Hodnoty se mohou mezi sebou kombinovat pomocí operátoru | (např. DDBLT_WAIT | DDBLT_COLORFILL). Na konci této části bude několik příkladů, jak je možno použít uvedené hodnoty. Druhá struktura již není tak důležitá, ale přesto s její pomocí můžeme nastavit zajímavé efekty.

 

2. lpDDBltFx

Princip použití je trošku odlišný, protože musíte nejdříve vytvořit strukturu typu DDBLTFX, kterou pak předáte funkci Blt() při vlastním blittování. Jako první musíte inicializovat velikost struktury, abyste s ní mohli pracovat (viz příklad). Následující tabulka ukazuje některé vlastnosti této struktury:
 

Proměnná

Použití

dwSize

Velikost struktury v bytech. Tento parametr musí být bezpodmínečně nastaven dříve než začnete se strukturou pracovat (sizeof(DDBLTFX);).

dwDDFX

Tady můžete nastavit speciální efekty při blitování (rotace: DDBLTFX_ROTATE180, převracení: DDBLTFX_MIRRORLEFTRIGHT, problikávání: DDBLTFX_NOTEARING ....etc.).

dwFillColor

Pamatujete si na hodnotu DDBLT_COLORFILL? Právě zde se nastavuje barva výplně, opět pomocí makra RGB.

Pokud používáme parametr dwDDFX, musíme funkci Blt() říct, že ho opravdu chceme použít nastavením hodnoty DDBLT_DDFX. To samé platí i pro dwFillColor a DDBLT_COLORFILL. Nyní si ukážeme několik příkladů, jak použít funkci Blt() v praxi:

 //1. priklad
 //normalni kopirovani bitmapy mezi dvema povrchy
 //oba povrchy jsou predem vytvorene

  CRect dest, src;

  //nastaveni dvou ctvercu 200x200
  //cilovy ctverec
 
  dest.left = 0;  
  dest.top = 0;  
  dest.right = 200;  
  dest.bottom = 200;

  //zdrojovy ctverec  
  src.left = 0;  
  srct.top = 0;  
  src.right = 200;  
  src.bottom = 200;

  //pouzivame zdrojovy color key
  DDSetColorKey(srcSurface, CLR_INVALID);

  //vlastni kopirovani - blitting
  destSurface->Blt(&dest, srcSurface, &src, DDBLT_WAIT | DDBLT_KEYSRC, NULL);

  //-------------------------------------------------

  //2. priklad
  //kopirovani jednobarevne plochy
  //oba povrchy jsou predem vytvorene

  CRect dest;

  //struktura DDBLTFX je potreba kvuli definici barvy
  DDBLTFX fx;

  fx.dwSize = sizeof(DDBLTFX); //nutne
  fx.dwFillColor = RGB(150, 100, 200); //nejaka pekna barva

  //nastaveni jen ciloveho ctverce 200x200
  //cilovy ctverec

  dest.left = 0;
  dest.top = 0;
  dest.right = 200;
  dest.bottom = 200;
  //nastaveni color key je zbytecne, protoze se jedna o jednolitou plochu
  //vlastni kopirovani - blitting

  destSurface->Blt(&dest, NULL, NULL, DDBLT_WAIT | DDBLT_COLORFILL, &fx);

Použití ostatních efektů je analogické, přesto pokud byste měli nějaké speciální požadavky, napište mi.
 

Poznámka: Pokud nenastavíte color key pro povrch, který pak kopírujete s parametrem DDBLT_KEYSRC, funkce Blt() vrací chybnou hodnotu, takže nezapomeňte důsledně hlídat, který povrch má a který ne nastavený color key.

Poznámka: Abyste mohli použít přímo tuto funkci s použitím třídy CSurface a CDisplay musíte využít funkcí CSurface::GetDDrawSurface() a CDisplay::GetBackSurface(), které obě vrací ukazatele na pravé povrchy DirectDraw.

6.3. Závěr

A je tu zase konec. Doufám, že se vám dnešní lekce líbila a že vám přinesla zase něco nového. Už jsme se určitě dostali za půlku celé teorie DirectDraw. Příště si vytvoříme třídy CSprite, která bude představovat jakýkoliv kreslený obrázek.

Těším se příště nashledanou.
                                                                                                                                                                                                                                                                                                               Jiří Formánek