DirectX (12.)

Ve 12. lekci o DirectX budeme pokračovat v budování projektu z minulé lekce. Konkrétně dnes vytvoříme knihovnu Display.dll a pokusíme se implementovat třídu CSurface. Funkce nebudu rozbírat do úplných detailů, protože většina kódu Vám již bude známá.

12.1. Knihovna Display.dll

Nejprve tedy vložme zcela novou knihovnu. Z menu File vyberte volbu New. Na dialogu zvolte kartu Projects a označte MFC AppWizard (dll). Potřebujeme totiž vytvořit rozšířenou knihovnu MFC. Opět nezapomeňte zaškrtnout volbu Add to current project a do políčka Project name vepište Display. Na dialogu AppWizardu zvolte MFC Extension DLL a stiskněte tlačítko Finish. Do ClassView přibude projekt Display, který se nastaví jako aktivní - označte tedy jako aktivní projekt Game.

Nyní nastavme vzájemné vazby mezi projekty. Z menu Project vyberte volbu Dependencies. Na dialogu nastavte, aby projekt Game byl závislý na projektu Display takto:

Dále zeditujme nastavení nového projektu, abychom mohli použít knihovnu Common.dll a aby se soubor Display.dll vytvářel v Debug a Release celého projektu. Využijeme kontextového menu ClassView. Pravým tlačítkem klikněte na projekt Display a vyberte položku Settings. Na dialogu nastavte toto nastavení:

Nastavení samozřejmě proveďte i pro Release. Všimněte si, že nastavení je vlastně stejné jako v projektu Game (navíc jsou přidané knihovny potřebné pro DirectDraw, ale to už známe). Po kompilaci přibude do adresáře Debug či Release knihovna Display.dll.

12.2. Nové třídy

Knihovna bude obsahovat několik tříd:

Protože CDisplay je závislá na CSurface, vytvořme nejprve třídu CSurface:

CSurface

Členské proměnné

 

 

Typ

Název

Popis

BOOL

m_bInit

Proměnná je TRUE pokud je objekt povrchu řádně zinicializován. Pak lze volat další metody jako Blt() apod.

BOOL

m_bColorKey

Proměnná je TRUE pokud se povrch má vykreslovat se zapnutým color key (zkrátka zda-li má či nemá CK).

DWORD

m_dwWidth

Šířka povrchu v pixelech. Velikost povrchu je určena velikostí bitmapy, kterou do povrchu nahráváme (viz metody Create())

DWORD

m_dwHeight

Výška povrchu v pixelech.

CString

m_csBitmap

Řetězec, do něhož uložíme cestu k bitmapě v datovém souboru. To se bude hodit, až bude potřeba povrch obnovit.

LPDIRECTDRAWSURFACE7

m_lpDDSurface

Ukazatel na vlastní objekt povrchu (rozhraní IDirectDrawSurface7)

CDisplay*

m_pDisplay

Pomocný ukazatel na objekt CDisplay.

Metody

 

 

Návratová hodnota

Název s parametry

Popis

HRESULT

Create(DWORD, DWORD, UINT)

Následující tři funkce inicializují objekt povrchu - liší se pouze parametry. První verze má jako první dva parametry požadovanou velikost povrchu. Třetí parametr je určuje další vlastnosti povrchu např. CK apod.

HRESULT

Create(CString, UINT)

Druhá verze má první parametr řetězec s úplnou cestou bitmapy v datovém souboru. Povrch má velikost bitmapy a bitmapa je také nahrána do povrchu. Druhý parametr je stejný jako u předchozí funkce Create().

HRESULT

Create(LPDIRECTDRAWSURFACE7)

Třetí a poslední funkce Create() je vlastně kopírovací konstruktor. Vytvoří nový povrch pomocí již existujícího povrchu - udělá kompletní kopii. Jednu z těchto funkcí je třeba zavolat, aby bylo možné volat ostatní metody.

