C++ – HBITMAP adding transparency / alpha channel

cgdi+hbitmapwinapi

I'm trying to add transparency to a hbitmap object but it never draw anything :/

this is the code i use to draw the handle

HDC hdcMem = CreateCompatibleDC(hDC);
    HBITMAP hbmOld = (HBITMAP) SelectObject(hdcMem, m_hBitmap);

    BLENDFUNCTION blender = {AC_SRC_OVER, 0, (int) (2.55 * 100), AC_SRC_ALPHA}; // blend function combines opacity and pixel based transparency
    AlphaBlend(hDC, x, y, rect.right - rect.left, rect.bottom - rect.top, hdcMem, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, blender);

    SelectObject(hdcMem, hbmOld);
    DeleteDC(hdcMem);

and this is the code which should add a alpha channel to the hbitmap

BITMAPINFOHEADER bminfoheader;
    ::ZeroMemory(&bminfoheader, sizeof(BITMAPINFOHEADER));
    bminfoheader.biSize        = sizeof(BITMAPINFOHEADER);
    bminfoheader.biWidth       = m_ResX;
    bminfoheader.biHeight      = m_ResY;
    bminfoheader.biPlanes      = 1;
    bminfoheader.biBitCount    = 32;
    bminfoheader.biCompression = BI_RGB;

    HDC windowDC = CreateCompatibleDC(0);
    unsigned char* pPixels = new unsigned char[m_ResX * m_ResY * 4];

    GetDIBits(windowDC, m_hBitmap, 0, m_ResY, pPixels, (BITMAPINFO*) &bminfoheader, DIB_RGB_COLORS); // load pixel info

    // add alpha channel values of 255 for every pixel if bmp
    for (int count = 0; count < m_ResX * m_ResY; count++)
    {
        pPixels[count * 4 + 3] = 255; <----  here i've tried to change the value to test different transparency, but it doesn't change anything
    }
    SetDIBits(windowDC, m_hBitmap, 0, GetHeight(), pPixels, (BITMAPINFO*) &bminfoheader, DIB_RGB_COLORS); // save the pixel info for later manipulation

    DeleteDC(windowDC);

edit:

this is the code how I create the bitmap
I fill the pixeldata in later in some code

m_hBuffer = CreateBitmap( m_ResX, m_ResY, 1, 32, nullptr );

Best Answer

This is a fun one!

Guess what this prints out?

#include <stdio.h>

int main()
{
    printf("%d\n", (int) (2.55 * 100));
    return 0;
}

Answer: 254 - not 255. Two things happening here:

  • Floats are often inexact - that 2.55 doesn't get represented by a binary value that represents 2.55 exactly - it's probably something like 2.5499963... (I just made that up, but you get the idea - essentially the number is represented as a sum of fractions of 2 - since binary is base 2 - so something like .5 or .25 can likely be represented exactly, but most other numbers will be represented as an approximation. You typically don't notice this because float print routines will convert back to base 10 for display, which essentially introduces another inexactness that ends up cancelling out the first one: so what you see as the assigned value or the printed out value are not exactly the value of the representation as stored in memory).

  • Casting to int truncates - ie rounds down.

Put these together, and your (2.55 * 100) is getting you 254, not 255 - and you have to have the magic value 255 for per-pixel alpha to work.

So lesson here is stick with pure integers. Or, if you ever do need to convert from float to integers, be aware of what's going on, and work around it (eg. add .5, then truncate - or use a similar technique.)

By the way, this is one of those cases where stepping through code line by line and checking all inputs and outputs at each step (you can never be too paranoid when debugging!) might have shown the issue; right before you step into AlphaBlend, you should see a 254 when you hover over that param (assuming using DevStudio or similar editor) and realize that something's up.

Related Topic