Windows Developer Journal, July, 2002 Windows Developer Magazine

July, 2002

Back in March 1998, Tom Armstrong and I published an article in Dr. Dobb's Journal showing a technique for implementing transparent regions in bitmapped images. In those days, Windows developers had to roll their own transparency solution, and the path was quite arduous. Today, Windows programmers can render images with transparency using Microsoft's IPicture COM interface, which is considerably easier than the solution Tom and I suggested. It's an easy way to accomplish the same task - if you watch out for its single notable drawback

The IPicture interface is a good way to draw images with transparency today. In the future, you'll probably want to use GDI+, Microsoft's new GDI interface for C++ programmers. I'll show you how to convert the sample program to write cleaner code with GDI+, and discuss the improvements it brings to the table.

Transparent GIFs

When creating a GIF file, you have the option of designating one color in the image as transparent. This transparency feature is normally used to make it appear as if an irregularly shaped image is floating on the background. An example of how this can be useful is shown in Figures 1 and 2.

In Figure 1, two news articles displayed on a Web page have graphical icons that illustrate article topics. The users of this particular web site have the ability to change their personal settings to use a different layout, which changes the background color of the page. In Figure 2, a different theme is used to show the same articles.

In both Figure 1 and Figure 2, the background of the GIF file used to illustrate the article is transparent. Web browsers know that they need to let the background color show through in the transparent regions of the graphic. You can see that the browser deals with the transparency properly in both cases, with nice effect. A beige background in Figure 1, a white background in Figure 2, and in both the topic images float nicely above the background.



Figure 1 - Transparent GIFs on a light brown background



Figure 2 - The same GIFs on a white background

Loading the GIF File

Getting an external file into a format Windows can display can be a chore. Tom and I chose to skirt the issue in our original article by working with files stored in Windows' Device Independent Bitmap format. Although not a popular file format, it was one of the few that Windows was able to read and write without help from third-party libraries.

The situation has improved quite a bit since then. You can now use Microsoft's OleLoadPicture() function to load both JPEG and GIF files. Given the popularity of these two file formats, you might find that OleLoadPicture() covers all your needs in this area.

OleLoadPicture() loads data from an IStream object and stores it in an IPicture object. You'll find that having the data in IPicture format is quite convenient when it comes time to render it. However, the odds are good that you want to be able to load your data from a file, which is not quite the same thing as an IStream interface.

The easiest way I know of to get from a file to an IStream object is to simply read the whole thing into memory and then use CreateStreamOnHGlobal() to create an IStream. A fragment that accomplishes that is shown here:

C++:
  1. FILE *f = fopen( filename, "rb" ) ;
  2. if ( !f )
  3.     return FALSE;
  4. long size = _filelength( fileno( f ) ) ;
  5. HGLOBAL hGlobal = GlobalAlloc( GMEM_MOVEABLE, size ) ;
  6. LPVOID pvData = GlobalLock( hGlobal ) ;
  7. read( pvData, 1, size, f ) ;
  8. GlobalUnlock(hGlobal) ;
  9. fclose( f ) ;
  10. LPSTREAM pStream = NULL;
  11. HRESULT hr = CreateStreamOnHGlobal( hGlobal, TRUE, &pStream ) ;

At this point you have an IStream object ready for use, and all that remains is to make the call to load the IPictureobject:

C++:
  1. hr = ::OleLoadPicture( pStream,
  2.                        size,
  3.                        FALSE,
  4.                        IID_IPicture,
  5.                        (LPVOID *) &m_pPicture ) ;

Although the code isn't particularly elegant looking, it has accomplished the goal of reading a GIF or JPEG file into memory in just a dozen lines or so. All that remains is the task of drawing the code on the appropriate display.

The Drawing Part

In our original article, Tom and I presented a straightforward method for displaying bitmaps with transparency. It required the use of three bitmaps:

  1. The background bitmap.
  2. The bitmap to be displayed, with a known transparent color.
  3. A special mask bitmap. The mask bitmap should have all bits set to zero in areas where the display bitmap is opaque, and all bits set to one in the areas where the display bitmap is transparent.

Displaying the bitmap is simply a matter of performing the following sequence of operations:

Output = Bitmap1 XOR Bitmap2 AND Bitmap3 XOR Bitmap 2

With just a little bit of work, you can see that the output will be a copy of the pixel from Bitmap1 in every position where the mask (Bitmap3) has a value of all ones. The output will be a copy from Bitmap2 in every position where the mask has a value of all zeros.

That's all you really need to understand in order to draw bitmaps with transparency in any sort of graphics environment. What made it into a long article in 1998 was simply all the plumbing you had to do in order to get the background bitmap, create the mask, get the display bitmap, and so on.

