Radek Chalupa   konzultace a školení programování, vývoj software na zakázku

Bitmapa a její vykreslení.

19.1.2002

V tomto pokračování se seznámíme s dalším objektem GDI - bitmapou. Ukážeme si jak vykreslit bitmapu uloženou v prostředcích programu do okna. V té souvislosti si ukážeme použití štětců tvořených vzorkem z bitmapy.

Na obrázku vidíte konečný výsledek dnešního výkladu: jde o okno, jehož štětec pozadí je definován vzorkem z bitmapy a v handleru zprávy WM_PAINT je uprostřed okna vykreslena bitmapa (fotografie v true-color barevné hloubce).

win-api-19

Co je to bitmapa ?

Z pohledu programátora je bitmapa grafický objekt podobný jako nám již známá pera nebo štětce. Znamená to že musíme "nějakým způsobem" získat handle bitmapy (ať už již existující nebo námi vytvořené), poté tento handle vybrat do kontextu zařízení, s kterým pak můžeme "pracovat". Jednotlivé pixely tohoto kontextu zařízení (obsahujícího bitmapu) pak představují barevné body tvořící bitmapu. Barva každého pixelu je určena třemi barevnými složkami - červená, zelená a modrá - každá z nich může mít definovanou intenzitu v rozsahu 0-255 (1 BYTE).

Nejjednodušším způsobem získání handle bitmapy je její načtení z prostředků (resources) programu funkcí LoadBitmap:

HBITMAP LoadBitmap(
  HINSTANCE hInstance,  // handle modulu
  LPCTSTR lpBitmapName  // jméno zdroje - bitmap
);

Tato funkce nám vrátí handle bitmapy. Často chceme získat nějaké informace o takto načtené bitmapě. Obvykle nás zajímají především její rozměry (z důvodu jejího umístění při kreslení). Protože bitmapa je grafický objekt, použijeme již známou funkci GetObject:

int GetObject(
  HGDIOBJ hgdiobj,  // handle grafického objektu
  int cbBuffer,     // velikost bufferu
  LPVOID lpvObject  // buffer pro informace o objektu
);

V případě bitmapy je bufferem pro informaci o objektu struktura BITMAP:

typedef struct tagBITMAP {
  LONG   bmType; 
  LONG   bmWidth; 
  LONG   bmHeight; 
  LONG   bmWidthBytes; 
  WORD   bmPlanes; 
  WORD   bmBitsPixel; 
  LPVOID bmBits; 
} BITMAP, *PBITMAP; 

Většinou chceme bitmapu vykreslit do kontextu zařízení okna. Musíme proto nejprve vytvořit vlastní paměťový kontext zařízení, do něj vybrat handle bitmapy a poté můžeme použít tu nejjednodušší funkci pro kopírování (případně další bitové operace) kontextů zařízení - BitBlt:

BOOL BitBlt(
  HDC hdcDest, // handle cílového DC
  int nXDest,  // x-souřadnice horního levého rohu
  int nYDest,  // y-souřadnice horního levého rohu
  int nWidth,  // šířka cílového obdélníka
  int nHeight, // výška cílového obdélníka
  HDC hdcSrc,  // handle zdrojového DC
  int nXSrc,   // x-souřadnice levého horního rohu
  int nYSrc,   // y-souřadnice
  DWORD dwRop  // typ rastrové operace
);

V tom nejjednodušším případě kopírování bitmapy do kontextu zařízení okna je cílovým hdcDest kontext okna a hdcSrc kontext bitmapy. 2 - 5 parametr definují obdélník v cílovém kontextu zařízení, do kteréhoje bitmapa kreslena a parametry nXSrc a nYSrc definují levý horní roh v bitmapě, od kterrého se začíná kreslit. Pokud je různý od souřadnice (0,0) jde o výřez z bitmapy.

Pokud tedy chceme vykreslit celou bitmapu, musíme nejprve zjistit její rozměry (funkcí GetObject), které pak uvedeme jako parametry nXDest a nYDest. Pro běžné kopírování je pak typ rastrové operace definován hodnotou SRCCOPY. O dalších možnostech si řekneme někdy příště, nyní si ukažme výpis kódu, který (v handleru zprávy WM_PAINT) realizuje to co vidíme na úvodním screen-shotu. Nejprve k pozadí okna. Již víme že každá třída okna má definovaný štětec, tvořící výplň jeho pozadí. Kromě již probíraných typů štětců existuje ještě štětec tvořený vzorkem bitmapy, který získáme funkcí CreatePatterBrush:

HBRUSH CreatePatternBrush(
  HBITMAP hbmp   // handle bitmapy
);

Jediné co potřebujeme, je opět handle bitmapy. V projektu jsou přidané do zdrojů 2 bitmapy (IDB_BACKGROUD a IDB_BITMAP1). Pozadí tedy definujeme při registraci třídy okna takto:

// .... kód registrace třídy okna
wcex.hbrBackground  = 
  CreatePatternBrush(LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BACKGROUND)))

Nyní již zbývá jen handler zprávy WM_PAINT:

void OnPaint(HDC hdc)
{
  BITMAP bitmap;
  RECT rect;
  GetClientRect(hwndMain, &rect);
  HDC hdcBitmap = CreateCompatibleDC(hdc);
  HBITMAP hBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));
  GetObject(hBitmap, sizeof(bitmap), &bitmap);
  SelectObject(hdcBitmap, hBitmap);
  int xPos = (rect.right - bitmap.bmWidth) / 2;
  int yPos = (rect.bottom - bitmap.bmHeight) / 2;
  BitBlt(hdc,
    xPos, yPos,
    bitmap.bmWidth,
    bitmap.bmHeight,
    hdcBitmap, 0,0, SRCCOPY);
  DeleteObject(hBitmap);
  DeleteDC(hdcBitmap);
}

Jak je vidět, po použití musíme opět bitmapu zrušit funkcí DeleteObject, stejně jako jiný grafický object. Pro vytvoření kontextu zařízení bitmapy jsem použit tu nejjednodušší funkci CreateComaptibleDC:

HDC CreateCompatibleDC(
  HDC hdc   // handle to DC
);

která nám vytvoří kontext zařízení se stejnými vlastnostmi jako zadaný existující kontext zařízení, v našem případě kontext okna.

Doprovodný projekt je ke stažení zde: win_api_19.zip