Tutorial 25: Simple Bitmap

In this tutorial, we will learn how to use bitmap in our program. To be exact, we will learn how to display a bitmap in the client area of our window.

Theory

Bitmaps can be thought of as pictures stored in computer. There are many picture formats used with computers but Windows only natively supports Windows Bitmap Graphics files (.bmp). The bitmaps I'll refer to in this tutorial are Windows bitmap graphics files. The easiest way to use a bitmap is to use it as a resource. You can include the bitmap in the resources and declare the according ID in your source:
 
[IDB_MYBITMAP   1]
This method uses a constant to represent the bitmap. The first line just creates a constant named IDB_MYBITMAP which has the value of 1. We will use this symbol to refer to the bitmap in the program.

Now that we put the bitmap in the resource file, we can go on with the steps in displaying it in the client area of our window.

  1. call LoadBitmap to get the bitmap handle. LoadBitmap has the following definition:
      LoadBitmap proto hInstance:HINSTANCE, lpBitmapName:LPSTR


    This function returns a bitmap handle. hInstance is the instance handle of our program. lpBitmapName is a pointer to the string that is the name of the bitmap (incase you use the second method to refer to the bitmap). If you use a constant to refer to the bitmap (like IDB_MYBITMAP), you can put its value here. (In the example above it would be 100). A short example is in order:

       
       
  1. Obtain a handle to device context (DC). You can obtain this handle by calling BeginPaint in response to WM_PAINT message or by calling GetDC anywhere.
  2. Create a memory device context which has the same attribute as the device context we just obtained. The idea here is to create a kind of "hidden" drawing surface which we can draw the bitmap on. When we are finished with the operation, we just copy the content of the hidden drawing surface to the actual device context in one function call. It's an example of double-buffer technique used for fast display of pictures on the screen. You can create this "hidden" drawing surface by calling CreateCompatibleDC.
      CreateCompatibleDC  proto  hdc:HDC


    If this function succeeds, it returns the handle of the memory device context in eax. hdc is the handle to the device context that you want the memory DC to be compatible with.

  1. Now that you got a hidden drawing surface, you can draw on it by selecting the bitmap into it. This is done by calling SelectObject with the handle to the memory DC as the first parameter and the bitmap handle as the second parameter. SelectObject has the following definition:
      SelectObject   proto  hdc:HDC, hGdiObject:DWORD
  1. The bitmap is drawn on the memory device context now. All we need to do here is to copy it to the actual display device, namely the true device context. There are several functions that can perform this operation such as BitBlt and StretchBlt. BitBlt just copies the content of one DC to another so it's fast while StretchBlt can stretch or compress the bitmap to fit the output area. We will use BitBlt here for simplicity. BitBlt has the following definition:
      BitBlt  proto  hdcDest:DWORD, nxDest:DWORD, nyDest:DWORD, nWidth:DWORD, nHeight:DWORD, hdcSrc:DWORD, nxSrc:DWORD, nySrc:DWORD, dwROP:DWORD
    hdcDest is the handle of the device context that serves as the destination of bitmap transfer operation
    nxDest, nyDest are the coordinate of the upper left corner of the output area
    nWidth, nHeight are the width and height of the output area
    hdcSrc is the handle of the device context that serves as the source of bitmap transfer operation
    nxSrc, nySrc are the coordinate of the upper left corner of the source rectangle.
    dwROP is the raster-operation code (hence the acronym ROP) that governs how to combine the color data of the bitmap to the existing color data on the output area to achieve the final result. Most of the time, you only want to overwrite the existing color data with the new one.
  1. When you're done with the bitmap, delete it with DeleteObject API call.
That's it! To recapitulate, you need to put the bitmap into the resource scipt. Then load it from the resource with LoadBitmap. You'll get the bitmap handle. Next you obtain the handle to the device context of the area you want to paint the bitmap on. Then you create a memory device context that is compatible with the device context you just obtained. Select the bitmap into the memory DC then copy the content of the memory DC to the real DC.

Example Code:

________________________________________________________________________________________
; Equates:

[IDB_MAIN 30000] ; This is the equate given by the BitMap Loader.

________________________________________________________________________________________
; Data:

[hBitmap: 0  WindowHandle: 0  hDC: 0  hMemDC: 0
 ClassName: 'SimpleWin32ASMBitmapClass' 0
 AppName:   'Win32ASM Simple Bitmap Example' 0]

________________________________________________________________________________________

; Window Class Structure:

[WindowClassEx: wc_Size: len
                wc_style: &CS_HREDRAW+&CS_VREDRAW
                WndProc: MainWindowProc
                wc_ClsExtra: 0  wc_WndExtra: 0       hInstance: 0    wc_hIcon: 0
                wc_hCursor: 0   wc_hbrBackground: 6  wc_MenuName: 0  wc_ClassName: ClassName
                wc_hIconSm: 0]

