Win32 GDI - Need help Adding a bitmap to this example

Started by
37 comments, last by aston2 4 years, 1 month ago

Firstly, I'd like to say hello to everyone here! I have found a lot of useful information on this website and hope to learn a lot more during my stay ?

I've been reading theForger's Win32 API tutorial and want to use it as a base for simple games/demos. Using the following code, I've got a ball bouncing around the window using double buffering, but I've been trying to add another bitmap as a background image, without much success. Can someone show me how it's done? Where is the best place to put a static bitmap?

Here is the code in it's present working state:

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include "resource.h"

#define WINDOW_WIDTH 405
#define WINDOW_HEIGHT 295

const char g_szClassName[] = "myWindowClass";
const int ID_TIMER = 1;
const int BALL_MOVE_DELTA = 2;
HICON hMyIcon;

typedef struct _BALLINFO
{
	int width;
	int height;
	int x;
	int y;
	int dx;
	int dy;

}BALLINFO;

BALLINFO g_ballInfo;

HBITMAP g_hbmBall = NULL;
HBITMAP g_hbmMask = NULL;

HBITMAP CreateBitmapMask(HBITMAP hbmColour, COLORREF crTransparent)
{
	HDC hdcMem, hdcMem2;
	HBITMAP hbmMask;
	BITMAP bm;

	GetObject(hbmColour, sizeof(BITMAP), &bm);
	hbmMask = CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL);

	hdcMem = CreateCompatibleDC(0);
	hdcMem2 = CreateCompatibleDC(0);

	SelectObject(hdcMem, hbmColour);
	SelectObject(hdcMem2, hbmMask);

	SetBkColor(hdcMem, crTransparent);

	BitBlt(hdcMem2, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
	BitBlt(hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem2, 0, 0, SRCINVERT);

	DeleteDC(hdcMem);
	DeleteDC(hdcMem2);

	return hbmMask;
}

void DrawBall(HDC hdc, RECT* prc)
{
	HDC hdcBuffer = CreateCompatibleDC(hdc);
	HBITMAP hbmBuffer = CreateCompatibleBitmap(hdc, prc->right, prc->bottom);
	HBITMAP hbmOldBuffer = (HBITMAP)SelectObject(hdcBuffer, hbmBuffer);

	HDC hdcMem = CreateCompatibleDC(hdc);
	HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, g_hbmMask);

	FillRect(hdcBuffer, prc, (HBRUSH)GetStockObject(WHITE_BRUSH));

	BitBlt(hdcBuffer, g_ballInfo.x, g_ballInfo.y, g_ballInfo.width, g_ballInfo.height, hdcMem, 0, 0, SRCAND);

	SelectObject(hdcMem, g_hbmBall);
	BitBlt(hdcBuffer, g_ballInfo.x, g_ballInfo.y, g_ballInfo.width, g_ballInfo.height, hdcMem, 0, 0, SRCPAINT);

	BitBlt(hdc, 0, 0, prc->right, prc->bottom, hdcBuffer, 0, 0, SRCCOPY);

	SelectObject(hdcMem, hbmOld);
	DeleteDC(hdcMem);

	SelectObject(hdcBuffer, hbmOldBuffer);
	DeleteDC(hdcBuffer);
	DeleteObject(hbmBuffer);
}

