Transparent Bitmaps

By: Kenn Scribner for TechTalk


Reader Kevin Hoffman noted this (quite correctly) about this article:

Hi Kenn,
I've read over your "Drawing Bitmaps with Transparency to a Status Bar, Part I and II" posted in TechTalk, Library of Computer Science. It's a great article. However, in Part I you're recreating the wheel. The image list control (CImageList) has the nice ability to draw transparent bitmaps. (Using the same technique that you described.) The image list can open an icon and create the mask automatically, or along with a regular bitmap, a mask bitmap can be provided. Or you can tell it what color the background is
on the bitmap and it will create a mask automatically. Another cool feature of the image list is the ability for it to "blend" the bitmap 50% with the system highlight color when drawing it. Just FYI :)
Maybe you knew this already and wanted to recreate the wheel -- oh well :)

-Kevin (LFDev@compuserve.com)

Actually, I did know this!  I use the image list control all of the time...it seems all of the later common controls use it.  My intent was to "set up" device contexts for future issues.  Besides, the routine makes a terrific "quick and dirty" bitmap copy routine for those little jobs... :)  Thanks, Kevin!


Run the demo program (all demos require Visual C++ 5.0!)
Download the demo source

Background: To be honest, I was goofing off one day (yes, while on the clock).  I'm always hunting for little tidbits, and when I saw this one, I had to try it.  I had done some status bar work where I'd placed some "LED" controls in an owner-drawn pane, but they never looked quite right to me.  This technique, though, I've found to be quite solid and even visually stunning (okay, that's stretching it, but the bitmaps do look good!).   I use this technique, for example, in my Class Werx product.  When you use the Wizard and add/remove control options, bitmaps appear and disappear in the window to the left.  Transparent bitmaps.  Why not?  Works like a champ.  :)

Drawing Bitmaps with Transparency to a Status Bar

Part I, Understanding Transparent Bitmaps
(Part II, Owner-Drawn Status Bar Panes)

In the last issue we examined the status bar and how we might assign it a child control. In this issue, we’ll take a look at bitmaps with transparency, and then (next issue) how we draw them directly onto the status bar or any Windows surface which can provide a device context. We have a lot of ground to cover in a short space, so I’ll dive right in.

