Tutorial 25: Einfache Bitmaps
In diesem Tutorial werden wir lernen, wie wir Bitmaps in unserem Programm benutzen können. Um genau zu sein, wir werden lernen wie man ein Bitmap in der Client-Area unseres Fensters anzeigt. Laden Sie das Beispiel herunter.
Theorie
Bitmaps können als Bilder, die auf dem Computer gespeichert werden, betrachtet werden. Es gibt viele Bild-Formate die auf dem Computer benutzt werden, doch Windows unterstützt Standardmäßig nur Windows Bitmap Grafik Dateien (.bmp). Die Bitmaps, auf die ich mich in diesem Tutorial beziehe, sind Windows Bitmap Grafik Dateien. Der einfachste Weg ein Bitmap zu benutzen, ist die Benutzung einer Ressource Definition Datei (.rc) wie folgt:
#define IDB_MYBITMAP 100
IDB_MYBITMAP BITMAP "c:\project\beispiel.bmp"
Diese Methode benutzt eine Konstante um das Bitmap zu repräsentieren. Die erste Zeile erzeugt lediglich eine Konstante namens IDB_MYBITMAP welche den Wert 100 hat. Wir werden dieses Label benutzen, um das Bitmap in unserem Programm zu referenzieren. Die nächste Zeile deklariert eine Bitmap Ressource. Sie teilt dem Ressource-Compiler mit, wo die tatsächliche Bitmap-Datei zu finden ist.
Die andere Methode benutzt einen Namen um das Bitmap zu repräsentieren:
MyBitMap BITMAP "c:\project\beispiel.bmp"
Diese Methode setzt vorraus, dass Sie das Bitmap in Ihrem Programm mit dem String "MyBitMap" ansprechen, statt mit einem Wert.
Beide Methode funktionieren einwandfrei, so lange Sie wissen, welche Mehtode Sie benutzen.
Nun da wir das Bitmap in eine Ressource-Datei gesteckt haben, können wir mit den Schritten fortfahren, es in der Client Area unseres Fensters anzuzeigen.
-
Rufen Sie LoadBitmap auf, um das Handle des Bitmaps zu erhalten. LoadBitmap hat folgende Definition:
LoadBitmap proto hInstance:HINSTANCE,
lpBitmapName:LPSTR
Diese Funktion gibt ein Bitmap-Handle zurück. hInstance ist das Instanz-Handle unseres Programms. lpBitmapName ist ein Zeiger auf den String, der den Namen des Bitmaps enthält (wenn Sie die zweite Methode verwenden). Wenn Sie eine Konstante benutzen, um das Bitmap zu referenzieren (wie IDB_MYBITMAP), können Sie hier den Wert angeben. (In dem obigen Beispiel würde das 100 sein). Daraus folgt kurzes Beispiel:
Erste Methode:
.386
.model flat, stdcall
................
.const
IDB_MYBITMAP equ 100
...............
.data?
hInstance dd ?
..............
.code
.............
invoke GetModuleHandle,NULL
mov hInstance,eax
............
invoke LoadBitmap,hInstance,IDB_MYBITMAP
...........
Zweite Methode:
.386
.model flat, stdcall
................
.data
BitmapName db "MyBitMap",0
...............
.data?
hInstance dd ?
..............
.code
.............
invoke GetModuleHandle,NULL
mov hInstance,eax
............
invoke LoadBitmap,hInstance,addr BitmapName
...........
-
Ermitteln Sie ein Handle für den Geräte-Kontext (Device-Context=DC). Sie können dieses Handle ermitteln, indem Sie BeginPaint als Antwort auf die WM_PAINT-Nachricht aufrufen oder GetDC irgendwo anders.
-
Erzeugen Sie ein Speicher Device Kontext mit den selben Attributen wie den Device-Kontext, den wir gerade erhalten haben. Die Idee dahinter ist, eine Art "versteckte" Mal-Oberfläche zu erzeugen, wo wir das Bitmap drauf malen können. Wenn wir mit dieser Operation fertig sind, kopieren wir einfach den Inhalt der versteckten Mal-Oberfläche auf den akteullen Device-Kontext mit einem Funktions-Aufruf. Das ist ein Beispiel für die Double-Buffering-Technik, die bei schnellem Anzeigen von Bildern benutzt wird. Sie können diese "versteckte" Oberfläche erzeugen, indem Sie CreateCompatibleDC aufrufen.
CreateCompatibleDC proto hdc:HDC
Wenn die Funktion erfolgreich war, wird das Handle des Speicher-Device-Kontext in EAX zurückgeliefert. hdc ist das Handle des Device Kontext, mit dem der Speicher-Device-Kontext kompatibel sein soll.
-
Nun, da Sie eine versteckte Mal-Oberfläche haben, können Sie auf sie zeichen, indem Sie das Bitmap auf sie auswählen. Das wird mit einem Aufruf von SelectObject mit dem Handle des Speicher DC als ersten Parameter und das Bitmap-Handle als zweiter Parameter gemacht. SelectObject hat folgende Definition:
SelectObject
proto hdc:HDC, hGdiObject:DWORD
-
Das Bitmap wird nun auf der Speicher-Device-Kontext gemalt. Alles was wir hier noch machen müssen, ist ein Kopie davon auf den aktuellen Device-Kontext. Es gibt verschiedene Funktionen, die das machen können, so wie BitBlt und StrechBlt. BitBlt kopiert nur den Inhalt eines DC auf einen anderen, demnach ist sie ziemlich schnell, während StretchBlt das Bitmap strecken oder komprimieren kann, damit es auf die Ausgabe-Fläche passt. Wir werden der Einfachheit halber BitBlt benutzen. BitBlt hat folgende Definition:
BitBlt proto
hdcDest:DWORD, nxDest:DWORD, nyDest:DWORD, nWidth:DWORD, nHeight:DWORD,
hdcSrc:DWORD, nxSrc:DWORD, nySrc:DWORD, dwROP:DWORD
hdcDest
ist das Handle des Device Kontextes, dass als Ziel der Bitmap-Transfer-Operation dient.
nxDest,
nyDest sind die Koordinaten der oberen linken Ecke der Ausgabe-Fläche.
nWidth,
nHeight sind die Breite und Höhe der Ausgabe-Fläche.
hdcSrc ist das Handle des Device Kontextes, der als Quelle der Bitmap-Transfer-Operation dient.
nxSrc, nySrc sind die Koordinaten der oberen linken Ecke des Quell-Rechtecks.
dwROP ist der Raster-Operations Code (daher das Akronym ROP), der es regelt, wie die Farbdaten des Bitmaps mit den bereits vorhandenen Farben auf der Ausgabe-Fläche kombiniert werden soll, um das finale Ergebniss zu erzielen. In den meisten Fällen möchten Sie die bestehenden Farben mit den neuen überschreiben.
-
Wenn Sie mit dem Bitmap fertig sind, löschen Sie es mit DeleteObject.
Das ist alles! Nochmal kurz zusammgefasst, Sie müssen das Bitmap in eine Ressource-Skript tun. Dann laden Sie aus der Ressource mit LoadBitmap. Sie erhalten das Bitmap-Handle. Als nächstes ermitteln Sie das Handle des Device-Kontext der Fläche, auf der Sie das Bitmap zeichnen wollen. Dann erzeugen Sie ein Speicher-Device-Kontext, der kompatibel mit dem Device-Kontext ist, den Sie erhalten haben. Wählen das Bitmap im Speicher DC aus und kopieren dann den Inhalt des Specher DC auf den richtigen DC.
Beispiel Code:
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
IDB_MAIN equ 1
.data
ClassName db "SimpleWin32ASMBitmapClass",0
AppName db "Win32ASM einfaches Bitmap Beispiel",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hBitmap dd ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.break .if (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL ps:PAINTSTRUCT
LOCAL hdc:HDC
LOCAL hMemDC:HDC
LOCAL rect:RECT
.if uMsg==WM_CREATE
invoke LoadBitmap,hInstance,IDB_MAIN
mov hBitmap,eax
.elseif uMsg==WM_PAINT
invoke BeginPaint,hWnd,addr ps
mov hdc,eax
invoke CreateCompatibleDC,hdc
mov hMemDC,eax
invoke SelectObject,hMemDC,hBitmap
invoke GetClientRect,hWnd,addr rect
invoke BitBlt,hdc,0,0,rect.right,rect.bottom,hMemDC,0,0,SRCCOPY
invoke DeleteDC,hMemDC
invoke EndPaint,hWnd,addr ps
.elseif uMsg==WM_DESTROY
invoke DeleteObject,hBitmap
invoke PostQuitMessage,NULL
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
;---------------------------------------------------------------------
; Das Ressource Skript
;---------------------------------------------------------------------
#define IDB_MAIN 1
IDB_MAIN BITMAP "tweety78.bmp"
Analyse:
Es gibt nicht allzuviel in diesem Tutorial zu analysieren ;)
#define IDB_MAIN 1
IDB_MAIN BITMAP "tweety78.bmp"
Definiert eine Konstante namens IDB_MAIN mit dem Wert 1. Und dann wird diese Konstante als Bitmap-Ressource-Identifizierer benutzt. Die Bitmap-Datei die in dem Ressource eingebunden wird, ist "tweety78.bmp" welche im gleichen Verzeichnis wie das Ressource-Skript liegt.
.if uMsg==WM_CREATE
invoke LoadBitmap,hInstance,IDB_MAIN
mov hBitmap,eax
Als Antwort auf WM_CREATE rufen wir LoadBitmap auf, um das Bitmap aus der Ressource zu laden, mit dem Bitmap-Ressource-Identifizierer als zweiten Parameter. Wir erhalten das Handle des Bitmaps, wenn die Funktion zurückgekehrt ist.
Nun, da das Bitmap geladen ist, können wir die Client Area unseres Hauptfensters zeichnen.
.elseif uMsg==WM_PAINT
invoke BeginPaint,hWnd,addr ps
mov hdc,eax
invoke CreateCompatibleDC,hdc
mov hMemDC,eax
invoke SelectObject,hMemDC,hBitmap
invoke GetClientRect,hWnd,addr rect
invoke BitBlt,hdc,0,0,rect.right,rect.bottom,hMemDC,0,0,SRCCOPY
invoke DeleteDC,hMemDC
invoke EndPaint,hWnd,addr ps
Wir zeichnen das Bitmap als Antwort auf die WM_PAINT Nachricht. Als erstes rufen wir BeginPaint auf, um das Handle des Device-Kontext zu bekommen. Dann erzeugen wir einen kompatiblen Speicher DC mittels CreateCompatibleDC. Als nächstes wählen wir das Bitmap im Speicher DC mittels SelectObject aus. Bestimmung der Dimension der Client Area mit GetClientRect. Nun können wir das Bitmap in der Client Area anzeigen, in dem wir BitBlt aufrufen, was das Bitmap aus dem Speicher DC in den richtigen DC kopiert. Wenn das Zeichnen beendet ist, brauchen wir den Speicher DC nicht mehr, weshalb wir ihn mit DeleteDC löschen. Die Zeichen-Session wird mit EndPaint beendet.
.elseif uMsg==WM_DESTROY
invoke DeleteObject,hBitmap
invoke PostQuitMessage,NULL
Wenn wir das Bitmap nicht mehr brauchen, löschen wir es mit DeleteObject.
Deutsche Übersetzung: Joachim Rohde
Die original Win32Asm-Tutorials stammen von Iczelion's Win32 Assembly HomePage