HRESULT Blt(CRect, CRect, CSurface*) Následující funkce zajišťují vykreslování do aktuálního povrchu buď z jiného povrchu nebo jednolité výplně. První verze má dva parametry typu CRect, což jsou obdélníky, ze kterých a do kterých se kopíruje příslušná část povrchu určeného třetím parametrem.
HRESULT Blt(CRect, COLORREF) Druhá verze vyplní obdélník zadaný prvním parametrem spojitou barvou definovanou druhým parametrem.
HRESULT CopyBitmap(CString) Tato funkce slouží ke kopírování bitmapy ze souboru do předem vytvořeného povrchu. Využívá se při obnově povrchu apod.
DWORD Height() Vrací výšku povrchu.
DWORD Width() Vrací šířku povrchu.
BOOL IsColorKey() Vrací TRUE pokud povrch je s CK, jinak FALSE.
BOOL IsInit() Vrací stav proměnné m_bInit.
BOOL IsValid() Vrací stav proměných m_bInit a zároveň ukazatele this (viz. dále).
LPDIRECTDRAWSURFACE7* GetSurface() Vrací přímo ukazatel na povrch DirectDraw.
void SetColorKey() Nastaví proměnnou m_bIsColorKey na TRUE (zapne CK)
HRESULT Restore(BOOL) Obnoví povrch při ztrátě. Parametr určuje zda-li povrch bude obnoven a naplněn původní bitmapu nebo jen obnoven.
HRESULT Release() Uvolní paměť povrchu a zruší objekt. Tato metoda není potřeba volat explicitně, protože se volá z destruktoru.
LPDIRECTDRAWSURFACE7 operator LPDIRECTDRAWSURFACE7() Přetížený operátor. Objekt CSurface půjde přetypovat na LPDIRECTDRAWSURFACE7.

Konstruktor a destruktor třídy neuvádím, protože to beru jako samozřejmost.

Deklarace třídy není složitá:

class AFX_EXT_CLASS CSurface
{
    private:
        LPDIRECTDRAWSURFACE7 m_lpDDSurface;
        CDisplay *m_pDisplay;

        BOOL m_bInit;
        BOOL m_bColorKey;

        DWORD m_dwWidth;
        DWORD m_dwHeight;
        CString m_csBitmap;

    public:
    
   //
        // Create functions

        HRESULT Create(DWORD dwWidth, DWORD dwHeight, UINT nFlags = DDFLG_COLORKEY);
        HRESULT Create(CString strBMP, UINT nFlags = DDFLG_COLORKEY);
        HRESULT Create(LPDIRECTDRAWSURFACE7 lpSurface);
      
 //
        HRESULT Release();
        HRESULT Restore(BOOL bFill = false);
        HRESULT SetColorKey();
       
//
        // Copy bitmap to surface

        HRESULT CopyBitmap(CString csBitmap);
        //
        // Inline functions
        LPDIRECTDRAWSURFACE7 GetSurface();
        BOOL IsInit() {return m_bInit;}
        BOOL IsColorKey() {return m_bColorKey;}
        DWORD Width() {return m_dwWidth;}
        DWORD Height() {return m_dwHeight;}
        BOOL IsValid();
        //
        // Blitting functions
        HRESULT Blt(CRect rcDestin, CRect rcSource, CSurface* surSource);
        HRESULT Blt(CRect rcDestin, COLORREF Color);
        //
        // Overloaded operator to return pointer to surface
        operator LPDIRECTDRAWSURFACE7() {return m_lpDDSurface;};

    public:
        CSurface();
        ~CSurface();
};

Na deklaraci není nic zvláštního ani překvapivého snad kromě makra AFX_EXT_CLASS, o kterém již byla také řeč.

Nyní se podívejme na konstruktor a destruktor třídy:

