Rendering Transparent GIFs - Revisited
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:
At this point you have an IStream
object ready for use, and all that remains is to make the
call to load the IPicture
object:
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:
- The background bitmap.
- The bitmap to be displayed, with a known transparent color.
- 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:
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:
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:
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:
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:
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.