break;
The first step is of course loading the bitmap, this is quite simple with a bitmap resource, there are no significant differences from loading other resource types. Then we can get down to drawing…
case WM_PAINT:
{
BITMAP bm;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hbmOld = SelectObject(hdcMem, g_hbmBall);
GetObject(g_hbmBall, sizeof(bm), &bm);
BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, hbmOld);
DeleteDC(hdcMem);
EndPaint(hwnd, &ps);
}
break;
To start off we declare a couple of variables we need. Notice that the first one is a BITMAP , not an HBITMAP . BITMAP is a struct that holds information about an HBITMAP which is the actual GDI object. We need a way to get the height and width of the HBITMAP so we use GetObject() which contrary to it's name doesn't really get an object, but rather information about an existing one. "GetObjectInfo" would have been a more appropriate label. GetObject() works for various GDI object types which it can distinguish based on the value of the second parameter, the size of the structure.
The PAINTSTRUCT is a structure that contains information about the window being painted and what exactly is going on with the paint message. For most simple tasks, you can simply ignore the information it contains, but it's required for the call to BeginPaint() . BeginPaint() as it's name suggests is designed specifically for handling the WM_PAINT message. When not handling a WM_PAINT message you would use GetDC() which we will see in the timer animation examples in a while… but in WM_PAINT , it's important to use BeginPaint() and EndPaint() .
BeginPaint() returns us an HDC that represents the HWND that we pass to it, the one that WM_PAINT is being handled for. Any drawing operation we perform on this HDC will immediately display on the screen.
Setting up a Memory DC for the Bitmap
As I mention above, in order to draw on or with bitmaps, we need to create a DC in memory… the easiest way to do that here is to CreateCompatibleDC() with the one we already have. This gives us a Memory DC that is compatible with the color depth and display properties of the HDC for the window.
Now we call SelectObject() to select the bitmap into the DC being careful to store the default bitmap so that we can replace it later on and not leak GDI objects.
Once we've gotten the dimentions of the bitmap filled into the BITMAP struct, we can call BitBlt() to copy the image from our Memory DC to the Window DC, thus displaying on the screen. As always, you can look up each parameter in MSDN, but in short they are: The destination, the position and size, the source and source position, and finally the Raster Operation (ROP code), which specifies how to do the copy. In this case, we want a simple exact copy of the source made, no fancy stuff.
BitBlt() is probably the all time happiest function in all of the Win32 API and is the staple diet of anyone learning to write games or other graphics applications in windows. It was probably the first API that I memorised all the parameters to.
At this point the bitmap should be on the screen, and we need to clean up after ourselves. The first thing to do is restore the Memory DC to the state it was when we got it, which means replacing our bitmap with the default one that we saved. Next we can delete it altogether with DeleteDC() .
Finally we release the Window DC we got from BeginPaint() using EndPaint() .
Destroying an HDC is a little confusing sometimes because there are at least 3 ways to do it depending on how you got it in the first place. Here's a list of the common methods of gaining an HDC, and how to release it when you're done.
• GetDC() — ReleaseDC()
• BeginPaint() — EndPaint()
• CreateCompatibleDC() — DeleteDC()
And finally, at the termination of our program, we want to free any resources that we allocated. Technically speaking this isn't absolutely required, since modern Windows platforms are pretty good at freeing everything when your program exists, but it's always a good idea to keep track of your own objects because if get lazy and don't delete them they have a habit of getting loose. And no doubt, there are still bugs in windows especially older versions that won't clean up all of your GDI objects if you don't do a thorough job.
case WM_DESTROY:
DeleteObject(g_hbmBall);
PostQuitMessage(0);
break;
Example: bmp_two
Giving bitmaps the appearance of having transparent sections is quite simple, and involves the use of a black and white Mask image in addition to the colour image that we want to look transparent.
The following conditions need to be met for the effect to work correctly: First off, the colour image must be black in all areas that we want to display as transparent. and second, the mask image must be white in the areas we want transparent, and black elsewhere. The colour and mask images are displayed as the two left most images in the example picture on this page.
How does this get us transparency? First we BitBlt() the mask image using the SRCAND operation as the last parameter, and then on top of that we BitBlt() the colour image using the SRCPAINT operation. The result is that the areas we wanted transparent don't change on the destination HDC while the rest of the image is drawn as usual.
SelectObject(hdcMem, g_hbmMask);
BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCAND);
SelectObject(hdcMem, g_hbmBall);
BitBlt(hdc, 0, bm.bmHeight, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCPAINT);
Pretty simple eh? Fortunately it is, but one question remains… where does the mask come from? There are basically two ways to get the mask…
• Make it yourself in whatever graphics program you made the colour bitmap in, and this is a reasonable solution if you are using a limited number of graphics in your program. This way you can just add the mask resource to your program and load it with LoadBitmap() .
• Generate it when your program runs, by selecting one colour in your original image to be your "transparent" colour, and create a mask that is white everywhere that colour exists, and black everywhere else.
Since the first one is nothing new, you should be able to do things that way yourself if you want to. The second way involves from BitBlt() trickery, and so I will show one way of accomplishing this.
The simplest way to do it, would be to loop through every pixel on the colour image, check it's value and then set the corresponding pixel on the mask to black or white… SetPixel() is a very slow way to draw images however, and it's not really practical.
A much more efficient way involves using the way BitBlt() converts from colour images to black and white. If you BitBlt() (using SRCCOPY ) from an HDC holding a colour image into an HDC holding a black and white image, it will check what colour is set as the Background Colour on the colour image, and set all of those pixels to White, any pixel that is not the background colour will end up Black.
Читать дальше