CSurface::CSurface()
{
    m_bInit = false;
    m_bColorKey = false;

    m_dwHeight = 0;
    m_dwWidth = 0;

    m_pDisplay = disGetDisplay();

    SAFE_NULL(m_lpDDSurface);
}

CSurface::~CSurface()
{
    if(m_bInit) { //if you do not call release() before destruct object
        Release(); //call now
    }
}

Opět zde není nic nového. Všimněte si volání uvolňovací funkce v destruktoru (viz. výše). Funkce disGetDisplay() vrací přímo objekt CDisplay. Je to globálně vytvořená funkce, o které bude řeč později.

Proberme implementace zbylých funkcí:

HRESULT CSurface::Create(DWORD dwWidth, DWORD dwHeight, UINT nFlags)
{
    HRESULT dwReturn = 1;// 1...ERROR_ALREADYINIT;
    DDSURFACEDESC2 ddsd;
    //
    // Is surface already created? Is initialized display system?
    if(m_bInit || !m_pDisplay) {
        DXTRACE("Cannot create surface because it is already created or the display system is not initialized.");
        return dwReturn;
    }
    //
    // Check if the input param are valid
    ASSERT(dwWidth > 0 && dwHeight > 0);
    //
    // Create a DirectDrawSurface for this bitmap
    ZeroMemory( &ddsd, sizeof(ddsd) );
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;

    m_dwWidth = ddsd.dwWidth = dwWidth;
    m_dwHeight = ddsd.dwHeight = dwHeight;
    //
    // Creation of the new surface
    dwReturn = m_pDisplay->GetDirectDraw()->CreateSurface(&ddsd, &m_lpDDSurface, NULL);
    //
    // Try to create surface in RAM if video is full
    if(dwReturn == DDERR_OUTOFVIDEOMEMORY) {
        ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
        dwReturn = m_pDisplay->GetDirectDraw()->CreateSurface(&ddsd, &m_lpDDSurface, NULL);
    }
    if(DD_OK != dwReturn) {
        DXERR("Cannot create the surface due", dwReturn);
        return dwReturn;
    }
    m_bInit = true;
    return dwReturn;
}

Tuto funkci si probereme trochu podrobněji. Nejprve zjistíme platnost ukazatele m_pDisplay a to, zda-li povrch už nebyl jednou inicializován. Poté zkontrolujeme vstupní parametry. Dále plníme strukturu DDSURFACEDESC2, jak jsme byli zvyklí - nastavíme off-screen surface, šířku výšku a typ paměti v případě, že video paměť je plná. Vytvoříme vlastní povrch. Při neúspěchu zkusíme ještě povrch vytvořit v RAM místo video paměti. A to je vše. Detailní popis výše uvedených řádků najdete v několika předcházejících lekcích.

HRESULT CSurface::Create(CString strBMP, UINT nFlags)
{
    HRESULT dwRet = 1;// = ERROR_NOINIT;
    DWORD dwSize;
    DWORD dwWritten;
    LPBYTE lpData;
    HANDLE hFile = NULL;
    BITMAP bm;
    HBITMAP hBitmap;
    CString strTempFile = _T("C:\\surface.tmp");
    HDC hdcImage;
    HDC hdc = NULL;
    //
    // Is surface initialized ?
    // Is bitmap valid?
    // Is display initialized?

    if(m_bInit || strBMP.IsEmpty() || !m_pDisplay) {
        DXTRACE("General error has been occured while surface was created.");
        return dwRet;
    }
    //
    // Save internal copy of bitmap path

    m_csBitmap = strBMP;
    //
    // Find file in storage

    if((dwRet = stgGetFile3(strBMP, &lpData, dwSize)) != S_OK) {
        DXERR("Cannot find specified bitmap is storage due", dwRet);
        return dwRet;
    }
    //create temporary file
    hFile = (HANDLE)CreateFile(strTempFile,
                               GENERIC_WRITE ,
                               FILE_SHARE_WRITE | FILE_SHARE_READ,
                               NULL,
                               CREATE_ALWAYS,
                               FILE_ATTRIBUTE_TEMPORARY,
                               NULL);
    if(hFile == INVALID_HANDLE_VALUE) {
        dwRet = GetLastError();
        DXERR("Cannot create temporary file due", dwRet);
        delete [] lpData;
        return dwRet;
    }
    //
    // Write bitmap data to file
    WriteFile(hFile, lpData, dwSize, &dwWritten, NULL);
    if(dwWritten != dwSize) {
        dwRet = GetLastError();
        DXERR("Cannot write data to temporary file due", dwRet);
        CloseHandle((HANDLE)hFile);
        DeleteFile(strTempFile);
        delete [] lpData;
        return dwRet;
    }
    //
    // Close temporary file
    CloseHandle((HANDLE)hFile);
    //
    // Get bitmap handle
 
   hBitmap = (HBITMAP)LoadImage(NULL, strTempFile, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE|LR_CREATEDIBSECTION);
    if (hBitmap == NULL) {
        dwRet = GetLastError();
        DXERR("Cannot create bitmap handle due", dwRet);
        DeleteFile(strTempFile);
        delete [] lpData;
        return dwRet;
    }
    //
    // Get size of the bitmap
 
  if(!GetObject(hBitmap, sizeof(BITMAP), &bm))    {
        dwRet = GetLastError();
        DXERR("Cannot get info about bitmap due", dwRet);
        DeleteFile(strTempFile);
        delete [] lpData;
        return dwRet;
    }
    //
    // Create surface
 
   if(ERROR_SUCCESS != (dwRet = Create(bm.bmWidth, bm.bmHeight, nFlags))) {
        DXERR("Cannot create surface due", dwRet);
        DeleteFile(strTempFile);
        delete [] lpData;
        return dwRet;
    }
    //
    // Make sure this surface is restored.
 
   Restore();
    //
    // Create DC for our bitmap
    hdcImage = CreateCompatibleDC(NULL);
    if (!hdcImage) {
        dwRet = GetLastError();
        DXERR("Cannot create compatible DC due", dwRet);
        DeleteFile(strTempFile);
        delete [] lpData;
        return dwRet;
    }
    //
    // Select bitmap intoa a memoryDC so we can use it.
    SelectObject(hdcImage, hBitmap);
    //
    // Get DC to surface
    if ((dwRet = m_lpDDSurface->GetDC(&hdc)) != DD_OK)
    {
        DXERR("Cannot get surface DC due", dwRet);
        return dwRet;
    }
    //
    // Blit bitmap to GDI surface
    if(0 == StretchBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcImage, 0, 0, bm.bmWidth , bm.bmHeight, SRCCOPY))
    {
        dwRet = GetLastError();
        DXERR("Cannot blit bitmap to surface due", dwRet);
        return dwRet;
    }
    //
    // Release DC

    if(DD_OK != (dwRet = m_lpDDSurface->ReleaseDC(hdc))) {
        DXERR("Cannot release surface DC due", dwRet);
        return dwRet;
    }
    //
    // If we want to set color key, set it now

    if(nFlags & DDFLG_COLORKEY) {
        dwRet = SetColorKey();
    }
    //
    //release handle to GDI object and handle to DC
    DeleteDC(hdcImage);
    DeleteObject(hBitmap);
    DeleteFile(strTempFile);
    delete [] lpData;

    return dwRet;
}

