GDI DC 中的透明度

Transparency in GDI DCs

我的 "simple" 目标是在屏幕上绘制一个具有一定透明度的位图。这一点并不难:

#include <windows.h>
#include "BBKG.h"

HINSTANCE hInst;
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
static int wH = 156;
static int wW = 166;

HBITMAP CreateBitmapMask(HBITMAP hbmColour, COLORREF crTransparent)
{
    HDC mem0, mem1;
    HBITMAP hbmMask;
    BITMAP bm;
    GetObject(hbmColour, sizeof(BITMAP), &bm);
    hbmMask = CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL);
    mem0 = CreateCompatibleDC(0);
    mem1 = CreateCompatibleDC(0);
    SelectObject(mem0, hbmColour);
    SelectObject(mem1, hbmMask);
    SetBkColor(mem0, crTransparent);
    BitBlt(mem1, 0, 0, bm.bmWidth, bm.bmHeight, mem0, 0, 0, SRCCOPY);
    BitBlt(mem0, 0, 0, bm.bmWidth, bm.bmHeight, mem1, 0, 0, SRCINVERT);
    DeleteDC(mem0);
    DeleteDC(mem1);

    return hbmMask;
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
    hInst = hInstance;
    MSG  msg;
    HWND hwnd;
    WNDCLASSW wc;

    wc.style = 0;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.lpszClassName = L"nope";
    wc.hInstance = hInst;
    wc.hbrBackground = NULL;
    wc.lpszMenuName = NULL;
    wc.lpfnWndProc = WndProc;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

    RegisterClassW(&wc);
    hwnd = CreateWindowW(wc.lpszClassName, L"",
        WS_VISIBLE | WS_POPUP|  WS_EX_TRANSPARENT,
        100, 100, wW, wH, NULL, NULL, hInst, NULL);

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0)) {
        //Workaround for focusables stealing my Esc key
        if (msg.message == WM_KEYDOWN){
            if (msg.wParam == VK_ESCAPE) {
                SendMessage(hwnd, WM_CLOSE, 0, 0);
            }
        }
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
    WPARAM wParam, LPARAM lParam)
{
    static int px;
    static int py;
    static HBITMAP bhbm;
    static RECT nRect = { 0, 0, wW, wH };

    switch (msg)
    {
    case WM_CREATE:
    {
        HWND bb = CreateWindowW(L"STATIC", L"",
            WS_VISIBLE | WS_CHILD ,
            0, 0, wW, wH,
            hwnd, (HMENU)11, hInst, NULL);
        //SetTimer(hwnd, 1, 80, NULL);

        return 0;
    }
    case WM_PAINT: {
        //Vars
        RECT wRect;
        if (GetUpdateRect(hwnd, &wRect, FALSE) == 0) {
            return 0; //Nothing to paint
        }
        PAINTSTRUCT gps;
        PAINTSTRUCT ps;
        BeginPaint(hwnd, &gps);
        HWND bb = GetDlgItem(hwnd, 11);
        HDC bbhdc = BeginPaint(bb, &ps);
        HDC mdc = CreateCompatibleDC(bbhdc);

        //Load Image
        BITMAP pBM;
        HBITMAP pHBM = (HBITMAP)LoadImage(NULL, L"twi00.bmp", 0, 0, 0, LR_LOADFROMFILE);
        HBITMAP pMBM = CreateBitmapMask((HBITMAP)pHBM, 0x00000000);
        GetObject(pHBM, sizeof(pBM), &pBM);

        //Paint
        HBITMAP oldBM = (HBITMAP)SelectObject(mdc, pMBM);
        BitBlt(bbhdc, 0, 0, pBM.bmWidth, pBM.bmHeight, mdc, 0, 0, SRCAND);
        SelectObject(mdc, pHBM);
        BitBlt(bbhdc, 0, 0, pBM.bmWidth, pBM.bmHeight, mdc, 0, 0, SRCPAINT);

        //Cleanup
        SelectObject(mdc, oldBM);
        DeleteObject(pHBM);
        DeleteObject(pMBM);
        DeleteDC(mdc);
        EndPaint(bb, &ps);
        EndPaint(hwnd, &gps);
        return 1;
    }
    case WM_ERASEBKGND: {
        return 0;
    }
    case WM_DESTROY:
    {
        DeleteObject(bhbm);
        PostQuitMessage(0);
        return 0;
    }
    case WM_LBUTTONDOWN:
        SetCapture(hwnd);
        px = LOWORD(lParam);
        py = HIWORD(lParam);
        return 1;
    case WM_LBUTTONUP:
    {
        ReleaseCapture();
        return 1;
    }
    case WM_MOUSEMOVE:
    {
        if (GetCapture() == hwnd)
        {
            RECT rcWindow;
            GetWindowRect(hwnd, &rcWindow);
            SetWindowPos(hwnd, NULL, rcWindow.left + LOWORD(lParam) - px, rcWindow.top + HIWORD(lParam) - py, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
        }
        break;
    }
    }
    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

使用任何带有黑色边框的通用 bmp 都可以,我使用了这个:

现在的问题是,当我移动 window (click/drag) 时,我怎样才能使背景更新?我希望将位图放入透明 window 中,以便它覆盖在事物之上,但它似乎只是抓住它背后的像素。

如果可能的话,我正在尝试在没有 GDI+ 或其他库的情况下执行此操作。

CreateWindow() 不接受 extended window 样式,例如 WS_EX_TRANSPARENT (这就是为什么它有 EX其名称)。您必须改用 CreateWindowEx()

hwnd = CreateWindowExW(WS_EX_TRANSPARENT,
    wc.lpszClassName, L"",
    WS_VISIBLE | WS_POPUP,
    100, 100, wW, wH, NULL, NULL, hInst, NULL);

更好的选择是创建一个 layered window (see also this) by using the WS_EX_LAYERED extended style). Then you can use the UpdateLayeredWindow() 函数来为 window 提供位图和透明颜色(您也可以指定 alpha)。让 window 为您管理透明绘制位图的所有艰苦工作。

您的 WndProc() 也可以响应 WM_NCHITTEST 消息,告诉 OS 所有对 window 的点击都应该被视为用户点击window 的标题栏。让 window 为您处理鼠标跟踪和 auto-positioning。

试试像这样的东西:

#include <windows.h>

HINSTANCE hInst;
static int wH = 156;
static int wW = 166;

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
    hInst = hInstance;

    WNDCLASSW wc = {0};    
    wc.lpszClassName = L"nope";
    wc.hInstance = hInst;
    wc.lpfnWndProc = WndProc;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

    RegisterClassW(&wc);
    HWND hwnd = CreateWindowEx(WS_EX_LAYERED,
        wc.lpszClassName, L"",
        WS_POPUP, 100, 100, wW, wH, NULL, NULL,
        hInst, NULL);

    HBITMAP hBmp = (HBITMAP) LoadImage(NULL, L"twi00.bmp", 0, 0, 0, LR_LOADFROMFILE);
    HDC hdcScreen = GetDC(0);
    HDC hdcBmp = CreateCompatibleDC(hdcScreen);
    HBITMAP oldBM = (HBITMAP) SelectObject(hdcBmp, hBmp);

    POINT pt = {0};
    UpdateLayeredWindow(hwnd,
        hdcScreen,
        NULL, NULL,
        hdcBmp, &pt,
        RGB(0, 0, 0), // black
        NULL, ULW_COLORKEY
    );

    SelectObject(hdcBmp, oldBM);
    DeleteDC(hdcBmp);
    ReleaseDC(0, hdcScreen);
    DeleteObject(hBmp);

    ShowWindow(hwnd, nCmdShow);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        //Workaround for focusables stealing my Esc key
        if ((msg.message == WM_KEYDOWN) && (msg.wParam == VK_ESCAPE) {
            SendMessage(hwnd, WM_CLOSE, 0, 0);
        }
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
    WPARAM wParam, LPARAM lParam)
{    
    switch (msg)
    {
        case WM_DESTROY:
        {
            PostQuitMessage(0);
            return 0;
        }

        case WM_NCHITTEST:
        {
            return HTCAPTION;
        }
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}