Lapys
Lapys

Reputation: 946

Create a mask from a bitmap and transparency color - Windows GDI

Here’s the code I've spent the past two days trying to debug:

#include <windows.h>

HBITMAP createImageMask(HBITMAP bitmapHandle, const COLORREF transparencyColor) {
    // For getting information about the bitmap's height and width in this context
    BITMAP bitmap;

    // Create the device contexts for the bitmap and its mask
    HDC bitmapGraphicsDeviceContext = CreateCompatibleDC(NULL);
    HDC bitmapMaskGraphicsDeviceContext = CreateCompatibleDC(NULL);

    // For the device contexts to re-select the initial object they initialized with
    // and de-select the bitmap and mask
    HGDIOBJ bitmapDummyObject;
    HGDIOBJ bitmapMaskDummyObject;

    // The actual mask
    HBITMAP bitmapMaskHandle;

    // 1. Generate the mask.
    GetObject(bitmapHandle, sizeof(BITMAP), &bitmap);
    bitmapMaskHandle = CreateBitmap(bitmap.bmWidth, bitmap.bmHeight, 1, 1, NULL);

    // 2. Setup the device context for the mask (and the bitmap)
    //    — also get the initial selected objects in the device contexts.
    bitmapDummyObject = SelectObject(bitmapGraphicsDeviceContext, (HGDIOBJ) (HBITMAP) bitmapHandle);
    bitmapMaskDummyObject = SelectObject(bitmapMaskGraphicsDeviceContext, (HGDIOBJ) (HBITMAP) bitmapMaskHandle);

    // 3. Set the background color of the mask.
    SetBkColor(bitmapGraphicsDeviceContext, transparencyColor);

    // 4. Copy the bitmap to the mask and invert it so it blends with the background color.
    BitBlt(bitmapMaskGraphicsDeviceContext, 0, 0, bitmap.bmWidth, bitmap.bmHeight, bitmapGraphicsDeviceContext, 0, 0, SRCCOPY);
    BitBlt(bitmapGraphicsDeviceContext, 0, 0, bitmap.bmWidth, bitmap.bmHeight, bitmapMaskGraphicsDeviceContext, 0, 0, SRCINVERT);

    // 5. Select the bitmaps out before deleting the device contexts to avoid any issues.
    SelectObject(bitmapGraphicsDeviceContext, bitmapDummyObject);
    SelectObject(bitmapMaskGraphicsDeviceContext, bitmapMaskDummyObject);

    // Clean-up
    DeleteDC(bitmapGraphicsDeviceContext);
    DeleteDC(bitmapMaskGraphicsDeviceContext);

    // Voila!
    return bitmapMaskHandle;
}

It creates a bitmap handle (HBITMAP) and doesn't generate any errors (from the GetLastError function).


Problem: It doesn't generate a monochrome version of the bitmap I applied to it,
rather it just creates a bitmap that's only filled with black.

So what's up with the code, what am I doing wrong?
Or how do I properly create bitmap masks?

(I’m attempting to do this without GDI+ or other libraries, if possible)


Here’s the image where the transparency color is red (RGB(255, 0, 0)):

Here’s the image mask (expected result then actual result respectively (left-to-right)):


References here: theForger’s Win32 API Programming Tutorial - Transparent Bitmaps

Upvotes: 2

Views: 3509

Answers (1)

jwezorek
jwezorek

Reputation: 9535

This code works, although it is not doing exactly what you think it is doing. If you are not seeing any output, whatever the problem is is external to this one function.

What this code is doing is setting up two bitmaps for use by an old Win32 technique for painting a sprite in which you use two calls to BitBlt with different raster-operation codes, one painting a mask and one painting the sprite such that the sprite's background will not be painted. Note that it is both creating a mask and also altering the source bitmap. (That "const" in "const HBITMAP bitmapHandle" is not really doing anything. A bitmap handle is like a resource ID referring to a bitmap managed by Windows in a way that C++ knows nothing about. Making one const does not mean the bitmap to which it refers cannot be altered.) If you look at the code, the final BitBlit is blitting into the source bitmap, not the mask. What this call is doing is blacking out the key color in the source bitmap, which is what is required to paint a sprite using rop codes and two blits.