Tato funkce konečně obsahuje nějaké novinky, takže si ji přiblížíme ještě více. Funkce opět obsahuje kontroly zda-li je povrch neinicializován a zda-li je inicializován objekt CDisplay. Idea funkce je taková, že nejprve nahrajeme bitmapu z datového souboru, poté vytvoříme dočasný soubor, do kterého bitmapu nahrajeme, dále získáme rozměry bitmapy, vytvoříme povrch podle těchto rozměrů a nakopírujeme bitmapu do povrchu. Je to celkem kostrbatý způsob, ale funguje to! Funkcí stgGetFile3() nahrajeme požadovanou bitmapu z datového souboru do paměťového bufferu.Funkce má tři parametry: cesta k bitmapě, ukazatel na ukazatel na buffer a velikost bufferu. Buffer je alokován až uvnitř funkce a parametr dwSize poté obsahuje velikost souboru. Poté vytvoříme nový soubor pomocí API funkce CreateFile() a zapíšeme obsah paměťového souboru do souboru na disku. Dále využíváme API funkce k získání rozměrů bitmapy. Když známe rozměry, můžeme konečně vytvořit povrch funkcí Create(DWORD,DWORD,UINT). Nakonec zbývá zkopírovat bitmapu do povrchu. To provedeme na úrovni DC s bitmapou a DC povrchu. Pokud si to uživatel přeje, nastavíme CK a na úplný závěr uvolníme všechnu alokovanou paměť.

A konečně je tu třetí verze funkce Create():

HRESULT CSurface::Create(LPDIRECTDRAWSURFACE7 lpSurface)
{
    DDSURFACEDESC2 ddsd;
    ddsd.dwSize = sizeof(ddsd);

    HRESULT dwReturn = 1;
// = ERROR_INIT or INVALID SURFACE;

    if(lpSurface && !m_bInit) {
        dwReturn = lpSurface->GetSurfaceDesc(&ddsd);
        if(dwReturn != DD_OK) {
            DXERR("You may pass invalid surface. Cannot get info about due", dwReturn);
            return dwReturn;
        }
        //
        // Init member fuction according existing surface
        m_lpDDSurface = lpSurface;
        m_dwHeight = ddsd.dwHeight;
        m_dwWidth = ddsd.dwWidth;
        m_bColorKey = ddsd.dwFlags & DDSD_CKSRCBLT;
        m_bInit = true;
        m_csBitmap = _S_EMPTY;
    }
    return dwReturn;
}

Vypadá přesně jako kopírovací konstruktor. Na začátku zjistíme informace o povrchu daném vstupním parametrem funkce. V druhé části funkce inicializujeme ostatní atributy třídy CSurface. Povrch ovšem zůstane prázdný (bez bitmapy).

Dále probereme možná nejsložitější funkci třídy, funkci CopyBitmap():

