Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Transparent Bitmap Buttons with Alpha-Blended Regions in .NET

0.00/5 (No votes)
12 Aug 2004 1  
How to create and draw transparent bitmap buttons in .NET Forms.

Sample Image - TransButtonNetDemo.jpg

Introduction

Since I created alpha-blended bitmap buttons for MFC, I thought I would try the same thing in a .NET Windows Forms environment. Even though the concepts behind the coding were very similar, I found implementing the details to be a bit more challenging. I therefore thought it might benefit others to learn some of the pitfalls and workarounds that are required to make this work.

I did not create a very sophisticated demo project for this example. There are simply a number of buttons on the bitmapped background window for display examples, and only the refresh buttons and exit button actually do something. The refresh buttons will change the background bitmap (2 in all) so the transparent buttons have another look.

Creating the Bitmaps

The bitmaps for this button must contain four images, laid out horizontally within the bitmap. The four states represent Normal, Selected, Hover, and Disable, in that order. The four images must be the same size. I used 48x48 round buttons and 75x48 pill buttons for this demo.

The button bitmaps are 32-bit-per-pixel (32bpp) RGB images with an alpha channel that determines transparency. I created these images in Photoshop a bit different than the MFC button images. The tricky part is the alpha channel, because Photoshop is not always very clear about how to get this just right. The steps I took were to first create a new alpha channel by selecting the Channels tab in the Layers-Channels-Paths window, and then select the tiny "Create New Channel" button at the bottom. Once the Alpha Channel is created, I double-clicked this channel to open the Options dialog, and then changed the Opacity to 0% with a Black or White Color and a Masked Area selection. Next, I turned off the RGB channel views (deselect the little eye next to each one), and left the Alpha channel visible. The display should be either black or white at this point depending on the mask color. I then selected the RGB channels by using Ctrl-click on the RGB channel. This should select part or all of the actual button layers. Finally, I painted the opaque areas with a black or white brush to get the level of transparency I want. The display usually looks something like this:

Photoshop Alpha Channel painting

The images must be saved as 32-bit ARGB, so the Alpha layer must be preserved in the Save dialog, and remember that there can only be one Alpha layer.

Loading the Bitmaps from a Resource DLL

One of the most frustrating things I learned about alpha channel bitmaps in GDI+ is that they don't load properly. No matter what I tried, the alpha-channel was always lost, and the bitmaps were converted back to 32bppRGB. I finally found a solution to this problem when I stumbled across a demo by Steve McMahon on vbaccelerator.com. Steve's demo provided a good workaround by loading the bitmaps from a resource DLL and doing some manipulation to keep the alpha data in tact.

