V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
koomox
V2EX  ›  程序员

C++屏幕水印实现遇到问题

  •  
  •   koomox · 2023-09-26 07:04:48 +08:00 · 1427 次点击
    这是一个创建于 428 天前的主题,其中的信息可能已经有所发展或是发生改变。

    C++用得不多,并且对 GDIPLUS 也不是很了解。想实现一个屏幕水印得工具,下面得代码运行后,Graphics 创建得对象背景非透明,并且程序无法正常关闭退出。大佬们帮忙看看,指定迷津

    代码如下:

    #include <Windows.h>
    #include <gdiplus.h>
    #include <string>
    
    using namespace Gdiplus;
    #pragma comment(lib, "gdiplus.lib")
    
    const std::wstring watermarkText = L"Your Watermark Text";
    const int watermarkFontSize = 38;
    const int watermarkSpacing = 100;
    
    void DrawWatermark(HDC hdc, int windowWidth, int windowHeight)
    {
        // 初始化 GDI+
        GdiplusStartupInput gdiplusStartupInput;
        ULONG_PTR gdiplusToken;
        GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
        SetBkMode(hdc, TRANSPARENT);
    
        // 创建 Graphics 对象
        Graphics graphics(hdc);
    
        
    
        // 创建字体
        FontFamily fontFamily(L"Arial");
        Font font(&fontFamily, watermarkFontSize, FontStyleRegular, UnitPixel);
    
        // 设置文本颜色
        SolidBrush textBrush(Color(255, 0, 0, 0)); // 文本颜色为黑色
    
        // 获取文本尺寸
        RectF layoutRect;
        graphics.MeasureString(watermarkText.c_str(), -1, &font, PointF(0, 0), &layoutRect);
    
        // 计算水印文本块的总数以填满整个屏幕
        int numBlocksX = (windowWidth + watermarkSpacing) / (static_cast<int>(layoutRect.Width) + watermarkSpacing);
        int numBlocksY = (windowHeight + watermarkSpacing) / (static_cast<int>(layoutRect.Height) + watermarkSpacing);
    
        // 计算实际的间距
        int actualSpacingX = (windowWidth - numBlocksX * static_cast<int>(layoutRect.Width)) / (numBlocksX - 1);
        int actualSpacingY = (windowHeight - numBlocksY * static_cast<int>(layoutRect.Height)) / (numBlocksY - 1);
    
        // 保存当前的世界变换矩阵
        Matrix oldTransform;
        graphics.GetTransform(&oldTransform);
    
        // 绘制水印文本块
        for (int y = 0; y < numBlocksY; y++) {
            for (int x = 0; x < numBlocksX; x++) {
                int textX = x * (static_cast<int>(layoutRect.Width) + actualSpacingX);
                int textY = y * (static_cast<int>(layoutRect.Height) + actualSpacingY);
    
                // 移动 Graphics 对象到文本块位置
                graphics.ResetTransform();
                graphics.TranslateTransform(static_cast<float>(textX), static_cast<float>(textY));
                graphics.RotateTransform(-45.0f);
    
                // 绘制水印文本
                graphics.DrawString(watermarkText.c_str(), -1, &font, PointF(0, 0), &textBrush);
    
                // 恢复原始的世界变换矩阵
                graphics.SetTransform(&oldTransform);
            }
        }
    
        // 关闭 GDI+
        GdiplusShutdown(gdiplusToken);
    }
    
    LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        switch (message)
        {
        case WM_CREATE:
        {
            // 设置窗口样式为 WS_EX_LAYERED
            SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED | WS_EX_TRANSPARENT);
    
            // 设置窗口为完全透明
            SetLayeredWindowAttributes(hwnd, RGB(0, 0, 0), 0, LWA_COLORKEY);
    
            // 设置窗口大小为屏幕大小
            int windowWidth = GetSystemMetrics(SM_CXSCREEN);
            int windowHeight = GetSystemMetrics(SM_CYSCREEN);
            SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, windowWidth, windowHeight, SWP_SHOWWINDOW);
    
            break;
        }
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
            int windowWidth = GetSystemMetrics(SM_CXSCREEN);
            int windowHeight = GetSystemMetrics(SM_CYSCREEN);
    
            // 绘制水印
            DrawWatermark(hdc, windowWidth, windowHeight);
            EndPaint(hwnd, &ps);
    
            break;
        }
        case WM_DESTROY:
        {
            PostQuitMessage(0);
            break;
        }
        default:
            return DefWindowProc(hwnd, message, wParam, lParam);
        }
    
        return 0;
    }
    
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
    {
        WNDCLASSEX wcex;
        ZeroMemory(&wcex, sizeof(WNDCLASSEX));
        wcex.cbSize = sizeof(WNDCLASSEX);
        wcex.lpfnWndProc = WndProc;
        wcex.hInstance = hInstance;
        wcex.lpszClassName = L"WatermarkWindowClass";
        RegisterClassEx(&wcex);
    
        HWND hwnd = CreateWindow(L"WatermarkWindowClass", L"", WS_POPUP, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), NULL, NULL, hInstance, NULL);
    
        if (hwnd == NULL)
        {
            return 0;
        }
    
        ShowWindow(hwnd, nCmdShow);
    
        MSG msg;
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    
        return static_cast<int>(msg.wParam);
    }
    
    
    6 条回复    2023-09-26 23:18:32 +08:00
    ysc3839
        1
    ysc3839  
       2023-09-26 08:12:40 +08:00 via Android
    没有必要用 std::wstring ,直接写 const wchar_t* watermarkText 即可。
    CreateWindow 改成 CreateWindowExW ,第一个参数直接写 WS_EX_LAYERED | WS_EX_TRANSPARENT ,不需要 SetWindowLong 。
    不应该用 SetLayeredWindowAttributes ,而应该用 UpdateLayeredWindow ,大致方法参见 https://www.cnblogs.com/strive-sun/p/13073015.html 。后续就不用管了,WM_PAINT 那块删掉。
    tool2d
        2
    tool2d  
       2023-09-26 09:30:33 +08:00
    ”程序无法正常关闭退出“ 这是缺少 WM_CLOSE 。

    你多问几次 GPT ,复杂代码一次成型几乎不太可能。多问几次就可以了。
    xqb
        3
    xqb  
       2023-09-26 16:04:27 +08:00
    保证 GdiplusShutdown 前 graphics 析构就可以了
    zhuangzhuang1988
        4
    zhuangzhuang1988  
       2023-09-26 20:08:09 +08:00
    koomox
        5
    koomox  
    OP
       2023-09-26 23:01:04 +08:00
    感谢各位。 @ysc3839 参考你提供的方案,解决了很多问题。放出最终版本,还有一些小问题,但是可以正常运行了,欢迎大家指正。
    ```
    #include <Windows.h>
    #include <gdiplus.h>
    #include <string>
    #include <thread>

    using namespace Gdiplus;
    #pragma comment(lib, "gdiplus.lib")

    const wchar_t* watermarkText = L"Your Watermark Text";
    const int watermarkFontSize = 38;
    const int watermarkSpacing = 100;

    void DrawWatermarkToBitmap(HWND hwnd, HDC hdc, int width, int height)
    {
    // 创建位图上下文
    HDC memDC = CreateCompatibleDC(hdc);
    HBITMAP hBitmap = CreateCompatibleBitmap(hdc, width, height);
    HBITMAP hOldBitmap = static_cast<HBITMAP>(SelectObject(memDC, hBitmap));

    // 创建 Graphics 对象
    Graphics graphics(memDC);

    // 创建字体
    FontFamily fontFamily(L"Arial");
    Font font(&fontFamily, watermarkFontSize, FontStyleRegular, UnitPixel);

    // Create a SolidBrush with the text color
    SolidBrush textBrush(Color(128, 255, 0, 0));

    // 获取文本尺寸
    RectF layoutRect;
    graphics.MeasureString(watermarkText, -1, &font, PointF(0, 0), &layoutRect);

    // 计算水印文本块的总数以填满整个位图
    int numBlocksX = (width + watermarkSpacing) / (static_cast<int>(layoutRect.Width) + watermarkSpacing);
    int numBlocksY = (height + watermarkSpacing) / (static_cast<int>(layoutRect.Height) + watermarkSpacing);

    // 计算实际的间距
    int actualSpacingX = (width - numBlocksX * static_cast<int>(layoutRect.Width)) / (numBlocksX - 1);
    int actualSpacingY = (height - numBlocksY * static_cast<int>(layoutRect.Height)) / (numBlocksY - 1);

    // 绘制水印文本块
    for (int y = 0; y < numBlocksY; y++) {
    for (int x = 0; x < numBlocksX; x++) {
    int textX = x * (static_cast<int>(layoutRect.Width) + actualSpacingX);
    int textY = y * (static_cast<int>(layoutRect.Height) + actualSpacingY);

    // 移动 Graphics 对象到文本块位置
    graphics.ResetTransform();
    graphics.TranslateTransform(static_cast<float>(textX), static_cast<float>(textY));
    graphics.RotateTransform(-45.0f);

    // 绘制水印文本
    graphics.DrawString(watermarkText, -1, &font, PointF(0, 0), &textBrush);
    }
    }

    // 渲染位图到窗口
    BLENDFUNCTION blend = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
    POINT ptSrc = { 0, 0 };
    SIZE sizeWnd = { width, height };
    POINT ptDst = { 0, 0 };
    UpdateLayeredWindow(hwnd, hdc, &ptDst, &sizeWnd, memDC, &ptSrc, 0, &blend, ULW_ALPHA);

    // 清理资源
    SelectObject(memDC, hOldBitmap);
    DeleteObject(hBitmap);
    DeleteDC(memDC);

    //ReleaseDC(hwnd, hdc);
    }

    LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
    switch (message)
    {
    case WM_CREATE:
    SetTimer(hwnd, 1, 1000, NULL); // 创建定时器,每秒更新水印
    break;
    case WM_TIMER:
    {
    DrawWatermarkToBitmap(hwnd, GetDC(hwnd), GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
    break;
    }
    case WM_SIZE:
    // 窗口大小改变时重新绘制
    {
    DrawWatermarkToBitmap(hwnd, GetDC(hwnd), GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
    break;
    }
    case WM_CLOSE:
    DestroyWindow(hwnd);
    break;
    case WM_DESTROY:
    {
    PostQuitMessage(0);
    break;
    }
    default:
    return DefWindowProc(hwnd, message, wParam, lParam);
    }

    return 0;
    }

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


    // 注册窗口类
    WNDCLASSEX wcex;
    ZeroMemory(&wcex, sizeof(WNDCLASSEX));
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.lpfnWndProc = WndProc;
    wcex.hInstance = hInstance;
    wcex.lpszClassName = L"WatermarkWindowClass";
    RegisterClassEx(&wcex);

    HWND hwnd = CreateWindowExW(WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST, L"WatermarkWindowClass", L"Watermark Window", WS_POPUP, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), NULL, NULL, hInstance, NULL);

    if (hwnd == NULL)
    {
    return 0;
    }

    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    ShowWindow(hwnd, nCmdShow);

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

    // 关闭 GDI+
    GdiplusShutdown(gdiplusToken);

    return static_cast<int>(msg.wParam);
    }
    ```
    koomox
        6
    koomox  
    OP
       2023-09-26 23:18:32 +08:00
    @ysc3839 感谢,参考你的方案,已解决问题。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3023 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 14:45 · PVG 22:45 · LAX 06:45 · JFK 09:45
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.