HRESULT CSurface::CopyBitmap(CString csBitmap)
{
    HRESULT dwReturn = 1;
// = ERROR_NOINIT or BITMAP NAME IS INVALID;
    DWORD dwSize;
    DWORD dwWritten;
    LPBYTE lpData;
    HANDLE hFile = NULL;
    BITMAP bm;
    HBITMAP hBitmap;
    CString strTempFile = _T("C:\\surface.tmp");
    HDC hdcImage;
    HDC hdc = NULL;
    HBITMAP hOld;
    //
    // If display initialized and storage opened
    if(!csBitmap.IsEmpty() && m_bInit) {
        //
        // Try to find file in storage
        dwReturn = stgGetFile3(csBitmap, &lpData, dwSize);
        if(dwReturn != ERROR_SUCCESS) {
            DXERR("Cannot get file from storage due:", dwReturn);
            return dwReturn;
        }
        //
        // Create temporary file
 
       hFile = (HANDLE)CreateFile(strTempFile,
                                    GENERIC_WRITE ,
                                    FILE_SHARE_WRITE | FILE_SHARE_READ,
                                    NULL,
                                    CREATE_ALWAYS,
                                    FILE_ATTRIBUTE_TEMPORARY,
                                    NULL);
        if(hFile == INVALID_HANDLE_VALUE) {
            dwReturn = GetLastError();
            DXERR("Cannot create temporary file due:", dwReturn);
            delete [] lpData;
            return dwReturn;
        }
        //
        // Write bitmap data to the temporary file
        WriteFile(hFile, lpData, dwSize, &dwWritten, NULL);
        if(dwWritten != dwSize) {
            dwReturn = GetLastError();
            DXERR("Cannot write data due:", dwReturn);
            CloseHandle((HANDLE)hFile);
            delete [] lpData;
            DeleteFile(strTempFile);
            return dwReturn;
        }
        //
        // Close it
        CloseHandle((HANDLE)hFile);
        //
        // Get bitmap handle
        // Try to load bitmap from file
        hBitmap = (HBITMAP)LoadImage(NULL, strTempFile, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE|LR_CREATEDIBSECTION);
        if (hBitmap == NULL) {
            dwReturn = GetLastError();
            DXERR("Cannot get bitmap handle due:", dwReturn);
            delete [] lpData;
            DeleteFile(strTempFile);
            return dwReturn;
        }
        //
        // Get size of the bitmap
        if(!GetObject(hBitmap, sizeof(BITMAP), &bm))
        {
            dwReturn = GetLastError();
            DXERR("Cannot get bitmap information due:", dwReturn);
            delete [] lpData;
            DeleteObject(hBitmap);
            DeleteFile(strTempFile);
            return dwReturn;
        }
        //
        // Create DC for our bitmap
        hdcImage = CreateCompatibleDC(NULL);
        if (!hdcImage) {
            dwReturn = GetLastError();
            DXERR("Cannot create temporary DC due:", dwReturn);
            delete [] lpData;
            DeleteObject(hBitmap);
            DeleteFile(strTempFile);
            return dwReturn;
        }
 
       //
        // Select bitmap into a memoryDC so we can use it.
 
      hOld = (HBITMAP) SelectObject(hdcImage, hBitmap);
        //
        // Get DC of DD surface
 
       if ((dwReturn = m_lpDDSurface->GetDC(&hdc)) == DD_OK)
        {
 
          //
            // Blit bitmap to GDI surface
 
          StretchBlt(hdc, 0, 0, Width(), Height(), hdcImage, 0, 0, bm.bmWidth , bm.bmHeight, SRCCOPY);
            if(DD_OK != (dwReturn = m_lpDDSurface->ReleaseDC(hdc)))
            {
                DXERR("Cannot release surface DC due:", dwReturn);
            }
            //
            // If we want to set color key, set it now else exit with OK
            if(m_bColorKey) {
                dwReturn = SetColorKey();
                if(dwReturn != ERROR_SUCCESS) {
                    DXERR("Cannot set CK on surface due:", dwReturn);
                }
            }
            //
            //release handle to GDI object and handle to DC
            DeleteDC(hdcImage);
            DeleteObject(hBitmap);
            DeleteFile(strTempFile);
            delete [] lpData;
   
        }
        if(dwReturn != DD_OK) {
            DXERR("Cannot get surface DC due:", dwReturn);
            delete [] lpData;
            DeleteObject(hBitmap);
            DeleteDC(hdcImage);
            DeleteFile(strTempFile);
            return dwReturn;
        }
    }
    return dwReturn;
}

Funkce funguje podobně jako druhá verze Create(). Nejprve vytáhneme bitmapu z datového souboru, ale poté ji nakopírujeme do povrchu, který byl předem vytvořen tzn., že nealokujeme další paměť pro povrch. Protože kopírování bitmapy se provádí na úrovni DC (kontextů zařízení) povrchu a DC s bitmapou, použijeme funkci StretchBlt(). Tato funkce provede vykreslení (včetně roztáhnutí čí smrštění bitmapy) z jednoho DC do jiného (parametry jsou dost podobné funkci Blt() DirectDraw s tím rozdílem, že místo povrchů se používají DC). Na konci funkce je důležité uvolnění paměti!

Povrch jsme vytvořili musíme ho ovšem i uvolnit. K tomu slouží funkce Release(), která se volá z destruktoru:

HRESULT CSurface::Release()
{
    SAFE_RELEASE(m_lpDDSurface);
//safe release surface
    m_bInit = false;
//and reset all member variable
    m_bColorKey = false;
    m_dwHeight = 0;
    m_dwWidth = 0;

    return ERROR_SUCCESS;
}

Tělo funkce je velice krátké a srozumitelné. Důležité je uvolnění rozhraní povrchu DirectDraw.

Z minulého projektu si jistě vzpomínáte na obnovu povrchů po ztrátě focusu okna. Následující funkce Restore() zajistí kompletní obnovu povrchu včetně vyplnění povrchu původní bitmapou, pokud si to uživatel přeje:

HRESULT CSurface::Restore(BOOL bFill)
{
    HRESULT dwReturn = 1;
// 1...ERROR_NOINIT;
    if(m_bInit) {
      
 //
        // Try tu restore surface

        dwReturn = m_lpDDSurface->Restore();
        if(dwReturn != DD_OK) {
            DXERR("Cannot restore surface due", dwReturn);
            return dwReturn;
        }
        if(bFill) {
          
 //
            // Refill surface by bitmap

            dwReturn = CopyBitmap(m_csBitmap);
        }
    }
    return dwReturn;
}


Jediný vstupní parametr určuje, zda-li se povrch naplní či nenaplní původní bitmapou. Jinak na funkce není nic zajímavého. Nejprve obnovíme (realokujeme) povrch pomocí funkce Restore() (stejně jsme to dělali i v minulém projektu). Poté využijeme funkce CopyBitmap() k naplnění povrchu původní bitmapou. To je vše!

K nastavení Color Key používáme funkci SetColorKey():

HRESULT CSurface::SetColorKey()
{
    HRESULT dwReturn = 1;
    DWORD dwPixel;
    DDSURFACEDESC2 ddsd;
    DDCOLORKEY ddck;

    if(m_bInit) {
      
 //
        // Now lock the surface so we can read back the converted color

        ddsd.dwSize = sizeof(ddsd);
        dwReturn = GetSurface()->Lock( NULL, &ddsd, DDLOCK_WAIT, NULL );
        if(dwReturn != DD_OK) {
            DXERR("Cannot lock surface to set color key due", dwReturn);
            return dwReturn;
        }
      
 //
        // Get first pixel

        dwPixel = *(DWORD *) ddsd.lpSurface;
      
 //
        // Mask it to bpp

        if(ddsd.ddpfPixelFormat.dwRGBBitCount < 32) {
            dwPixel &= (1 << ddsd.ddpfPixelFormat.dwRGBBitCount) - 1;
        }
    
   //
        // Unlock surface

        GetSurface()->Unlock(NULL);
 
       //
        // Set color values

        ddck.dwColorSpaceLowValue = ddck.dwColorSpaceHighValue = dwPixel;
        //
        // Set color key on surface
 
       dwReturn = m_lpDDSurface->SetColorKey(DDCKEY_SRCBLT, &ddck);
        if(dwReturn != DD_OK) {
            DXERR("Cannot set color key on surface due", dwReturn);
        }
        if(dwReturn == DD_OK) {
            m_bColorKey = true;
        }
    }
    return dwReturn;
}

Funkce vybere první pixel bitmapy a barvu tohoto pixelu nastaví jako transparentní. Abychom mohli přistoupit přímo k povrchu, musíme tento povrch "uzamknout" funkcí Lock(), která zároveň inicializuje ukazatel na pole pixelů. Poté přečteme barevnou informaci prvního pixelu a povrch odemkneme funkcí Unlock(). Zbytek metody je zcela zřejmý, nastavení CK jsme probrali v minulých lekcích.

Následující funkce vrací přímo ukazatel na povrch DirectDraw:

LPDIRECTDRAWSURFACE7 CSurface::GetSurface()
{
    //
    // Is surface initialized ?
    if(m_bInit) {
        return m_lpDDSurface;
    }
    return NULL;
}