This technique by the way is an extremely old way of doing this, superceded by the introduction of MaskBlt into the API, which will do what you want in one call. But even further, MaskBlt at this point is out-dated. You probably want to paint sprites for a game or something game-like. Almost certainly What you actually want is to load PNGs with per-pixel alpha and paint them with alpha compositing. You can do this with GDI+ or with an open source graphics library such as FreeImage.

In any case below is minimal code that demonstrates this mask code actually works. Just change the following source such that "D:\test\hex_badge.bmp" is a path to wherever you have that hexagonal bitmap in your question.

#include <windows.h>

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

HBITMAP g_bmp;
HBITMAP g_bmpMask;

HBITMAP createImageMask( HBITMAP bitmapHandle, const COLORREF transparencyColor) {
    // For getting information about the bitmap's height and width in this context
    BITMAP bitmap;

    // Create the device contexts for the bitmap and its mask
    HDC bitmapGraphicsDeviceContext = CreateCompatibleDC(NULL);
    HDC bitmapMaskGraphicsDeviceContext = CreateCompatibleDC(NULL);

    // The actual mask
    HBITMAP bitmapMaskHandle;

    // 1. Generate the mask.
    GetObject(bitmapHandle, sizeof(BITMAP), &bitmap);
    bitmapMaskHandle = CreateBitmap(bitmap.bmWidth, bitmap.bmHeight, 1, 1, NULL);

    // 2. Setup the device context for the mask (and the bitmap).
    SelectObject(bitmapGraphicsDeviceContext, bitmapHandle);
    SelectObject(bitmapMaskGraphicsDeviceContext, bitmapMaskHandle);

    // 3. Set the background color of the mask.
    SetBkColor(bitmapGraphicsDeviceContext, transparencyColor);

    // 4. Copy the bitmap to the mask and invert it so it blends with the background color.
    BitBlt(bitmapMaskGraphicsDeviceContext, 0, 0, bitmap.bmWidth, bitmap.bmHeight, bitmapGraphicsDeviceContext, 0, 0, SRCCOPY);
    BitBlt(bitmapGraphicsDeviceContext, 0, 0, bitmap.bmWidth, bitmap.bmHeight, bitmapMaskGraphicsDeviceContext, 0, 0, SRCINVERT);

    // Clean-up
    DeleteDC(bitmapGraphicsDeviceContext);
    DeleteDC(bitmapMaskGraphicsDeviceContext);

    // Voila!
    return bitmapMaskHandle;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{

    MSG msg = { 0 };
    WNDCLASS wc = { 0 };
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
    wc.lpszClassName = L"minwindowsapp";

    g_bmp = (HBITMAP)LoadImage(hInstance, L"D:\\test\\hex_badge.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
    g_bmpMask = createImageMask(g_bmp, RGB(255, 0, 0));

    if (!RegisterClass(&wc))
        return 1;

    if (!CreateWindow(wc.lpszClassName,
        L"Minimal Windows Application",
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        0, 0, 640, 480, 0, 0, hInstance, NULL))
        return 2;

    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return 0;
}

LRESULT HandleWmPaint(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;

    HDC hdcScr = GetDC(NULL);
    HDC hdcBmp = CreateCompatibleDC(hdcScr);
    HBITMAP hbmOld = (HBITMAP)SelectObject(hdcBmp, g_bmp);

    HDC hdcMask = CreateCompatibleDC(hdcScr);
    HBITMAP hbmOldMask = (HBITMAP) SelectObject(hdcMask, g_bmpMask );

    HDC hdc = BeginPaint(hWnd, &ps);
    BitBlt(hdc, 0, 0, 184, 184, hdcMask, 0, 0, SRCCOPY);
    BitBlt(hdc, 184, 0, 184, 184, hdcBmp, 0, 0, SRCCOPY);
    EndPaint(hWnd, &ps);

    SelectObject(hdcMask, hbmOldMask);
    DeleteDC(hdcMask);

    SelectObject(hdcBmp, hbmOld);
    DeleteDC(hdcBmp);
    ReleaseDC(NULL, hdcScr);

    return 0;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{

    switch (message)
    {
    case WM_CLOSE:
        PostQuitMessage(0);
        break;

    case WM_PAINT:
         return HandleWmPaint(hWnd, wParam, lParam);

    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;

output is as below:

enter image description here

I'm not sure why exactly you are not getting output but most likely you are either not successfully loading the bitmap or you are not successfully painting to the screen.

Upvotes: 4

Related Questions