public static Bitmap DibToBitmap(
    IntPtr hDib
    )
{
    BITMAP tBM = new BITMAP();
    GetObjectBitmap(hDib, Marshal.SizeOf(tBM), ref tBM);
    Bitmap bm = new Bitmap(tBM.bmWidth, tBM.bmHeight);

    // set the bitmap's data to the data from

    // the DIB:

    if (tBM.bmBitsPixel == 32)
    {
        // Bizarre but true: you *must* clone the newly created

        // bitmap to get one with the correct pixel format, even

        // if you attempted to create the original one with the 

        // correct format...

        bm = bm.Clone(new Rectangle(0, 0, tBM.bmWidth, tBM.bmHeight),
            PixelFormat.Format32bppArgb);

        // Lock the bitmap bits

        BitmapData destData = bm.LockBits(
            new Rectangle(0, 0, bm.Width, bm.Height),
            ImageLockMode.ReadWrite,
            PixelFormat.Format32bppArgb);
        int destWidth = destData.Stride;
        IntPtr destScan0 = destData.Scan0;

        unsafe
        {
            byte * pDest = (byte *) (void *) destScan0;
            // The DIB is upside down compared to a GDI+ bitmap

            pDest += ((bm.Width * 4) * (bm.Height - 1));
            byte * pSrc = (byte *) (void *) tBM.bmBits;

            for (int y = 0; y < bm.Height; ++y)
            {
                for (int x = 0; x < bm.Width; ++x)
                {
                    pDest[0] = pSrc[0]; // blue

                    pDest[1] = pSrc[1]; // green

                    pDest[2] = pSrc[2]; // red

                    pDest[3] = pSrc[3]; // alpha

                    
                    // Move to next BGRA

                    pDest += 4;
                    pSrc += 4;
                }
                pDest -= (bm.Width * 8);
            }
        }

        bm.UnlockBits(destData);
    }
    else
    {
        // Easier to just copy src -> dst using GDI.


        // Put the DIB into a DC:

        IntPtr hWndDesktop = GetDesktopWindow();
        IntPtr hDCComp = GetDC(hWndDesktop);
        IntPtr hDCSrc = CreateCompatibleDC(hDCComp);
        ReleaseDC(hWndDesktop, hDCComp);
        IntPtr hBmpOld = SelectObject(hDCSrc, hDib);

        Graphics gfx = Graphics.FromImage(bm);
        IntPtr hDCDest = gfx.GetHdc();
        BitBlt(hDCDest, 0, 0, tBM.bmWidth, tBM.bmHeight, hDCSrc, 0, 0, SRCCOPY);
        gfx.ReleaseHdc(hDCDest);

        SelectObject(hDCSrc, hBmpOld);
        DeleteDC(hDCSrc);
    }

    return bm;

}

Using Steve's idea, I created an MFC DLL, loaded it with all the 32bppARGB bitmaps, and used his ResourceLibrary and ImageUtility classes to create GDI+ Bitmap objects.

    // Resource file created as MFC dll to store 32bpp

    // alpha channel bitmaps.

    string ResourceFileName = "TransButtonResources.dll";

    if (File.Exists(ResourceFileName))
    {
        using (ResourceLibrary lib = new ResourceLibrary())
        {
            lib.Filename = ResourceFileName;
            if (!lib.Handle.Equals(IntPtr.Zero))
            {
                // Load bitmaps from resource file with specified ids

                bgImage1 = LoadBitmapResource(lib, 2000);
                bgImage2 = LoadBitmapResource(lib, 2006);
                purpleButton  = LoadBitmapResource(lib, 2001);
                redButton = LoadBitmapResource(lib, 2002);
                whiteButton = LoadBitmapResource(lib, 2003);
                openButton = LoadBitmapResource(lib, 2004);
                saveButton = LoadBitmapResource(lib, 2005);
            }
        }
    }

The Button Class

My TransButton class here is nothing very fancy. I actually borrowed the control state code from the XP button example. I added functions to set the bitmap and region, and painted the correct image from a horizontal offset within the 4-state bitmap.

    if (ButtonImages != null)
    {
        Rectangle destRect = new Rectangle( 0, 0, 
            pea.ClipRectangle.Width, pea.ClipRectangle.Height);
        Rectangle srcRect = new Rectangle( xOffset, yOffset, 
            pea.ClipRectangle.Width, pea.ClipRectangle.Height);
        GraphicsUnit units = GraphicsUnit.Pixel;
        pea.Graphics.DrawImage(ButtonImages, destRect, srcRect, units);
    }

The very nice thing about GDI+ is that I no longer have to do pixel-by-pixel alpha-blending.

Using the Code

If you create a new project and use the classes here, remember that there is unmanaged code within these classes, so your project settings must be changed to allow unsafe code.

Follow these steps to create and display your transparent buttons:

  1. Create the buttons with 32-bit ARGB format.
  2. Create a resource DLL and import the bitmaps.
  3. Move the resource DLL to your .NET project directory, or provide a reference path.
  4. Create buttons on your .NET project's Form that have transparent background color.
  5. Change the buttons class type to TransButton.TransButton.
  6. Extract the bitmaps from DLL using ResourceLibrary class code.
  7. Set the Bitmap and Region of buttons after InitializeComponent.

That's about it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here