void UpdateBall(RECT* prc)
{
	g_ballInfo.x += g_ballInfo.dx;
	g_ballInfo.y += g_ballInfo.dy;

	if(g_ballInfo.x < 0)
	{
		g_ballInfo.x = 0;
		g_ballInfo.dx = BALL_MOVE_DELTA;
	}
	else if(g_ballInfo.x + g_ballInfo.width > prc->right)
	{
		g_ballInfo.x = prc->right - g_ballInfo.width;
		g_ballInfo.dx = -BALL_MOVE_DELTA;
	}

	if(g_ballInfo.y < 0)
	{
		g_ballInfo.y = 0;
		g_ballInfo.dy = BALL_MOVE_DELTA;
	}
	else if(g_ballInfo.y + g_ballInfo.height > prc->bottom)
	{
		g_ballInfo.y = prc->bottom - g_ballInfo.height;
		g_ballInfo.dy = -BALL_MOVE_DELTA;
	}
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg)
	{
		case WM_CREATE:
		{
			UINT ret;
			BITMAP bm;

			g_hbmBall = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_BALL));
			if(g_hbmBall == NULL)
				MessageBox(hwnd, "Could not load IDB_BALL!", "Error", MB_OK | MB_ICONEXCLAMATION);

			g_hbmMask = CreateBitmapMask(g_hbmBall, RGB(0, 0, 0));
			if(g_hbmMask == NULL)
				MessageBox(hwnd, "Could not create mask!", "Error", MB_OK | MB_ICONEXCLAMATION);

			GetObject(g_hbmBall, sizeof(bm), &bm);

			ZeroMemory(&g_ballInfo, sizeof(g_ballInfo));
			g_ballInfo.width = bm.bmWidth;
			g_ballInfo.height = bm.bmHeight;

			g_ballInfo.dx = BALL_MOVE_DELTA;
			g_ballInfo.dy = BALL_MOVE_DELTA;

			ret = SetTimer(hwnd, ID_TIMER, 15, NULL);
			if(ret == 0)
				MessageBox(hwnd, "Could not SetTimer()!", "Error", MB_OK | MB_ICONEXCLAMATION);
		}
		break;
		case WM_CLOSE:
			DestroyWindow(hwnd);
		break;
		case WM_PAINT:
		{
			RECT rcClient;
			PAINTSTRUCT ps;
			HDC hdc = BeginPaint(hwnd, &ps);

			GetClientRect(hwnd, &rcClient);
			DrawBall(hdc, &rcClient);

			EndPaint(hwnd, &ps);
		}
		break;
		case WM_TIMER:
		{
			RECT rcClient;
			HDC hdc = GetDC(hwnd);

			GetClientRect(hwnd, &rcClient);

			UpdateBall(&rcClient);
			DrawBall(hdc, &rcClient);

			ReleaseDC(hwnd, hdc);
		}
		break;
		case WM_DESTROY:
			KillTimer(hwnd, ID_TIMER);

			DeleteObject(g_hbmBall);
			DeleteObject(g_hbmMask);


			PostQuitMessage(0);
		break;
		default:
			return DefWindowProc(hwnd, msg, wParam, lParam);
	}
	return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    hMyIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON));
	WNDCLASSEX wc;
	HWND hwnd;
	MSG Msg;

	wc.cbSize		 = sizeof(WNDCLASSEX);
	wc.style		 = 0;
	wc.lpfnWndProc	 = WndProc;
	wc.cbClsExtra	 = 0;
	wc.cbWndExtra	 = 0;
	wc.hInstance	 = hInstance;
	wc.hIcon		 = hMyIcon;
	wc.hIconSm		 = NULL;
	wc.hCursor		 = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = g_szClassName;

	if(!RegisterClassEx(&wc))
	{
		MessageBox(NULL, "Window Registration Failed!", "Error!",
			MB_ICONEXCLAMATION | MB_OK);
		return 0;
	}

	hwnd = CreateWindowEx(
		0,
		g_szClassName,
		"Double Buffer Test",
		WS_OVERLAPPED | WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, hInstance, NULL);

	if(hwnd == NULL)
	{
		MessageBox(NULL, "Window Creation Failed!", "Error!",
        MB_ICONEXCLAMATION | MB_OK);
		return 0;
	}

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);

	while(GetMessage(&Msg, NULL, 0, 0) > 0)
	{
		TranslateMessage(&Msg);
		DispatchMessage(&Msg);
	}
	return Msg.wParam;
}

None

Advertisement

I've played around and added a background image. A few other rearrangements:

Do NOT return 0 for every handled message. Lookup the docs for each of them. Not all message require zero to be returned when handled. Always put the return DefWindowPro after the call. You may have to forward a few messages after handling them.

Also, do all your painting in WMPAINT, not in WM_TIMER.

For your sample I've included a BITMAP with the size of 320x200 named IDB_BACKGROUND with this code:

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include "resource.h"

// for TransparentBlt
#pragma comment( lib, "Msimg32.lib" )

#define WINDOW_WIDTH 405
#define WINDOW_HEIGHT 295

const char g_szClassName[] = "myWindowClass";
const int ID_TIMER = 1;
const int BALL_MOVE_DELTA = 2;
HICON hMyIcon;

typedef struct _BALLINFO
{
  int width;
  int height;
  int x;
  int y;
  int dx;
  int dy;

}BALLINFO;

BALLINFO g_ballInfo;

HBITMAP g_hbmBall = NULL;
HBITMAP g_hbmMask = NULL;

HBITMAP g_hbmBackground = NULL;

HBITMAP CreateBitmapMask( HBITMAP hbmColour, COLORREF crTransparent )
{
  HDC hdcMem, hdcMem2;
  HBITMAP hbmMask;
  BITMAP bm;

  GetObject( hbmColour, sizeof( BITMAP ), &bm );
  hbmMask = CreateBitmap( bm.bmWidth, bm.bmHeight, 1, 1, NULL );

  hdcMem = CreateCompatibleDC( 0 );
  hdcMem2 = CreateCompatibleDC( 0 );

  SelectObject( hdcMem, hbmColour );
  SelectObject( hdcMem2, hbmMask );

  SetBkColor( hdcMem, crTransparent );

  BitBlt( hdcMem2, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY );
  BitBlt( hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem2, 0, 0, SRCINVERT );

  DeleteDC( hdcMem );
  DeleteDC( hdcMem2 );

  return hbmMask;
}



void DrawScene( HDC hdc, RECT* prc )
{
  HDC hdcBuffer         = CreateCompatibleDC( hdc );
  HBITMAP hbmBuffer     = CreateCompatibleBitmap( hdc, prc->right, prc->bottom );
  HBITMAP hbmOldBuffer  = (HBITMAP)SelectObject( hdcBuffer, hbmBuffer );

  HDC hdcMem            = CreateCompatibleDC( hdc );

  // background
  HBITMAP hbmOld        = (HBITMAP)SelectObject( hdcMem, g_hbmBackground );
  SetStretchBltMode( hdcBuffer, COLORONCOLOR );
  StretchBlt( hdcBuffer, 0, 0, prc->right, prc->bottom, hdcMem, 0, 0, 320, 200, SRCCOPY );

  // ball
  SelectObject( hdcMem, g_hbmBall );
  TransparentBlt( hdcBuffer, g_ballInfo.x, g_ballInfo.y, g_ballInfo.width, g_ballInfo.height, hdcMem, 0, 0, g_ballInfo.width, g_ballInfo.height, RGB( 255, 255, 255 ) );

  // blit double buffer to window
  BitBlt( hdc, 0, 0, prc->right, prc->bottom, hdcBuffer, 0, 0, SRCCOPY );

  SelectObject( hdcMem, hbmOld );
  DeleteDC( hdcMem );

  SelectObject( hdcBuffer, hbmOldBuffer );
  DeleteDC( hdcBuffer );
  DeleteObject( hbmBuffer );
}

