Click here to Skip to main content
Click here to Skip to main content

Transparent Bitmap Buttons with Alpha-Blended Regions in .NET

, 12 Aug 2004
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Bob Carboni
Web Developer
United States United States
Software architect and developer with over 20 years of experience, specializing in GUI designs and intranet systems in MFC/C++ and ASP.Net using C#.

Comments and Discussions

 
GeneralPhotoshop PinsussAnonymous22-Jun-05 12:46 
Generalbug fixing PinmemberAli Demirkaya4-Apr-05 3:46 
GeneralRe: bug fixing PinsussAnonymous23-Jun-05 4:12 
GeneralRe: bug fixing PinmemberLu Tai Phong16-Mar-10 15:56 
QuestionTried PNGs? Pinmemberip25518-Aug-04 13:29 
AnswerRe: Tried PNGs? PinmemberBillWoodruff18-Aug-04 13:47 
GeneralRe: Tried PNGs? Pinmemberip25519-Aug-04 3:02 
GeneralRe: Tried PNGs? Pinmemberradialronnie4-Apr-08 12:52 
AnswerRe: Tried PNGs? - Issues with PNGs PinmemberBob Carboni18-Aug-04 16:04 
GeneralRe: Tried PNGs? - Issues with PNGs PinmemberMathew Hall18-Aug-04 18:34 
GeneralRe: Tried PNGs? - Issues with PNGs PinmemberBob Carboni19-Aug-04 4:41 
GeneralRe: Tried PNGs? - Issues with PNGs PinmemberMathew Hall19-Aug-04 22:59 
GeneralRe: Tried PNGs? - Issues with PNGs Pinmemberip25519-Aug-04 3:33 
GeneralRe: Tried PNGs? - Issues with PNGs PinmemberBob Carboni19-Aug-04 4:56 
AnswerRe: Tried PNGs? PinmemberMathew Hall18-Aug-04 18:43 
GeneralRe: Tried PNGs? PinmemberSk8tz30-Jan-05 20:09 
GeneralRe: Tried PNGs? PinmemberMathew Hall17-Feb-05 2:10 
AnswerRe: Tried PNGs? Pinmembersnarfblam27-Sep-06 12:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140814.1 | Last Updated 13 Aug 2004
Article Copyright 2004 by Bob Carboni
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid