16. Grafika (Bitmaps, Device Contexts and BitBlt)
2014.06.21. 14:06
Példa: bmp_one
GDI
A windóz valódi nagy dobása a DOS-hoz képest, hogy nem kell ismernünk a hardvert ami a grafikáért felelős. Ehelyett egy grafikus eszköz illesztő felületet (Graphics Device Interface - GDI) biztosít számunkra. A GDI olyan általános grafikai készletet használ, amivel rajzolhatunk a képernyőre, a memóriába, vagy akár a nyomtatóra.
Eszköz környezetek
Van egy úgynevezett eszköz környezet (device context - DC), amit a HDC (Handle of Device Context) adattípus képvisel számunkra. Ez egy olyan kezelő, ami a képernyőt szimbolizálja, vagy az ablakot, a memóriát, egy képet, vagy éppen a nyomtatót. A szép az egészben az, hogy nem is kell érdekeljen hogy ténylegesen melyik, hiszen ugyanúgy kell kezelni mindet.
Ha például az ablakra akarunk rajzolni, akkor fogjuk a HDC-t amit a GetDC() szolgáltat (ez képviseli az ablakot), és ezzel a HDC-vel használhatjuk a GDI bármelyik funkcióját. Kitehetünk képet ( BitBlt() ), írhatunk szöveget ( TextOut() ), húzhatunk vonalat ( LineTo() ) ésatöbbiésatöbbi.
Bitképek
A bitképeket ugyanúgy tölthetjük be mint a korábbi példákban az ikonokat, erre a LoadBitmap() szolgál. Ez egyszerűen betölti a bitkép erőforrást. Használhatjuk a LoadImage() hívást is, egy képfájlokhoz van (*.bmp).
Most jön a csavar!
A bitkép objektumokat (HBITMAP típus) nem tudjuk közvetlenül rajzoltatni! Ahhoz hogy a rajzoló funkciókat a bitképen használni tudjuk, az kell hogy létrehozzunk egy Memory DC-t, majd a SelectObject()-tel kiválasztni a HBITMAP-ot. TODO: Ezt most nem értem.
Ennek az lesz a hatása, hogy ha valamit ügyködünk a HDC eszközön, akkor az megjelenik a bitképen is.
GDI szivárgás
Van egy kis gond a HDC-kkel. A számuk korlátozott. Ez főképpen a Windows95 előtti verzióknál volt nagyon durva, mert a programunk megosztva használja a rendszerrel. Akár működésképtelenné is teheti azt! Szerencsére a Windows2000 és XP óta ez már nem játszik, mert ha gáz van, akkor felszabadítják maguknak amit kell. NT alapú rendszereknél már elméletileg nem tudjuk lenyúlni a rendszer erőforrásait. Kivéve ha hiba csúszik a gépezetbe. Ha egy ideig jól működik a programunk, aztán egyszercsak elkezd furcsaágokat művelni rajzolás közben, akkor ez utalhat arra, hogy GDI szivárgás esete áll fenn.
Itt kifejtjük hogy vissza kell hozni a már használt GDI erőforrást hogy aztán felszabadíthassuk, ellenkező esetben az újból és újból foglalás miatt kifogyhatunk a GDI erőforrásokból. Volt is gond ebből egy képernyővédőnél.
Bitképek megjelenítése
A rajzoló műveleteket a WM_PAINT végzi el. Amikor az ablakunk először jelenik meg, vagy lecsukott állapotból újra kinagyítjuk, akkor a WM_PAINT üzenet indul útjára, hogy az ablak tartalmát újra kell rajzolni. Amikor valamit kirajzoltunk, akkor az nem marad úgy! Minden alkalommal amikor kérés érkezik, sajnos újra kell rajzolni!
HBITMAP g_hbmBall = NULL;
case WM_CREATE: g_hbmBall = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_BALL)); if(g_hbmBall == NULL) MessageBox(hwnd, "Could not load IDB_BALL!", "Error", MB_OK | MB_ICONEXCLAMATION); break;
Első lépés természetesen a bitkép betöltése. Ez ugyanúgy megy mint erőforrás esetén, gyerünk tovább!
case WM_PAINT: { BITMAP bm; PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); HDC hdcMem = CreateCompatibleDC(hdc); HBITMAP hbmOld = SelectObject(hdcMem, g_hbmBall); GetObject(g_hbmBall, sizeof(bm), &bm); BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); SelectObject(hdcMem, hbmOld); DeleteDC(hdcMem); EndPaint(hwnd, &ps); } break;
Felvezetjük a használni kívánt változóinkat. Figyeljük meg, hogy az első típusa BITMAP, és nem HBITMAP! A BITMAP egy olyan struktúra, ami információt tartalmaz az aktuális GDI objektum HBITMAP-járól. Szükséges megállapítnunk a HBITMAP szélességét és magasságát, erre a GetObject()-et használjuk. Ez nem magát az objektumot adja vissza, csak infókat róla. A helyes név a GetObjectInfo() lett volna. Ezt kaptuk, használjuk. A GetObject() különböző GDI aobjektumokkal együtt tud működni, ami a második paraméter alapján képes a struktúra méretét megállapítani.
A PAINTSTRUCT egy olyan struktúra, ami az ablak festéséről tartalmaz információkat. Most egyszerűen nem foglalkozunk vele, de a BeginPaint(), és EndPaint() működéséhez szükséges lesz. A BeginPaint() ahogy a neve is jelzi, a WM_PAINT üzenetet kezeli. Ha nem kezeljük a WM_PAINT üzenetet, akkor a GetDC()-re lesz szükségünk, ahogyan azt az időzítős animációs példánál látni fogjuk. Addig viszont használjuk a BeginPaint(), és EndPaint() párost!
A BeginPaint() visszatér a HDC-vel, ami a HWND-t képviseli. Minden rajzolási művelet amit a HDC-vel végzünk, azonnal megjelenik a képernyőn.
A Memória DC bitképre állítása
Annak érdekében hogy rajzolhassunk a bitképre, szükséges egy DC-t létrehozni a memóriában. A könnyebb út a CreateCompatibleDC(). Kapunk egy Memory DC-t, ami átveszi a képernyő színmélységét és beállításait. Most meghívjuk a SelectObject()-et hogy kiválasszuk a bitképet, ezzel megkapjuk az alap képet, amit később kicserélhetünk. Ezzel elkerülhetjük a GDI szivárgást.
Rajzolás
Ahogy megvannak a bitkép méretei, kitöltjük a BITMAP struktúrát. Meghívjuk a BitBlt()-t, ezzel átmásoljuk a képet a memória DC-ből az ablak DC-jébe, amivel megjelenítjük a képernyőn. Mint mindig, ennek is utána nézhetünk az MSDN-en, de röviden itt is összefoglaljuk a lényeget: A cél, a pozíció, a méret, a forrás, a forrás pozíciója, és végül a "raszter művelet" (ROP code) határozzák meg, hogyan történjen a másolás. Most egyszerűen átmásoljuk és kész.
Takarítás
Ezen a ponton már kint lehet kép a képernyőn, ki kell takarítnunk magunk után. Először a Memory DC-t visszaállítjuk az eredeti állapotába, majd kicseréljük a bitmapunkat az egyik tárolt változatra. Ezután a DeleteDC()-vel végleg törölhetjük. Az EndPaint()-tel eldobjuk a Window DC-t, amit a BegintPaint() adott.
A HDC megsemmisítése kissé problémás, mert a létrehozásától függően három féleképpen is megtehetjük. Lássuk a párosokat:
GetDC() - ReleaseDC()
BeginPaint() - EndPaint()
CreateCompatibleDC() - DeleteDC()
Végezetül, amikor a programunk véget ér, felszabadítjuk a lefoglalt erőforrásokat. Elvileg ez nem feltétlenül szükséges, hiszen a modern windózok már megteszik ezt maguktól is. Elvileg. Gyakorlatilag azonban elképzelhető hogy vannak még hibák a GDI megvalósításában, szabadítsuk csak fel!
case WM_DESTROY: DeleteObject(g_hbmBall); PostQuitMessage(0); break;
A bejegyzés trackback címe:
Kommentek:
A hozzászólások a vonatkozó jogszabályok értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a Felhasználási feltételekben és az adatvédelmi tájékoztatóban.