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

227 天前
 koomox

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);
}

1238 次点击
所在节点    程序员
6 条回复
ysc3839
227 天前
没有必要用 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
227 天前
”程序无法正常关闭退出“ 这是缺少 WM_CLOSE 。

你多问几次 GPT ,复杂代码一次成型几乎不太可能。多问几次就可以了。
xqb
227 天前
保证 GdiplusShutdown 前 graphics 析构就可以了
zhuangzhuang1988
227 天前
koomox
227 天前
感谢各位。 @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
227 天前
@ysc3839 感谢,参考你的方案,已解决问题。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/977125

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX