从位图和透明度颜色创建蒙版 - Windows GDI

Posted

技术标签:

【中文标题】从位图和透明度颜色创建蒙版 - Windows GDI【英文标题】:Create a mask from a bitmap and transparency color - Windows GDI 【发布时间】:2020-04-12 19:15:33 【问题描述】:

这是我过去两天尝试调试的代码:

#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;

它会创建一个位图句柄 (HBITMAP),并且不会产生任何错误(来自 GetLastError 函数)。


问题:它不会生成我应用到它的位图的单色版本, 而是创建一个仅填充黑色的位图。

那么代码是怎么回事,我做错了什么? 或者如何正确创建位图蒙版?

(如果可能,我正在尝试不使用 GDI+ 或其他库)


这是透明颜色为红色的图像 (RGB(255, 0, 0)):

这是图像掩码(预期结果和实际结果分别(从左到右)):


此处参考: theForger’s Win32 API Programming Tutorial - Transparent Bitmaps

【问题讨论】:

看来代码来自winprog.org/tutorial/transparency.html。您是否确保“调用此函数时未将位图选择到另一个 HDC”? @GSerg:在我的代码中,位图是从一个文件中加载的,没有引用任何 HDC。然后在掩码之前使用函数createImageMask 创建掩码位图,然后将位图选择到 HDC 中。 bitmapMaskHandle 在您删除该设备上下文时仍被选中到 bitmapMaskGraphicsDeviceContext。我认为现代版本的 GDI 已经对此非常宽容,但从技术上讲,它违反了 API。您应该在删除 DC 之前选择它。另外,向我们展示绘画代码。您的蒙版是单色位图,尝试显示时很容易出错。 @AdrianMcCarthy:通过“在删除之前选择它退出...”你是指SelectObject(hdc, myMask)然后SelectObject(hdc, someDummyObject)吗? SelectObject 返回旧对象。保存它,然后在完成后重新选择它。 【参考方案1】:

此代码有效,尽管它并没有完全按照您的想法执行。如果您没有看到任何输出,则无论问题出在这一功能之外。

这段代码所做的是设置两个位图供旧的 Win32 技术使用来绘制精灵,在该技术中,您使用不同的光栅操作代码对 BitBlt 进行两次调用,一个绘制蒙版,一个绘制精灵,这样不会绘制精灵的背景。请注意,它既创建了掩码,也更改了源位图。 (“const HBITMAP bitmapHandle”中的“const”实际上并没有做任何事情。位图句柄就像一个资源 ID,指的是由 Windows 管理的位图,而 C++ 对此一无所知。制作一个 const 并不意味着位图它所指的不能被改变。)如果你看一下代码,最终的 BitBlit 会进入源位图,而不是掩码。此调用的作用是将源位图中的关键颜色涂黑,这是使用 rop 代码和两个 blit 绘制 sprite 所必需的。

顺便说一句,这种技术是一种非常古老的方法,被在 API 中引入 MaskBlt 所取代,这将在一次调用中完成您想要的操作。但更进一步,MaskBlt 在这一点上已经过时了。您可能想为游戏或类似游戏的东西绘制精灵。几乎可以肯定,您真正想要的是使用每像素 alpha 加载 PNG 并使用 alpha 合成来绘制它们。您可以使用GDI+ 或FreeImage 等开源图形库来完成此操作。

在任何情况下,以下是演示此掩码代码实际工作的最少代码。只需更改以下来源,使“D:\test\hex_badge.bmp”成为您问题中具有该六角位图的任何位置的路径。

#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;

输出如下:

我不确定您到底为什么没有得到输出,但很可能您没有成功加载位图,或者您没有成功地在屏幕上绘画。

【讨论】:

出于挫败感,我实际上复制并粘贴了您的答案中的示例代码,但结果仍然与以前相同(我认为我的 Windows 坏了) 我会尝试另一种方法,感谢@jwezorek 的帮助。 其实我很久以前写过一篇关于使用 FreeImage 来做这类事情的博文。 jwezorek.com/2012/02/blitting-with-per-pixel-in-alpha-in-win32 立即查看

以上是关于从位图和透明度颜色创建蒙版 - Windows GDI的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的图片加上图层蒙版后,用画笔擦出半透明效果时候,擦出来的都是黑白的小方格,怎么才能擦出白的

如何在处理中加载的 png 图像上创建颜色剪贴蒙版?

画透明位图

一天学会PS抠图之蒙版抠图工具

将位图 (bmp) 转换为具有透明度的 png (Windows c++)

使用numpy蒙版数组和imshow绘制分段彩色图像