Click here to Skip to main content
15,868,141 members
Articles / Desktop Programming / MFC
Article

Transparent Button with Region and Anti-Aliased Edges

Rate me:
Please Sign up or sign in to vote.
4.83/5 (45 votes)
16 Aug 2004CPOL4 min read 282.3K   14.1K   168   38
Code for creating transparent, blended bitmapped buttons.

Sample Image - TransButtonDemo.jpg

Introduction

This is an example of how to create and use masked and semi-transparent bitmap buttons that can be overlaid on any background, including other bitmaps. The buttons are automatically alpha-blended (anti-aliased) with the background to create nice smooth edges, and provide region clipping to create various elliptical and other shapes.

Instead of creating a new class that duplicates all the other functionality of owner-draw buttons, the code to draw the transparency and create regions was added to the CHoverButtonEx class written by Niek Albers and modified by Frederick Ackers. If you are already using the CHoverButtonEx class, you can simply replace it with this version without affecting existing functionality, although I have also added a fourth image to the bitmaps for a disabled view, so you would need this image in your existing bitmaps, or you would need to change the references back to 3.

The demo project uses a dialog-based regioned window that displays a circular, patterned bitmap as the background. This type of bitmapped background shows how these buttons can take on a very stylistic look and feel. By pressing any of the four refresh buttons in the lower portion of the window, the background is changed to a different bitmap (there are six in all).

Additional thanks goes to Paul Nettle for his winDIB routines.

A Bit About Anti-Aliasing and Alpha-Blending

One of the trickiest parts of masking images on top of images is the rough edges, or pixelation, of the meeting places of these images when they are not straight edges. The solution to these rough edges is to blend the edges in a technique called anti-aliasing. In this demo, anti-aliasing is done using an alpha-blending technique, where the transparency of the foreground bitmap pixel determines the strength of color at any particular position on the display.

The formula for blending source and background pixels is:

displayColor = sourceColor × sourceAlpha / 255 + backgroundColor × (255 – sourceAlpha) / 255

where the alpha value range is 0 (transparent) to 255 (opaque). Therefore, any part of the button's bitmap that has an alpha value of 0 will not be seen, and areas with values greater than zero will be blended with the background at varying strengths up to 255 where the button's bitmap will be fully opaque.

// alphaBlend routine
static inline unsigned int alphaBlend(const unsigned 
                      int bg, const unsigned int src)
{
    unsigned int    a = src >> 24;    // sourceColor alpha

    // If source pixel is transparent, just return the background
    if (0 == a) return bg;

    // alpha-blend the src and bg colors
    unsigned int rb = (((src & 0x00ff00ff) * a) + 
          ((bg & 0x00ff00ff) * (0xff - a))) & 0xff00ff00;
    unsigned int    g  = (((src & 0x0000ff00) * a) + 
          ((bg & 0x0000ff00) * (0xff - a))) & 0x00ff0000;

    return (src & 0xff000000) | ((rb | g) >> 8);
}

Creating The Bitmaps

As explained, in order to blend the button bitmap with the background, the button bitmap must have an alpha value assigned to each pixel. This means that these bitmaps must be 32-bit-per-pixel (32bpp) images where the byte order is Alpha, Red, Green, Blue (ARGB).

One way to create this type of image is to use Adobe Photoshop. Create a new image with the color mode CMYK, making sure that the background color is set to black. Photoshop can automatically anti-alias edges for you against the black background. Save the file as a .RAW type, which only saves the actual bytes of the image and no header information. Unfortunately, the order of the .RAW image will be ABGR, so there is a bit reordering that must be done before blending, but this is no big deal.

Import the bitmaps into your project as a new resource type, "RAW". The bitmaps can now be loaded as resources for use on the buttons.