The good news is that Microsoft's IPicture interface can take care of all of the messy details here with one call to its Render() function. Once you have loaded the GIF file into an IPicture object, the snippet of code below is all you need to get it onto your display:

C++:
  1. void CChildView::RenderPicture( CDC &dc,
  2.                                 CMainFrame *frame,
  3.                                 CRect &rect )
  4. {
  5.     OLE_XSIZE_HIMETRIC width;
  6.     OLE_YSIZE_HIMETRIC height;
  7.     frame->m_pPicture->get_Width( &width ) ;
  8.     frame->m_pPicture->get_Height( &height ) ;
  9.     CSize sz( width, height ) ;
  10.     dc.HIMETRICtoDP( &sz ) ;
  11.     int x = ( rect.Width() - pt.x ) / 2;
  12.     int y = ( rect.Height() - pt.y ) / 2;
  13.     frame->m_pPicture->Render( dc,
  14.                                x,
  15.                                rect.Height() - y,
  16.                                pt.x, pt.y,           
  17.                                0,
  18.                                0,
  19.                                width,
  20.                                height,
  21.                                NULL ) ;
  22. }

Note that the code shown here centers the image in the client window, which complicates things just a bit. It also has to deal with the fact that IPicture uses the HIMETRIC mapping mode, which I have to convert to the more convenient pixel measurements.

A First Pass

Pressing this code into service reveals one striking problem with the IPicture interface. Calling its Render() function with the intention of writing directly to your display window leads to a somewhat unpleasant surprise. It turns out that IPicture uses the standard algorithm I describe earlier to implement transparency. Unfortunately, it performs each of the three steps directly on the display, so you get to see each intermediate bitmap in the process. The four screen shots below show it in all its glory.



Figure 3a - Opening the file



Figure 3b - The background has been XORed with the image



Figure 3c - The resulting bitmap has now been ANDed with the special transparency mask



Figure 3d - After a final XOR, the image is rendered with transparency

This sequence might be interesting to the average DDJ reader, but the end users of your program are hardly going to want to see these three images flashing in quick succession across their screen. The flickering is barely noticeable with small images, but on slow machines, big images go through the painting sequence with painful precision.

The solution to this problem is straightforward. Instead of drawing directly to the screen, you can create an off-screen bitmap, have Render() draw to that bitmap, then copy the completed product to the display. Having done that, you'll find that not only is the flicker eliminated, but the drawing operation actually completes much more quickly.

An MFC routine that accomplishes this in conjunction with the rendering routine above is shown here:

C++:
  1. void CChildView::DrawOff( CDC &dc,
  2.                           CRect &rect,
  3.                           CMainFrame *frame,
  4.                           CBrush &brush )
  5. {
  6.     CDC odc;
  7.     odc.CreateCompatibleDC( &dc ) ;
  8.     CBitmap obm;
  9.     obm.CreateCompatibleBitmap( &dc, rect.Width(), rect.Height() ) ;
  10.     odc.SelectObject( &obm ) ;
  11.     odc.FillRect( rect, &brush ) ;
  12.     RenderPicture( odc, frame, rect ) ;
  13.     dc.BitBlt(0, 0, rect.Width(), rect.Height(), &dc, 0, 0, SRCCOPY ) ;
  14. }

You can see that it only takes a few lines to create the off-screen bitmap. The code above creates a bitmap of the correct size, fills it with the correct background pattern, then calls the rendering routine. Once that is done, all that is left is a BitBlt() call which copies the result to the screen.

The Sample Program

To demonstrate this, I created an MFC program named TransparentGif, forgoing the usual document/view architecture. Instead, I just paint the entire client background of the window with a pattern, the better to view the transparency of the loaded image.

When the program first starts up, by default it draws directly to the screen. If you are blessed with a sub-Gigahertz CPU, you should be able to see the flickering effect quite clearly when loading the sample GIF included with the source. I added code to force a redraw every time you click on the image, which allows you to see the effect in all its beauty.

You can then use the Options|Off Screen Draw menu item to switch to the use of the off-screen bitmap. A little sampling between the two should be enough to convince you of the value of the off-screen drawing technique.

Switcing to GDI+

Looking forward to the future, you might find that this task is going to become a bit easier. Microsoft has developed a new interface to GDI for Windows programmers called GDI+. The class libraries and supporting DLL for GDI+ are shipping as part of Windows XP, and can be installed as an add-on to Windows NT, 2000, 98, and ME.

The header files and import libraries required for GDI+ are part of Microsoft's Platform SDK. To get this SDK, you'll have to have to subscribe to Microsoft's MSDN program at the Operating System Level or better, which has a list price of $699.