While "surfing" Microsoft’s Web site, I came across a Knowledge Base article, "HOW TO: Drawing Transparent Bitmaps" (http://support.microsoft.com/support/kb/articles/Q79/2/12.asp). I read it and was intrigued by the technique they used, which I’ll describe shortly. In all of the game programming I’ve done, I "blasted" a bitmap containing transparency to video memory using brute force. I examined each pixel and made a decision—do I draw it or not? The Knowledge Base article uses a different tact, that of using a copy of the original bitmap as a "mask", which when combined with the original bitmap and the target surface will yield transparencies given a color you want to act as the "transparent" color.

This is very much like black and white photography. The camera takes a picture which produces a "negative" image. The negative is processed, then used to expose photographic paper. Since the negative contains "inverse" information, the exposed photographic paper, when also chemically processed, yields a "positive" image which appears identical to the camera’s subject matter. As you can see, this is a far different and more elegant technique than my crude "blasting" methods (though I do not guarantee this method is faster, and speed is about the most critical metric there is to a game programmer).

The article outlines nine steps necessary to draw a bitmap with transparency:

1. Generate a memory device context (DC) to contain the original bitmap.
2. "Select" the original bitmap into the new "source" DC.
3. Generate a "destination" DC which will contain the final image.
4. Select the target surface’s background into the destination DC. 
5. Create the "AND" mask containing the non-transparent colors:
	a. Set the background to the transparent color.
	b. Create a monochrome DC.
	c. Copy the image into the monochrome DC.
6. Copy the AND mask to the destination DC using SRCAND.
7. Copy the inverse of the AND mask to the source DC using SRCAND.
8. Copy the source DC to the destination DC using SRCPAINT.
9. Copy the destination DC to the surface using SRCCOPY.

Hmm. Lots of steps using things called "DCs", copies using "SRCAND" and other stuff…what IS all of this? Well, a DC is a device context. I can’t adequately describe all of the important aspects of device contexts in this issue, as the topic is quite involved. But for our purposes here, let us define a device context as a Windows construct which contains a bitmap image we can manipulate using "raster operations", or raster ops for short. A raster op is nothing more than a logical operation to be applied to each pixel of the bitmap as it is copied from one device context to another. For example, if you SRCAND two pixels, the final pixel color will be the Boolean AND of each pixel’s color in both the source and destination DCs. You might get something strange, and then again, you might not. There are many raster ops…I suggest using Visual C++’s "help" function to see them all. Basically, though, there is one for just about every combination of the Boolean operations AND, OR, and NOT (complement). Look up CDC::BitBlt() (Bitmap Block Transfer) for the complete list a description of each.

Knowing this, you can see we’re essentially moving a bitmap around through several DCs, and while we’re at it, we’re applying a couple of different Boolean operations to the pixels of the bitmaps. The end result is we will eliminate all pixels which contain a color we designate as the transparent color. In effect, pixels of the transparent color will be "masked out", or logically manipulated such that they never make it from the original bitmap to the target surface. They will be replaced by pixels from the destination surface.

I’ve unabashedly stolen and copiously plagiarized the code required to perform these nine steps from the Knowledge Base article. Well, that and I converted it from the SDK version in the article to C++/MFC. I decided to code this routine as a member of my demo’s main frame object. This routine could be used by many objects, and the MFC main frame access helper function AfxGetMainWnd() helps by providing a pointer to the main frame object when we need it. Our other logical alternative would be to place the drawing routine in our application object (accessible by AfxGetApp()). Since the demo works with the status bar, which is "owned" by the main frame object, it made more sense to put the member function there.

And while I think this technique is really nifty, it’s not terribly fast. You wouldn’t use this routine for tasks requiring fast animation sequences with transparency. It simply takes too long to copy the bitmap data from device context to device context. However, if you have a more common need, this routine fill the void nicely. I use it a lot myself.

You’ll find the demo source code above. As is usual, I built the demonstration using Visual C++ version 5.0, so you may need to rebuild the demo project if you’re using an older version. Next time, we’ll look more closely at the demo, where we’ll see a real-world use for this sort of bitmap manipulation. Until then, kick bugs!

Drawing Bitmaps with Transparency to a Status Bar

Part II, Owner-Drawn Status Bar Panes

If you recall, last issue we covered the mechanics of drawing a bitmap with transparent regions. This issue, we’ll take our drawing routine and use it to place a bitmap with transparency on the status bar. Why? Many professional applications do this, for one. For example, print a document in Microsoft Word™. See the small printer kicking out paper down there? Microsoft didn’t know beforehand what color your status bar would be, so the regions shown in the correct color for you system were drawn with transparency.

I could have demonstrated a transparent bitmap by drawing a background in the main window, then by copying the transparent bitmap onto the background. Where the bitmap is transparent, you’d see the background image. But I wanted to provide you with some added value by briefly discussing owner-drawn status bar panes. While I’ll be describing the status bar, this technique also works for other owner-drawn controls, such as list boxes, combo boxes, or buttons, to name a few (less the "space reservation" I’ll discuss shortly, which if you remember is used to dictate what "panes" the status bar should display).

There are essentially three tricks we need to pull. First, we must reserve space in the status bar for our bitmap. Second, we must create a pane large enough to hold our bitmap and tell the status bar we want to draw the pane when the status bar repaints--this is the "owner draw" operation. Finally, we need to install a message handler which will be called when the status bar is repainting and requires us to draw our stuff in its pane.

The first trick, reserving space, is handled by modifying the indicators array we saw last issue. My code looks like this (note I removed the keyboard indicators):

static UINT indicators[] = {
	ID_SEPARATOR, // status line indicator
	ID_INDICATOR_INACTIVE, // "widest" possible string
	0 // bogus ID as a placeholder
};

I added a "bogus" indicator which will do nothing more than reserve the space in the indicator array. This was necessary to prepare for the next step.

The second trick will be to assign a valid indicator to the pane, at which time we’ll tell the status bar how big the pane should be and assign a "style" to the new pane. The style bits tell the control how the pane should be drawn. Since I’m drawing bitmaps, I first must load them somewhere so I can easily retrieve them (note they come in as bitmap resources). Here, I use two CMainFrame member variables of type CBitmap. Next, I determine how wide the bitmaps are and fiddle with the status bar:

BITMAP bm;
m_bmActive.GetBitmap(&bm);
m_wndStatusBar.SetPaneInfo(2,ID_INDICATOR_BITMAP,
	SBPS_NORMAL | SBT_OWNERDRAW,bm.bmWidth);

Of course, I know how wide the bitmaps are—I painted them. In the general case, though, you may not, so I’ve included the code you’d need to use an arbitrarily wide bitmap. I took care to make sure my bitmaps weren’t taller than the status bar pane (18 pixels, less two for 3D effects). If necessary, you can adjust the height of the status bar by using CStatusBar::GetStatusBarCtrl(), then by using CStatusBarCtrl::SetMinHeight(). Be careful, though. This will only work for MFC version 4.0 and higher, as you will be accessing the underlying status bar common control which was not used in the earlier versions of MFC. I recommend, however, you use CDC::StretchBlt() to adjust the bitmap to an appropriate rectangular area and leave the status bar height alone. In other words, adjust your bitmap to fit the user’s system rather than adjust the user’s system to accommodate your bitmap.

In any case, because we used the SBT_OWNERDRAW style, the status bar will look to us to repaint that pane when the control requires updating. When we want to change the bitmap shown on the status bar, I call CWnd::RedrawWindow() using the pane’s bounding rectangular region as the "dirty" rectangle. The status bar will then only redraw our bitmap, which reduces screen flicker. When we invalidate the bitmap’s rectangle, the status bar will issue a WM_DRAWITEM message, which we must intercept in order to perform the required painting. ClassWizard is no help here! The WM_DRAWITEM message doesn’t show up as a possible message handler! So, for our third trick, we need to add the message handler ourselves. First, we add this to our MainFrm.h file just before DECLARE_MESSAGE_MAP():

afx_msg void OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct);

Then, we modify the message map in the MainFrm.cpp message handling section. Add this just prior to the END_MESSAGE_MAP() macro:

ON_WM_DRAWITEM()

Finally, we add the actual handler to MainFrm.cpp:

void CMainFrame::OnDrawItem( int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct )
{
	// If this message came from the status bar, we'll process it
	if ( nIDCtl == ID_VIEW_STATUS_BAR ) {
		// It is here we will call DrawTransparentBitmap()
		// (demo provides details)
	} // if
}

That’s it! Each time we want to update the status bar, CMainFrame::OnDrawItem() will examine the DRAWITEMSTRUCT and pull out the information it requires to draw the new bitmap, then it will call DrawTransparentBitmap() to actually copy the image onto the status bar’s pane.

Whew! A lot of ground covered, if even lightly. However, many professional applications use this technique when dealing with the status bar. Feel free to download the demonstration program from (see top of page). Once again, I used Visual C++ 5.0, so if you’re using an earlier version, you’ll need to create a new project and "insert" my source files before you will be able to recompile. Next time, we’ll look at the Registry. Curious? See you next time!

Comments? Questions? Find a bug? Please send me a note!


[Back] [Left Arrow] [Right Arrow][Home]