játékfejlesztés.hu
FórumGarázsprojectekCikkekSegédletekJf.hu versenyekKapcsolatokEgyebek
Legaktívabb fórumozók:
Asylum:    5448
FZoli:    4892
Kuz:    4455
gaborlabor:    4449
kicsy:    4304
TPG:    3402
monostoria:    3284
DMG:    3172
HomeGnome:    2919
Matzi:    2520

Pretender:    2498
szeki:    2440
Seeting:    2306
Geri:    2188
Orphy:    1893
Joga:    1791
Bacce:    1783
MaNiAc:    1735
ddbwo:    1625
syam:    1491
(VB) BitBlt használata Visual Basicben 2005.05.16 07:07



BitBlt használata Visual Basicben


Nem túl sokatmondó egy cím, de aki játékfejlesztéssel akar foglalkozni a VB-ben, annak ismernie kell a BitBlt API eljárást. Megkerülhetetlen része a grafikának - a DirectX-hez is szükséges. A segédlet végére egy univerzális rajzoló eljárás lesz a kezünkben, amivel rengetek grafikai feladatot megoldhatunk, és gyors játékot írhatunk - még ha nem is használunk hozzá DirectX-et.


A VB-ben az a jó, hogy egyszerû megoldásokat ad a programozó kezébe. Ha grafikáról van szó, két vezérlõt is kapunk: az image és a picture vezérlõt. Az image csak képek megjelenítésére jó, a picture még szerkesztési metódusokat is ad. Ezekre nem térek ki, de elmondhatjuk róluk, hogy lassúak. Ráadásul senkinek sincs kedve 800 picture vezérlõt kirakni azért, mert annyi grafika megy a játékhoz. Kell tehát egy gyors rajzoló eljárás, ami megoldja a problémáinkat. Ez lesz a Bitblt. Viszont ilyen parancsot nem ismer a VB. A VB-nél mélybbre, a Windows API-ba kell nyúlnunk érte. A Windows API (Application Programming Interface) egy óriási függvénygyûjtemény, amit a windows minden futó program számára biztosít. Ennek az egyik része a GDI32.dll, aminek az egyik függvénye/eljárása a bitblt. Az API parancsokat nem kapjuk készen - elõször deklarálni kell õket, hogy használni tudjuk. Vágjunk bele!

!!!!! FONTOS !!!!!
Az itt leírtak Windows 98-on és Visual Basic 6-ban lettek tesztelve. Más verziókon más eredményt vagy hibát kaphatunk, elképzelhetõ, hogy módosítani kell a kódot.

Új projekt. Van egy üres formunk egy PictureBox vezérlõvel (picture1). Adjunk a formhoz egy modult, hogy újrafelhasználható legyen késõbb a kód. Ctrl-C - Ctrl-V, írjuk be a modulba:

Public Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long

Szép hosszú, mi? Ezt mind fejbõl kellene kitalálni, ha nem járna a VB-hez egy API text Viewer címû programocska, amivel ki tudjuk másolgatni a deklarációkat. Na de mivel meg hogyan rajzol ez a Bitblt? Ehhez elõször tudni kell mi a DC.

A DC itt nem Direct Connect-et, hanem Device Context-et jelent. A DC objektumot képzeljük el egy memóriatartománynak, ami képet tárol. Ez lehet maga az egész képernyõ, lehet 1-1 vezérlõnek a képe, akármi. A BitBlt jelentése: Bitblock Transfer. Bitképet másol egyik DC-rõl a másikra. Mi megadjuk honnan, mit, hova, milyen módon, a Bitblt meg másol. Több vezérlõnek (ilyen a form, a picturebox) létezik egy hDC tulajdonsága, ami egy handle-t ad a vezérlõ DC-jéhez. Ezzel a handle-val hivatkozunk a DC-re. Így nem az egész x kilobájtos képet pakolgatjuk egyik változóból a másikba, hanem csak egy 4 bájtos "kulcsot" hozzá. Mivel egy handle mindig ugyanarra a DC-re hivatkozik, ezért az egyszerûség és a bitblt kedvéért csak DC-t írok. Pl. A form1.hdc egy számot ad vissza. Ezzel hivatkozunk a Form DC-jére.

Nem kell kétségbe esni, nem vészes annyira ez. Dark Basices példával élve a DC maga a betöltött image, a handle a sorszáma, a Bitblt meg a Paste image parancs.

Eddig tartott az elmélet. Nézzük a BitBlt-hez mi kell:
A parancs alakja így néz ki:

BitBlt hDestDC , x , y , nWidth , hHeight , hSrcDC , xSrc , ySrc , dwRop