You need to perform two installations to develop and test GDI+ programs: the Win32 Platform SDK and the GDI+ runtime files. Both of these can be found on your MSDN CDs or can be downloaded directly from the MSDN site The GDI+ runtime kit can also be distributed with your software, so your end users need no special licensing.

Additions to the project

I made a copy of the TransparentGif program, renaming all the files and classes so that the new project goes by the name TransparentPlus. I then set to work making the changes needed to use GDI+ as the graphics interface.

The first few changes are the ones you typically have to make when using a new library. I added an include of the master header file, gdiplus.h, to stdafx.h. This ensures that I have all the correct prototypes and class definitions. To make sure that the compiler was able to find the header file, I had to add the SDK include directory to the preprocessor settings for the C++ compiler. I used the default installation location for the SDK, so I simply inserted the path:

C:\Program Files\Microsoft SDK\Include

into the list of additional directories for the compiler.

All of the classes and identifiers in GDI+ are placed in the Gdiplus namespace, so I also added a using namespace Gdiplus; declaration to stdafx.h. In a larger project, I probably wouldn't do this; the use of explicit namespaces in the source would add readability to the code.

Likewise, the import library was in the default SDK directory, so all I had to do was drag:

C:\Program Files\Microsoft SDK\lib\GdiPlus.lib

into my workspace pane, thereby adding it to the project.

Startup code

GDI+ programs require a few lines of code for initialization, as well as a cleanup call. I added the following lines to the InitInstance() function in my application class:

C++:
  1. GdiplusStartupInput gdiplusStartupInput;
  2. ULONG_PTR gdiplusToken;
  3. GdiplusStartup( &gdiplusToken, &gdiplusStartupInput, NULL) ;

This code is lifted cookie-cutter style straight out of the GDI+ documentation. The token initialized by the startup function is passed to the GdiplusShutdown()method when you are done using the library.

File Loading

As you recall, the file loading code in the standard GDI program was a real rat's nest. The hoops I had to jump through in order to load a file into an IPicture object gave a classic demonstration of an ugly Microsoft API.

I'm happy to say that someone working in API design in Redmond has seen the light in this area. Loading images is an order of magnitude easier with GDI+. Instead of loading into an IPicture COM object, GDI+ supports a C++ Image object. This requires that I change the type of CMainFrame::m_pPicture in the appropriate header file. I can then reduce the file loading code from the ugly mess I had before to this picture of simplicity:

C++:
  1. void CMainFrame::LoadPicture(CString filename)
  2. {
  3.     USES_CONVERSION;
  4.     LPWSTR wname = A2W( filename ) ;
  5.     if ( m_pPicture )
  6.         delete m_pPicture;
  7.         m_pPicture = Image::FromFile( wname, FALSE ) ;
  8. }

This is a huge improvement, and by itself is almost worth the cost of the SDK.

Drawing

The drawing code with GDI+ is a bit simpler as well, mostly because the Image object uses pixels for measurement instead of the HIMETRIC units from IPicture. Other than that a simple replacement of the Render() function with a call to DrawImage() constitutes most of the work. The updated routine is shown here:

C++:
  1. void CChildView::RenderPicture( CDC &dc,
  2.                                 CMainFrame *frame,
  3.                                 CRect &rect )
  4. {
  5.     if ( frame->m_pPicture )
  6.     {
  7.         int width = frame->m_pPicture->GetWidth() ;
  8.         int height = frame->m_pPicture->GetHeight() ;
  9.         int x = ( rect.Width() - width ) / 2;
  10.         int y = ( rect.Height() - height ) / 2;
  11.         Graphics graphics( dc ) ;
  12.         graphics.DrawImage( frame->m_pPicture,  x, y, width, height ) ;
  13.     }
  14. }

GDI+ makes my code look a lot nicer, and informal tests show that GDI+ renders a bit faster as well. Better yet, GDI+ is smart enough to avoid rendering all three steps directly on to the screen, which means you won't see a flash of the XOR'd or masked image.

Despite the speed of GDI+, I think you'll still find that you need to do this image rendering in an off-screen bitmap. Experimenting with the second test program shows that although you don't see intermediate images, you still get an annoying flickering as the image as drawn. Rendering off-screen eliminates that problem.

Conclusion

This article gives you a couple of different ways to effectively use transparent GIF files in your Win32 programs. For today, the IPicture interface does a pretty good job. When GDI+ rolls out as a standard part of Visual Studio, you'll probably want to switch over to it for your imaging needs. Either way, you've got good options that make things look a lot nicer than they did in 1998.

Links

Source for both demo programs