Tuto funkci využijeme pokud potřebuje přímo pracovat s povrchem DirectDraw (například jako parametr některých funkcí).

Funkce IsValid() testuje platnost povrchu a objektu CSurface:

BOOL CSurface::IsValid()
{
    if(m_lpDDSurface && IsInit()) {
        return TRUE;
    }
    else {
        return FALSE;
    }
}

Od IsInit() se liší právě v testování platnosti povrchu. Bezpečnější je tedy používat metodu IsValid().

Jako poslední si proberme pár funkcí Blt(). Má dvě verze. První kopíruje obsah povrchu do jiného povrchu a druhá vyplňuje aktuální povrch spojitou barvou:

HRESULT CSurface::Blt(CRect rcDestin, CRect rcSource, CSurface* surSource)
{
    HRESULT dwResult = 1; // 1 means: NO INIT
  
 //
    // Check if the destination surface is valid

    if(IsValid() && surSource->IsValid()) {
      
 //
        // Blit with color key

        if(surSource->IsColorKey()) {
            while(1) {
                dwResult = GetSurface()->Blt(&rcDestin, surSource->GetSurface(), &rcSource, DDBLT_KEYSRC, NULL);
                if(dwResult != DDERR_WASSTILLDRAWING) {
                    break;
                }
            }
        }
        //
        // Blit without color key
 
       else {
            while(1) {
                dwResult = GetSurface()->Blt(&rcDestin, surSource->GetSurface(), &rcSource, 0, NULL);
                if(dwResult != DDERR_WASSTILLDRAWING) {
                    break;
                }
            }
        }
        if(DD_OK != dwResult) {
            DXERR("Cannot blit solid rect due", dwResult);
        }
    }
    return dwResult;
}

Na funkci není nic složitého. Rozlišujeme zda-li je použit CK či nikoliv. Všimněte si také použití funkce GetSurface(). Vykreslování zkoušíme tak dlouho dokud Blt() nevrací něco jiného než DDERR_WASSTILLDRAWING. Tento kód funkce vrací tehdy, když ještě nejsou dokončeny předchozí kreslící operace. Vše ostatní známe z minulých lekcí.

Druhá verze je ještě jednodušší:

HRESULT CSurface::Blt(CRect rcDestin, COLORREF Color)
{
    HRESULT dwResult = 1; // 1 means: NOT INIT
    DDBLTFX fx;
   
//
    // Check if the destination surface is valid

    if(IsValid()) {
       
//
        // Fill DDBLTFX structure valid date

        fx.dwSize = sizeof(fx);
        fx.dwFillColor = Color;
       
//
        // Blitting...

        while(1) {
            dwResult = GetSurface()->Blt(&rcDestin, NULL, NULL, DDBLT_COLORFILL, &fx);
            if(dwResult != DDERR_WASSTILLDRAWING) {
                break;
            }
        }
        if(DD_OK != dwResult) {
            DXERR("Cannot blit solid rect due", dwResult);
        }
    }
    return dwResult;
}

Nepotřebujeme zde žádný zdrojový povrch ani obdélník. Naplníme strukturu DDBLTFX tak, aby funkce Blt() vyplnila oblast určenou cílovým obdélníkem rcDestin spojitou barvou Color.

12.2. Závěr

Dnešní lekce byla trochu delší, ale doufám, že jste všechno pochopili. Stihli jsme implementovat pouze třídu CSurface, ale v příkladu, který přikládám naleznete kompletní knihovnu Display.dll. Zbytek doděláme v příští lekci (aspoň doufám). Příklad si stáhněte ze sekce Downloads. Navíc v adresáři Release najdete program Storage3, který použijete při vytváření datových souborů. Zkuste si otevřít datový soubor použitý v projektu data.dat a uvidíte, jak program funguje.

Pokud by Vám přece jen něco uniklo, stačí napsat a já se k tomu vrátím.

Těším se příště nashledanou.

Jiří Formánek