void UpdateBall( RECT* prc )
{
  g_ballInfo.x += g_ballInfo.dx;
  g_ballInfo.y += g_ballInfo.dy;

  if ( g_ballInfo.x < 0 )
  {
    g_ballInfo.x = 0;
    g_ballInfo.dx = BALL_MOVE_DELTA;
  }
  else if ( g_ballInfo.x + g_ballInfo.width > prc->right )
  {
    g_ballInfo.x = prc->right - g_ballInfo.width;
    g_ballInfo.dx = -BALL_MOVE_DELTA;
  }

  if ( g_ballInfo.y < 0 )
  {
    g_ballInfo.y = 0;
    g_ballInfo.dy = BALL_MOVE_DELTA;
  }
  else if ( g_ballInfo.y + g_ballInfo.height > prc->bottom )
  {
    g_ballInfo.y = prc->bottom - g_ballInfo.height;
    g_ballInfo.dy = -BALL_MOVE_DELTA;
  }
}

LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
  switch ( msg )
  {
    case WM_CREATE:
      {
        UINT ret;
        BITMAP bm;

        g_hbmBall = LoadBitmap( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDB_BALL ) );
        if ( g_hbmBall == NULL )
          MessageBox( hwnd, "Could not load IDB_BALL!", "Error", MB_OK | MB_ICONEXCLAMATION );

        g_hbmMask = CreateBitmapMask( g_hbmBall, RGB( 0, 0, 0 ) );
        if ( g_hbmMask == NULL )
          MessageBox( hwnd, "Could not create mask!", "Error", MB_OK | MB_ICONEXCLAMATION );

        GetObject( g_hbmBall, sizeof( bm ), &bm );

        ZeroMemory( &g_ballInfo, sizeof( g_ballInfo ) );
        g_ballInfo.width = bm.bmWidth;
        g_ballInfo.height = bm.bmHeight;

        g_ballInfo.dx = BALL_MOVE_DELTA;
        g_ballInfo.dy = BALL_MOVE_DELTA;

        g_hbmBackground = LoadBitmap( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDB_BACKGROUND ) );
        if ( g_hbmBackground == NULL )
          MessageBox( hwnd, "Could not load IDB_BACKGROUND!", "Error", MB_OK | MB_ICONEXCLAMATION );

        ret = SetTimer( hwnd, ID_TIMER, 15, NULL );
        if ( ret == 0 )
          MessageBox( hwnd, "Could not SetTimer()!", "Error", MB_OK | MB_ICONEXCLAMATION );
      }
      return 0;
    case WM_CLOSE:
      DestroyWindow( hwnd );
      return 0;
    case WM_ERASEBKGND:
      return 1;
    case WM_PAINT:
      {
        // do all drawing only in here!
        RECT rcClient;
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint( hwnd, &ps );

        GetClientRect( hwnd, &rcClient );

        DrawScene( hdc, &rcClient );

        EndPaint( hwnd, &ps );
      }
      return 0;
    case WM_TIMER:
      {
        RECT rcClient;

        GetClientRect( hwnd, &rcClient );
        UpdateBall( &rcClient );
        InvalidateRect( hwnd, NULL, FALSE );
      }
      return 0;
    case WM_DESTROY:
      KillTimer( hwnd, ID_TIMER );

      DeleteObject( g_hbmBall );
      DeleteObject( g_hbmMask );


      PostQuitMessage( 0 );
      return 0;
  }
  return DefWindowProc( hwnd, msg, wParam, lParam );
}



