Iczelion - 25 - Einfache Bitmaps

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.
  1. 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 ...........


  1. 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.
  2. 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.
  1. 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
  1. 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.
  1. 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