// This routine loads raw bitmap data from a resource.
// The width and height must be defined as there is no bitmap header
//
// Note that the resource type is "RAW" here, but can be changed to 
// any name, or passed as a parameter if desired
BOOL CHoverButtonEx::LoadRaw(UINT rawid, long nWidth, long nHeight)
{
    //If Bitmaps are being loaded and the BS_OWNERDRAW is not set
    // then reset style to OwnerDraw.
    UINT style = GetButtonStyle();
    if (!(style & BS_OWNERDRAW))
    {
        style |= BS_OWNERDRAW;
        SetButtonStyle(style);
    }

    m_pRaw = NULL;
    CString resName;
    resName.Format("#%d", rawid);
    HGLOBAL hRaw = LoadResource(AfxGetResourceHandle(), 
        FindResource(AfxGetResourceHandle(), resName, "RAW"));
    if (!hRaw)
        return FALSE;

    m_pRaw = (unsigned int*)LockResource(hRaw);

    if (!m_pRaw)
        return FALSE;

    m_nRawWidth = nWidth;
    m_nRawHeight = nHeight;

    // The bitmap should contain four images:
    // normal, selected, hover, and disabled.
    // The images must be the same size within
    // the bitmap, but can be laid out
    // horizontally or vertically.
    if (!m_bHorizontal)
    {
        m_ButtonSize.cy=nHeight/4;
        m_ButtonSize.cx=nWidth;
    }
    else
    {
        m_ButtonSize.cy=nHeight;
        m_ButtonSize.cx=nWidth/4;
    }

    SetWindowPos( NULL, 0,0, m_ButtonSize.cx, 
        m_ButtonSize.cy,SWP_NOMOVE | SWP_NOOWNERZORDER );

    return TRUE;
}

Using the code

Adding transparent bitmapped buttons is very easy to implement.

  1. Create any number of owner-draw buttons on your dialog or view.
  2. Include the HoverButton header in your dialog or view header where the buttons are declared.
    #include "HoverButton.h"
    .
    .
  3. Change the button types to CHoverButtonEx.
    CHoverButtonEx m_Btn1;
    CHoverButtonEx m_Btn2;
    CHoverButtonEx m_Btn3;
    
  4. Create a region for the buttons, and assign parameters.
    // Create round buttons
    HRGN r = CreateEllipticRgn(0, 0, 48, 48);
    
    m_Btn1.SetHorizontal(TRUE);
    m_Btn1.SetRegion(r);
    m_Btn1.LoadRaw(IDR_RED, 192, 48);
    m_Btn1.SetToolTipText("Press Me!");
    
    m_Btn2.SetHorizontal(TRUE);
    m_Btn2.SetRegion(r);
    m_Btn2.LoadRaw(IDR_PURPLE, 192, 48);
    m_Btn2.SetToolTipText("Press Me!");
    
    m_Btn3.SetHorizontal(TRUE);
    m_Btn3.SetRegion(r);
    m_Btn3.LoadRaw(IDR_PURPLE, 192, 48);
    m_Btn3.SetToolTipText("Press Me!");
    
    DeleteObject(r);
    
  5. If the background changes, simply call RefreshBkgrnd.
    m_Btn1.RefreshBkgrnd();
    m_Btn2.RefreshBkgrnd();
    m_Btn3.RefreshBkgrnd();
    

That's it!

Points of Interest

One of the interesting routines here is how to determine what the background looks like. I decided to read the background before the button is first displayed, and save that image until the button is refreshed.

// If this is the first time drawing the button, or the background
// has been refreshed, we need to "read" the background and save
// it for transparency mixing (aka alpha blending).
if (m_pRaw && m_nRawWidth && m_nRawHeight && !m_bLoadBkgrnd)
{
    unsigned int bkPix;
    COLORREF c;

    int bkWidth;
    int bkHeight;

    // Get the size of one image
    if (!m_bHorizontal)
    {
        bkWidth = m_nRawWidth;
        bkHeight = m_nRawHeight/4;
    }
    else
    {
        bkWidth = m_nRawWidth/4;
        bkHeight = m_nRawHeight;
    }

    if (m_pBkGrnd)
        delete m_pBkGrnd;

    // Create array to hold background pixels
    m_pBkGrnd = new unsigned int[bkWidth*bkHeight];

    if (!m_pBkGrnd)
        return;

    unsigned int* pBkGrnd = m_pBkGrnd;

    for (int iY = 0; iY < bkHeight; iY++)
    {
        for (int iX = 0; iX < bkWidth; iX++, pBkGrnd++)
        {
            // Get the background pixel
            c = mydc->GetPixel(iX, iY);
            bkPix = (unsigned int)c;
            bkPix |= 0xff000000;        // Set pixel opaque
            // Set correct order of RGB
            bkPix = (bkPix&0xff00ff00) | ((bkPix>>16)&0xff) 
                               | ((bkPix<<16)&0xff0000);

            // Save pixel in array
            *pBkGrnd = bkPix;
        }
    }

    m_bLoadBkgrnd = TRUE;
}