int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
  hMyIcon = LoadIcon( hInstance, MAKEINTRESOURCE( IDI_ICON ) );
  WNDCLASSEX wc;
  HWND hwnd;
  MSG Msg;

  wc.cbSize = sizeof( WNDCLASSEX );
  wc.style = 0;
  wc.lpfnWndProc = WndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInstance;
  wc.hIcon = hMyIcon;
  wc.hIconSm = NULL;
  wc.hCursor = LoadCursor( NULL, IDC_ARROW );
  wc.hbrBackground = (HBRUSH)( COLOR_WINDOW + 1 );
  wc.lpszMenuName = NULL;
  wc.lpszClassName = g_szClassName;

  if ( !RegisterClassEx( &wc ) )
  {
    MessageBox( NULL, "Window Registration Failed!", "Error!",
      MB_ICONEXCLAMATION | MB_OK );
    return 0;
  }

  hwnd = CreateWindowEx(
    0,
    g_szClassName,
    "Double Buffer Test",
    WS_OVERLAPPED | WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, hInstance, NULL );

  if ( hwnd == NULL )
  {
    MessageBox( NULL, "Window Creation Failed!", "Error!",
      MB_ICONEXCLAMATION | MB_OK );
    return 0;
  }

  ShowWindow( hwnd, nCmdShow );
  UpdateWindow( hwnd );

  while ( GetMessage( &Msg, NULL, 0, 0 ) > 0 )
  {
    TranslateMessage( &Msg );
    DispatchMessage( &Msg );
  }
  return Msg.wParam;
}

Fruny: Ftagn! Ia! Ia! std::time_put_byname! Mglui naflftagn std::codecvt eY'ha-nthlei!,char,mbstate_t>

Thanks Endurion, it works great! I will add your information to my notes. I have a few questions though. Is there any advantage to using TransparentBlt over BitBlt, performance-wise? Also, I've read that Msimg32.lib is not available on Windows XP. Regarding StretchBlt; the image I'm using will be exactly the same size as the window, so should I go with BitBlt instead?

None

Obviously it will not be as performant as a plain BitBlt, but most of these are hardware accellerated anyway; so it shouldn't matter much.

BitBlt is obviously a better fit if the sizes match, so go for it.

The docs say msimg32.lib is available since Windows 2000, so that ought to work with XP as well.

Fruny: Ftagn! Ia! Ia! std::time_put_byname! Mglui naflftagn std::codecvt eY'ha-nthlei!,char,mbstate_t>

Good to know, though I'd like to get the most out of GDI. I have another question about painting. I have found that it is possible to load a bitmap without needing a BITMAP structure as show in the code below. Is this an acceptable way of doing it, or will I run into problems down the line? Also would it be advisable to have seperate functions for drawing a background and animating sprites on top of it, or is it just a matter of preference?

case WM_PAINT:
        {
            PAINTSTRUCT ps;

            HDC hdc = BeginPaint ( hWnd, &amp;ps );

            HDC hdcMem = CreateCompatibleDC( hdc );
            HBITMAP hbmOld = ( HBITMAP )SelectObject( hdcMem, g_hbmBackground );

            BitBlt ( hdc, 0, 0, 405, 290, hdcMem, 0, 0, SRCCOPY );

            SelectObject ( hdcMem, hbmOld );
            DeleteDC ( hdcMem );

            EndPaint( hWnd, &amp;ps );
        }

None

That code is not loading any bitmap? You probably think about the LoadBitmap function?

Regarding painting order, that's entirely up to you. Just in conjunctions with your WindowProc it's advised to only paint in WM_PAINT.

WM_PAINT is called for repainting, so if you split parts somewhere else, Windows won't call them when you get a full redraw.

Fruny: Ftagn! Ia! Ia! std::time_put_byname! Mglui naflftagn std::codecvt eY'ha-nthlei!,char,mbstate_t>

Sorry, I meant it displays the bitmap to the screen. I have the LoadBitmap function in the WM_CREATE handler. Do I really need to have a BITMAP structure since I won't be using any of it's members for a simple, static bitmap?

None

No, you don't need it. I don't see you using it in the code snippets anyway. You need the HBITMAP of course. A BITMAP describes the header file structur of a .bmp file.

And if you needed, you could use GetObject to extract the info from a HBITMAP handle. But usually all you need is the size of the bitmap in pixel.

Fruny: Ftagn! Ia! Ia! std::time_put_byname! Mglui naflftagn std::codecvt eY'ha-nthlei!,char,mbstate_t>

Thanks again for confirming that, I appreciate your input!

None

This topic is closed to new replies.

Advertisement