By: Kenn Scribner for TechTalk
Reader Kevin Hoffman noted this (quite correctly) about this article: Hi Kenn, 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, well 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 Ill dive right in.
While "surfing" Microsofts 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 Ill describe shortly. In all of the game programming Ive done, I "blasted" a bitmap containing transparency to video memory using brute force. I examined each pixel and made a decisiondo 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 cameras 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 surfaces 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 cant 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 pixels 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 were essentially moving a bitmap around through several DCs, and while were at it, were 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.
Ive 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 demos 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, its not terribly fast. You wouldnt 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.
Youll 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 youre using an older version. Next time, well look more closely at the demo, where well 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, well 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 didnt 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, youd see the background image. But I wanted to provide you with some added value by briefly discussing owner-drawn status bar panes. While Ill 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" Ill 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 well 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 Im 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 areI painted them. In the general case, though, you may not, so Ive included the code youd need to use an arbitrarily wide bitmap. I took care to make sure my bitmaps werent 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 users system rather than adjust the users 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 panes bounding rectangular region as the "dirty" rectangle. The status bar will then only redraw our bitmap, which reduces screen flicker. When we invalidate the bitmaps 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 doesnt 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 }
Thats 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 bars 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 youre using an earlier version, youll need to create a new project and "insert" my source files before you will be able to recompile. Next time, well look at the Registry. Curious? See you next time!
Comments? Questions? Find a bug? Please send me a note! |