How to draw 32-bit alpha channel bitmaps?
Asked Answered
P

6

13

I need to create a custom control to display bmp images with alpha channel. The background can be painted in different colors and the images have shadows so I need to truly "paint" the alpha channel.

Does anybody know how to do it?

I also want if possible to create a mask using the alpha channel information to know whether the mouse has been click on the image or on the transparent area.

Any kind of help will be appreciated!

Thanks.

Edited(JDePedro): As some of you have suggested I've been trying to use alpha blend to paint the bitmap with alpha channel. This just a test I've implemented where I load a 32-bit bitmap from resources and I try to paint it using AlphaBlend function:

void CAlphaDlg::OnPaint()
{
    CClientDC dc(this);
    CDC  dcMem;
    dcMem.CreateCompatibleDC(&dc);

    CBitmap bitmap;
    bitmap.LoadBitmap(IDB_BITMAP);

    BITMAP BitMap;
    bitmap.GetBitmap(&BitMap);
    int nWidth = BitMap.bmWidth;
    int nHeight = BitMap.bmHeight;
    CBitmap *pOldBitmap = dcMem.SelectObject(&bitmap);

    BLENDFUNCTION m_bf;
    m_bf.BlendOp = AC_SRC_OVER;
    m_bf.BlendFlags = 0;
    m_bf.SourceConstantAlpha = 255;
    m_bf.AlphaFormat = AC_SRC_ALPHA;
    AlphaBlend(dc.GetSafeHdc(), 100, 100, nWidth, nHeight, dcMem.GetSafeHdc(), 0, 0,nWidth, nHeight,m_bf); 

    dcMem.SelectObject(pOldBitmap);

    CDialog::OnPaint();
}

This is just a test so I put the code in the OnPaint of the dialog (I also tried the AlphaBlend function of the CDC object).

The non-transparent areas are being painted correctly but I get white where the bitmap should be transparent.

Any help???

This is a screenshot..it's not easy to see but there is a white rectangle around the blue circle: alt text http://img385.imageshack.us/img385/7965/alphamh8.png

Ok. I got it! I have to pre-multiply every pixel for the alpha value. Someone can suggest the optimized way to do that?

Plenum answered 20/11, 2008 at 23:59 Comment(0)
S
7

The way I usually do this is via a DIBSection - a device independent bitmap that you can modify the pixels of directly. Unfortunately there isn't any MFC support for DIBSections: you have to use the Win32 function CreateDIBSection() to use it.

Start by loading the bitmap as 32-bit RGBA (that is, four bytes per pixel: one red, one green, one blue and one for the alpha channel). In the control, create a suitably sized DIBSection. Then, in the paint routine

  • Copy the bitmap data into the DIBSection's bitmap data, using the alpha channel byte to blend the bitmap image with the background colour.
  • Create a device context and select the DIBSection into it.
  • Use BitBlt() to copy from the new device context to the paint device context.

You can create a mask given the raw bitmap data simply by looking at the alpha channel values - I'm not sure what you're asking here.

Secondbest answered 21/11, 2008 at 18:6 Comment(0)
P
13

For future google users, here is a working pre-multiply function. Note that this was taken from http://www.viksoe.dk/code/alphatut1.htm .

inline void PremultiplyBitmapAlpha(HDC hDC, HBITMAP hBmp)
{
   BITMAP bm = { 0 };
   GetObject(hBmp, sizeof(bm), &bm);
   BITMAPINFO* bmi = (BITMAPINFO*) _alloca(sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD)));
   ::ZeroMemory(bmi, sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD)));
   bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
   BOOL bRes = ::GetDIBits(hDC, hBmp, 0, bm.bmHeight, NULL, bmi, DIB_RGB_COLORS);
   if( !bRes || bmi->bmiHeader.biBitCount != 32 ) return;
   LPBYTE pBitData = (LPBYTE) ::LocalAlloc(LPTR, bm.bmWidth * bm.bmHeight * sizeof(DWORD));
   if( pBitData == NULL ) return;
   LPBYTE pData = pBitData;
   ::GetDIBits(hDC, hBmp, 0, bm.bmHeight, pData, bmi, DIB_RGB_COLORS);
   for( int y = 0; y < bm.bmHeight; y++ ) {
      for( int x = 0; x < bm.bmWidth; x++ ) {
         pData[0] = (BYTE)((DWORD)pData[0] * pData[3] / 255);
         pData[1] = (BYTE)((DWORD)pData[1] * pData[3] / 255);
         pData[2] = (BYTE)((DWORD)pData[2] * pData[3] / 255);
         pData += 4;
      }
   }
   ::SetDIBits(hDC, hBmp, 0, bm.bmHeight, pBitData, bmi, DIB_RGB_COLORS);
   ::LocalFree(pBitData);
}

