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