[FirstMessage: 0 #7]

____________________________________________________________________________________________
____________________________________________________________________________________________

Main:
    call 'Kernel32.GetModuleHandleA' &NULL | mov D§hInstance eax
    call 'User32.LoadIconA'  0  &IDI_APPLICATION | mov D§wc_hIcon eax  D§wc_hIconSm eax
    call 'User32.LoadCursorA' 0  &IDC_ARROW  | mov D§wc_hCursor eax
    call 'User32.RegisterClassExA'  WindowClassEX
    call 'User32.CreateWindowExA' &NULL ClassName AppName,
                                 &WS_OVERLAPPEDWINDOW,
                                 &CW_USEDEFAULT &CW_USEDEFAULT &CW_USEDEFAULT &CW_USEDEFAULT,
                                 &NULL &NULL D§hInstance &NULL
      mov D§WindowHandle eax

    call 'User32.ShowWindow'  D§WindowHandle &SW_SHOWNORMAL
    call 'User32.UpdateWindow'  D§WindowHandle

L1: call 'User32.GetMessageA' FirstMessage 0 0 0 | cmp eax 0 | je L9>
        call 'User32.TranslateMessage'  FirstMessage
        call 'User32.DispatchMessageA'  FirstMessage
    jmp L1<

L9: call 'Kernel32.ExitProcess' 0
____________________________________________________________________________________________
____________________________________________________________________________________________

[PAINTSTRUCT: PS_hdc: 0  PS_fErase: 0  PS_rcPaint: 0 0 0 0  PS_fRestore: 0
              PS_fIncUpdate: 0  PS_rgbReserved: 0 0 0 0   0 0 0 0]

[RECT:  Rect_left: 0  Rect_top: 0  Rect_right: 0  Rect_bottom: 0]

____________________________________________________________________________________________
____________________________________________________________________________________________

Proc MainWindowProc:
    Arguments @Adressee, @Message, @wParam, @lParam

    pushad
 
        .IF D@Message = &WM_DESTROY
            call 'GDI32.DeleteObject' D§hBitmap
            call 'User32.PostQuitMessage' &NULL
 
        .Else_If D@Message = &WM_CREATE
            call 'User32.LoadBitmapA' D§hInstance IDB_MAIN
            mov D§hBitmap eax

        .ELSE_If D@Message = &WM_PAINT
            call 'User32.BeginPaint'  D@Adressee  PAINTSTRUCT
            mov D§hdc eax

            call 'GDI32.CreateCompatibleDC' D§hdc | mov D§hMemDC,eax
            call 'GDI32.SelectObject' D§hMemDC D§hBitmap
            call 'User32.GetClientRect' D@Adressee RECT
            call 'GDI32.BitBlt' D§hdc 0 0 D§Rect_Right D§Rect_bottom D§hMemDC 0 0 &SRCCOPY
            call 'GDI32.DeleteDC' D§hMemDC
            call 'User32.EndPaint' D@Adressee PAINTSTRUCT
 
        .Else
            popad
            call 'User32.DefWindowProcA' D@Adressee D@Message D@wParam D@lParam
            Exit
 
        .ENDIF
 
    popad | mov eax &FALSE
EndP
 

Analysis:

There is not much to analyze in this tutorial ;)
 
 

        .Else_If D@Message = &WM_CREATE
            call 'User32.LoadBitmapA' D§hInstance IDB_MAIN
            mov D§hBitmap eax

In response to WM_CREATE, we call LoadBitmap to load the bitmap from the resource, passing the bitmap's resource identifier as the second parameter to the API. We get the handle to the bitmap when the function returns.
Now that the bitmap is loaded, we can paint it in the client area of our main window.
 

        .ELSE_If D@Message = &WM_PAINT
            call 'User32.BeginPaint'  D@Adressee  PAINTSTRUCT
            mov D§hdc eax

            call 'GDI32.CreateCompatibleDC' D§hdc | mov D§hMemDC,eax
            call 'GDI32.SelectObject' D§hMemDC D§hBitmap
            call 'User32.GetClientRect' D@Adressee RECT
            call 'GDI32.BitBlt' D§hdc 0 0 D§Rect_Right D§Rect_bottom D§hMemDC 0 0 &SRCCOPY
            call 'GDI32.DeleteDC' D§hMemDC
            call 'User32.EndPaint' D@Adressee PAINTSTRUCT

We choose to paint the bitmap in response to WM_PAINT message. We first call BeginPaint to obtain the handle to the device context. Then we create a compatible memory DC with CreateCompatibleDC. Next select the bitmap into the memory DC with SelectObject. Determine the dimension of the client area with GetClientRect. Now we can display the bitmap in the client area by calling BitBlt which copies the bitmap from the memory DC to the real DC. When the painting is done, we have no further need for the memory DC so we delete it with DeleteDC. End painting session with EndPaint.
 

        .IF D@Message = &WM_DESTROY
            call 'GDI32.DeleteObject' D§hBitmap
            call 'User32.PostQuitMessage' &NULL
When we don't need the bitmap anymore, we delete it with DeleteObject

[Iczelion's Win32 Assembly HomePage]