So then your OnPaint becomes:

void MyButton::OnPaint()
{
    CPaintDC dc(this);

    CRect rect(0, 0, 16, 16);

    static bool pmdone = false;
    if (!pmdone) {
        PremultiplyBitmapAlpha(dc, m_Image);
        pmdone = true;
    }

    BLENDFUNCTION bf;
    bf.BlendOp = AC_SRC_OVER;
    bf.BlendFlags = 0;
    bf.SourceConstantAlpha = 255;
    bf.AlphaFormat = AC_SRC_ALPHA;

    HDC src_dc = m_Image.GetDC();
    ::AlphaBlend(dc, rect.left, rect.top, 16, 16, src_dc, 0, 0, 16, 16, bf);
    m_Image.ReleaseDC();
}

And the loading of the image (in the constructor of your control):

if ((HBITMAP)m_Image == NULL) {
    m_Image.LoadFromResource(::AfxGetResourceHandle(), IDB_RESOURCE_OF_32_BPP_BITMAP);
}
Posology answered 19/6, 2013 at 8:55 Comment(3)
What if I want the transparent image to appear on a static label? pStatic->ModifyStyle(0xF, SS_BITMAP); pStatic->SetBitmap(bitmap);Ollie
A static variable in OnPaint() is surely a very bad idea. If you have more than one button this will fail. pmdone should be a member variable of each MyButton. The BLENDFUNCTION however could be static. Or better: You could call PremultiplyBitmapAlpha() immediately after LoadFromResource() and use the DC of any window with GetWindowDC(). Then you don't need the variable pmdone anymore.Shetler
m_Image should be static too, obviously. No need to load the same bitmap for each button. And no you cannot call it after the LoadFromResource() (at least not in the way it's presented here) because you might be instantiating these objects before you have any windows created.Posology
S
7

The way I usually do this is via a DIBSection - a device independent bitmap that you can modify the pixels of directly. Unfortunately there isn't any MFC support for DIBSections: you have to use the Win32 function CreateDIBSection() to use it.

Start by loading the bitmap as 32-bit RGBA (that is, four bytes per pixel: one red, one green, one blue and one for the alpha channel). In the control, create a suitably sized DIBSection. Then, in the paint routine

  • Copy the bitmap data into the DIBSection's bitmap data, using the alpha channel byte to blend the bitmap image with the background colour.
  • Create a device context and select the DIBSection into it.
  • Use BitBlt() to copy from the new device context to the paint device context.

You can create a mask given the raw bitmap data simply by looking at the alpha channel values - I'm not sure what you're asking here.

Secondbest answered 21/11, 2008 at 18:6 Comment(0)
H
1

You need to do an alpha blend with your background color, then take out the alpha channel to paint it to the control.

The alpha channel should just be every 4th byte of your image. You can use that directly for your mask, or you can just copy every 4th byte to a new mask image.

Hague answered 21/11, 2008 at 0:13 Comment(2)
Hi Mark, let's see if I understand your suggestion. Imagine I derive my control from CStatic/CWnd and I add a CBitmap as a member. Then every time the control is going to be painted (in the OnPaint method) I have to get the background and make the blend with my image?Plenum
(continue) Am I right? Do I have to do everythin by myself? It seems as it can have a high performance cost...could you (anybody) tell me the best way (more optimum) to do it? Maybe some godd articles or even codesnippet? Thanks.Plenum
P
1

Painting it is very easy with the AlphaBlend function.

As for you mask, you'll need to get the bits of the bitmap and examine the alpha channel byte for each pixel you're interested in.

Pearce answered 21/11, 2008 at 23:39 Comment(0)
B
1

An optimised way to pre-multiply the RGB channels with the alpha channel is to set up a [256][256] array containing the calculated multiplication results. The first dimension is the alpha value, the second is the R/G/B value, the values in the array are the pre-multiplied values you need.

With this array set up correctly, you can calculate the value you need like this:

R = multiplicationLookup[alpha][R];
G = multiplicationLookup[alpha][G];
B = multiplicationLookup[alpha][B];
Bork answered 2/12, 2008 at 10:15 Comment(1)
Doubtful, whether random accesses into a 64 KiB large array are faster than performing the calculation. Especially when the operation can be so perfectly spread across a vector unit. It certainly puts undue stress on the CPU caches, for very little (if any) in return.Exponent
S
1

You are on the right track, but need to fix two things.

First use ::LoadImage( .. LR_CREATEDIBSECTION ..) instead of CBitmap::LoadBitmap. Two, you have to "pre-multiply" RGB values of every pixel in a bitmap to their respective A value. This is a requirement of AlphaBlend function, see AlphaFormat description on this MSDN page. T

The lpng has a working code that does the premultiplication of the DIB data.

Stop answered 19/1, 2009 at 7:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.