hDestDC: A cél DC-je. Erre másolunk.
x,y: Melyik koordinátánál lesz a beillesztett kép bal felsõ sarka
nWidth, hHeight: A másolandó kép mérete
hSrcDC: Errõl másolunk. (Forrás DC)
xSrc, ySrc: Melyik koordinátánál lesz a kivágott kép bal felsõ sarka
dwRop: Raster Operation.



Legtöbbször a cél DC nem üres, van valami kép rajta. Mit csináljon vele a Bitblt? Kombinálja? Felülírja? Ezt adhatjuk meg a Raster Operationnel. A dwRop egy megjegyezhetetlen szám, de VB konstansokkal kiválthatjuk. Nyomjunk F2-t, elõjön az Object Browser. Baloldalt válasszuk ki a RasterOpConstants menüpontot. Itt találunk egy listát arról, mit használhatunk. Na vágjunk bele. A formunkon van (még mindig) egy picture vezérlõ. Tegyünk bele egy képet. Állítsuk a picture és a form autoredraw tulajdonságát true-ra, különben nem látunk semmit (vagy ha csak a formét állítjuk át, a form mögötti képet másoljuk).




Private Sub Form_Load()
BitBlt Form1.hDC, 0, 0, 50, 50, Picture1.hDC, 0, 0, vbSrcCopy
End Sub

A sor jelentése: a Form1 DC-jére a 0,0-s koordinátákra egy 50*50-es képet másolunk (vbsrccopy) a picture1 vezérlõ DC-jébõl a 0,0 koordinátáktól kezdve. Akárhányszor megtehetjük:

Private Sub Form_Load()
For i = 0 To 2
BitBlt Form1.hDC, 0, 0 + i * 50, 50, 50, Picture1.hDC, 0, 0, vbSrcCopy
Next
End Sub

Ha kiéltük kísérletezõ kedvünket, haladjunk tovább. Képet másolni egyik vezérlõrõl a másikra nem nagy dolog. Viszont szegény VB-s vezérlõknek nagy baja, hogy nem támogatják az átlátszóságot. Nem lehet megadni, hogy ahol mondjuk fekete a kép ott átlátszó lesz. De itt a Bitblt, ami nem csak gyors, hanem ezt is tudja.

Itt egy kicsit bele kéne merülni a bitszintû mûveletekbe, de ezt most nem tesszük meg. A dwRop-pal mindenféle hatást el lehet érni, többször blitteljük a ugyanazt a képet a formra más-más dwRop paraméterrel és szépen változnak a színek... Van errõl egy jó példaprogram, link az oldal alján.

Az átlátszóságot maszkolással érhetjül el. Két képet használunk: az egyik a normális, a másik a maszk, ami azt mondja meg, mi látható.
Tegyük fel, azt akarjuk, hogy a kép fekete részei ne látszódjanak. A maszk képen színezzük fehérre a normális kép fekete részeit, a nem fekete részeket meg feketére. A két képünket tegyük két pictureboxba. Az egyik neve legyen kep, a másiké maszk. Ellenõrizzük le az autoredraw tulajdonságokat, true-e mind. A kód a következõképpen módosul:

Private Sub Form_Load()
For i = 0 To 2
BitBlt Form1.hDC, 0, 0 + i * 50, 50, 50, maszk.hDC, 0, 0, vbSrcAnd
BitBlt Form1.hDC, 0, 0 + i * 50, 50, 50, kep.hDC, 0, 0, vbSrcPaint
Next
End Sub




A kép és a maszk egymásra másolásával átlátszó képet sikerült alkotni. Szép BitBlt-s grafikánk gyors, de még így is vannak problémák vele, például az, hogy pictureboxokat használ. Meg az, hogy kézzel kell rajzolgatni a maszkot. Van erre is megoldás: a memóriában létre lehet hozni vezérlõtõl független DC-ket, amikben képet tárolhatunk. Eddig könnyû dolgunk volt, csak egy API függvényt kellett meghívni, ezentúl viszont kapunk egy adagot a nyakunkba - jó példája ez annak, hogyan lehet egyszerû feladatokat túlbonyolítani. A sima Bitblt használata még könnyen megérthetõ, de szerintem nincs az az ember, aki egymaga rájön egy API leírásból a következõ dolgok mikéntjére. Nekem 3 angol példaprogram összekombinálásából sikerült "kifejleszteni" a segédlet végén található eljárást, szóval innentõl nem fejbõl alkotok

Na akkor összehozunk egy eljárást, ami betölt egy képet a háttérben. Elõször nézzük az API függvényeket!

Public Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long
Public Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
Public Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hdc As Long) As Long
Public Declare Function DeleteDC Lib "gdi32" (ByVal hdc As Long) As Long
Public Declare Function LoadImage Lib "user32" Alias "LoadImageA" (ByVal hInst As Long, ByVal lpsz As String, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As Long
Public Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
'Ezekre a konstansokra a LoadImage-nek van szüksége
Const IMAGE_BITMAP As Long = 0
Const LR_LOADFROMFILE As Long = &H10
Const LR_CREATEDIBSECTION As Long = &H2000

Maga az eljárás itt van. Használata egyszerû: megadunk egy képfájlt, az eljárás betölti, mi visszakapjuk a kép DC-jét. A kapott DC-t ugyanúgy használhatjuk, mint akármelyik vezérlõ hDC tulajdonságát.

Public Function GenerateDC(FileName As String) As Long
Dim DC As Long: Dim hBitmap As Long
'Készítünk egy új, üres DC-t
DC = CreateCompatibleDC(0)
'Ha nem sikerült...
If DC < 1 Then GenerateDC = 0: Exit Function
'Betöltjük a képet
hBitmap = LoadImage(0, FileName, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE Or LR_CREATEDIBSECTION)
'Ha nem sikerült... töröljük a dc-t és kilépünk
If hBitmap = 0 Then DeleteDC DC: GenerateDC = 0: Exit Function
'Belerakjuk a képet a DC-be
SelectObject DC, hBitmap
'A bitmapra nincs többé szükségünk, töröljük
DeleteObject hBitmap
'A függvény visszatérési értéke a DC lesz
GenerateDC = DC
End Function

Mostmár annyi grafikát használhatunk BitBlt-vel, amennyit akarunk. De még mindig azt kell mondanunk, hogy ez félmegoldás. Még mindig nekünk kell kézzel rajzolni a maszkot, ha átlátszó képet szeretnénk. Rajzolja a számítógép helyettünk... Ehhez megint felhasználunk egy csomó API rutint!

A következõ eljárás a végleges képbetöltõ eljárás lesz. Mivel szegény eljárásnak két értéket kell visszaadni, mi meg szeretjük az egyszerû megoldásokat, készítünk egy külön típust, a hangzatos DCT névvel. Berakjuk a modulunkba az API deklarációk elé, hadd lássa az egész program! Szükség lesz még egy BITMAP típusra, ezt készen kapjuk az API Text Viewerbõl.

Public Type dct
kepdc As Long
maskdc As Long
End Type
Public Type BITMAP '14 bytes
bmType As Long
bmWidth As Long
bmHeight As Long
bmWidthBytes As Long
bmPlanes As Integer
bmBitsPixel As Integer
bmBits As Long
End Type
Public Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long
Public Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
Public Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hdc As Long) As Long
Public Declare Function DeleteDC Lib "gdi32" (ByVal hdc As Long) As Long
Public Declare Function LoadImage Lib "user32" Alias "LoadImageA" (ByVal hInst As Long, ByVal lpsz As String, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As Long
Public Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
Public Declare Function CreateBitmap Lib "gdi32" (ByVal nWidth As Long, ByVal nHeight As Long, ByVal nPlanes As Long, ByVal nBitCount As Long, lpBits As Any) As Long
Public Declare Function SetBkColor Lib "gdi32" (ByVal hdc As Long, ByVal crColor As Long) As Long
Public Declare Function GetObjectAPI Lib "gdi32" Alias "GetObjectA" (ByVal hObject As Long, ByVal nCount As Long, lpObject As Any) As Long
'Ezekre a konstansokra a LoadImage-nek van szüksége
Const IMAGE_BITMAP As Long = 0
Const LR_LOADFROMFILE As Long = &H10
Const LR_CREATEDIBSECTION As Long = &H2000

'Jelezzük, hogy DCT típusú változót kapunk vissza
Public Function GenerateDC(FileName As String) As dct
'A hBitmap csak egy handle a bitmaphoz, a bitmap maga a BMP lesz
Dim dc As Long : Dim hBitmap As Long : Dim bmp As BITMAP
'Üres DC-t készítünk
DC = CreateCompatibleDC(0)
'Ha nem sikerült...
If DC < 1 Then GenerateDC = 0: Exit Function
'Betöltjük a képet - WinNT alatt nem mûködik állítólag
hBitmap = LoadImage(0, FileName, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE Or LR_CREATEDIBSECTION)
'Ha nem sikerült a betöltés...
If hBitmap = 0 Then
DeleteDC dc
GenerateDC.kepdc = 0
MsgBox "Bitkép nem tölthetõ be: " + FileName, vbOKOnly, "Hiba"
Exit Function
End If

'Belerakjuk a képet a DC-be
SelectObject dc, hBitmap

'Eddig érthetõ, a DC változó tartalmazza a betöltött képet. De itt jön a csavar!
'Maszkot úgy készítünk, hogy fekete-fehér (1 bites) bitképet csinálunk az eredeti képbõl, majd rámásoljuk az eredeti képre.
'Ehhez BitBlt kell, amihez ismerni kell a kép méretét. A kép méretét úgy tudjuk meg, hogy a már betöltött képet
'betöltjük a bmp változóba (ami BITMAP típusú!) A bmp meg megadja a méretet.

'Elõször csinálunk egy új, üres DC-t a maszknak:
mdc = CreateCompatibleDC(dc)
'A bitképet a bmp változóba töltjük
GetObjectAPI hBitmap, Len(bmp), bmp
'A bmp változónak van bmWidth és bmHeight eleme. CreateBitmappal egy ugyanekkora, üres fekete-fehér bitmapot készítünk
mbmp = CreateBitmap(bmp.bmWidth, bmp.bmHeight, 1, 1, 0&)
'Belerakjuk a képet a DC-be
SelectObject mdc, mbmp
'Feketére állítjuk a háttérszínt, mert fehér háttéren nem mûködik
SetBkColor dc, QBColor(0)
'Az eredeti kép DC-jét belemásoljuk a mask DC-jébe. A fekete háttér és a kétszínû mask bitmap miatt tökéletes maszkot kapunk.
BitBlt mdc, 0, 0, bmp.bmWidth, bmp.bmHeight, dc, 0, 0, vbSrcCopy

'A bitmapokra nincs többé szükségünk:
DeleteObject hBitmap
DeleteObject mbmp
'Visszatérési értékként megadjuk a DC-ket. A betöltés sikerült.
GenerateDC.kepdc = dc
GenerateDC.maskdc = mdc
End Function

Ezzel készen is van a gyönyörûszép képbetöltõ eljárás. A használata nagyon egyszerû. Térjünk át formunkra, töröljünk le róla minden vezérlõt.
A betöltött képeket a DC-k handle-i jelentik. Az eljárásunk két handle-t ad vissza: a képét és a maszkét.

Elõször deklaráljuk a változót, amibe a kép dc-i kerülnek:

dim rajz as dct

Private Sub Form_Load()
'Betöltjük a képet
rajz = GenerateDC("rajz.bmp")
'A már ismertetett módszerrel kirajzoljuk a képet
BitBlt Form1.hdc, 0, 0, 50, 50, rajz.maskdc, 0, 0, vbSrcAnd
BitBlt Form1.hdc, 0, 0, 50, 50, rajz.kepdc, 0, 0, vbSrcPaint
End Sub

Egy sor betöltés, két sor kirajzolás... Nem bonyolult.
Két egymáson lévõ képnél a felsõ kép fekete része alatti terület sötétebb. Ezért rakjunk be a bitblt parancsok elé egy SetBkColor Me.hdc, QBColor(15) sort. Ha a rajzoló kód többször fut le, használjuk a Me.Cls metódust.

Röviden ennyi. Remélem érthetõ volt. Az se baj, ha nem, a leírt eljárást megértés nélkül is lehet használni - szerintem.

By(t)e,
VT

Értékelés: 8.00

Új hozzászólás
Bacce          2007.04.05 11:11
Nagyon tetszett a cikk, minden tisztelet.. azon a nagy halom sokmindenfélén nagyot röhögtem...
A Maszkolást próbáltam VB5-ben nem igazán ment, de legalább a fehéret kicserélte feketére.. vagy fordítva.. nemtom... majd még utánadúrok ennek a témának.. Nah, szóval köcce...
És még egy kis hozzáfűznivalóm lenne, ha valaki nem akar a játékába vagy akármilyen programjába csak 1-2 átlátszó dolgot akkor egyszerűbb megoldás lehet még ennél is és univerzálisabb egy User Controlt csinálni... Van a Vb5-ben, gondolom az utána következőkben is, meg lehet hogy előtte is egy olyan az add-in managerben hogy add-in toolbar, ahol játszi könnyedséggel lehet összehozni egy átlátszó User-Controlt aminek meg lehet adni egy Kép tulajdonságot, és egy Mask tulajdonságot, amit utána csak be kell pakolni az ablakba, mint picture-t vagy bármit, és megadni neki a képet, meg a maskot... a masknál nem árt figyelni arra hogy csak 2 bites lehet, vagyis csak 2 szín szerepelhet rajta...
meg egy kicsit unstabil lesz tölle a környezet tesztelés közben, nem árt gyakrabban menteni, meg persze így 1 objektummal több... egyértelműen a bitblt a jobb ilyen szempontból... csak ha nem akarsz belefolyni a programozásba túlzottan, és csak 1-2 objektumra van szükséged, ez picit talán egyszerűbb, csak persze korlátoltabb is.