This method provides an accurate picture of the background, especially if the background has been stretched or tiled from the original image. Another method of determining the background might be to pass the background bitmap to the button class, and calculate the offsets, or simply pass a color reference if the background is not a bitmap. Any of these other methods might prove more efficient in selected cases.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
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

 
SuggestionLoad transparent bitmaps Pin
Resmonden4-May-12 3:18
Resmonden4-May-12 3:18 
QuestionHow to change the Region of the button Pin
sosozcm29-Nov-11 2:10
sosozcm29-Nov-11 2:10 
QuestionHow to load raw image at runtime Pin
MickeyJC17-Jul-08 2:57
MickeyJC17-Jul-08 2:57 
GeneralTransparent Button with Region and Anti-Aliased Edges Pin
CGroff8-Jul-08 1:05
CGroff8-Jul-08 1:05 
GeneralThanks a lot! Pin
Alex Cohn23-Oct-07 4:35
Alex Cohn23-Oct-07 4:35 
GeneralWhy not use straight png files [modified] Pin
Mark Hagers30-Aug-07 5:28
Mark Hagers30-Aug-07 5:28 
GeneralRe: Why not use straight png files [modified] Pin
sosozcm29-Nov-11 17:25
sosozcm29-Nov-11 17:25 
Generallittle bug Pin
Taulie11-Jul-07 23:59
Taulie11-Jul-07 23:59 
QuestionHow to create RAW ffrom PNG Pin
Sir.Costy19-Jun-07 22:06
Sir.Costy19-Jun-07 22:06 
AnswerRe: How to create RAW ffrom PNG Pin
Bob Carboni20-Jun-07 4:12
Bob Carboni20-Jun-07 4:12 
You can use PNG directly with GDI+. Look for other CodeProject articles using PNG. If you want to convert to RAW, it seems you will have to adjust transparency with alpha channel in PS to get the right levels. However, I cannot access your PNG example to test.
QuestionCan I use Bitmaps and not raw images? Pin
Sir.Costy30-May-07 22:46
Sir.Costy30-May-07 22:46 
QuestionHow can I change the image? Pin
Minkyu Ha7-Mar-07 14:37
Minkyu Ha7-Mar-07 14:37 
AnswerRe: How can I change the image? Pin
Bob Carboni8-Mar-07 12:52
Bob Carboni8-Mar-07 12:52 
QuestionWould you Please send me the bmp before raw? Pin
hongming66621-Feb-07 22:59
hongming66621-Feb-07 22:59 
AnswerRe: Would you Please send me the bmp before raw? Pin
Bob Carboni2-Feb-07 5:04
Bob Carboni2-Feb-07 5:04 
GeneralTransparency dosen't work with buttons on toolbars :( Pin
Sir.Costy15-Jan-07 5:27
Sir.Costy15-Jan-07 5:27 
GeneralRe: Transparency dosen't work with buttons on toolbars :( Pin
Bob Carboni30-Jan-07 13:42
Bob Carboni30-Jan-07 13:42 
QuestionAnti - aliased background ? Pin
Virtual Reality15-Dec-06 8:47
Virtual Reality15-Dec-06 8:47 
AnswerRe: Anti - aliased background ? Pin
Bob Carboni8-Mar-07 13:02
Bob Carboni8-Mar-07 13:02 
QuestionHow can I do that with VB.net? Pin
alexmbra17-May-06 6:05
alexmbra17-May-06 6:05 
AnswerRe: How can I do that with VB.net? Pin
Bob Carboni17-May-06 9:15
Bob Carboni17-May-06 9:15 
GeneralCMYK Black is different from RGB Black Pin
otterrrr11-Dec-05 8:48
otterrrr11-Dec-05 8:48 
GeneralNice but question on button response Pin
CosmoNova4-Sep-05 21:00
CosmoNova4-Sep-05 21:00 
GeneralDoes not work with window alway on top Pin
daniel_zy18-May-05 3:53
daniel_zy18-May-05 3:53 
QuestionHow to use it in WTL ? Pin
icer.L27-Feb-05 0:07
icer.L27-Feb-05 0:07 

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

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