已测试平台:

  • Windows11

开发环境:

  • Python
    • Python 3.10.13
    • Pyside6==6.9.0
  • C/C++
    • Platform v143
    • C++ 14
    • MSVC
    • WinSDK 10.0

Python Pyside6实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import sys
from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtCore import Qt, QTimer
from PySide6.QtGui import QPainter, QColor, QFont, QGuiApplication


class WatermarkWindow(QWidget):
def __init__(self, screen_geometry, text="Test", opacity=0.1, spacing=400):
super().__init__()
self.text = text
self.opacity = opacity
self.spacing = spacing
self.setGeometry(screen_geometry)

self.setWindowFlags(
Qt.WindowStaysOnTopHint |
Qt.FramelessWindowHint |
Qt.Tool
)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setAttribute(Qt.WA_TransparentForMouseEvents)

self.timer = QTimer(self)
self.timer.timeout.connect(self.update)
self.timer.start(2000)

def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.setOpacity(self.opacity)

color = QColor(255, 0, 0)
color.setAlphaF(self.opacity)
painter.setPen(color)
painter.setFont(QFont("Arial", int(self.spacing / 10), QFont.Bold))

spacing = self.spacing
for y in range(0, self.height(), spacing):
for x in range(0, self.width(), spacing):
painter.save()
painter.translate(x, y)
painter.rotate(-30)
painter.drawText(0, 0, self.text)
painter.restore()


def create_watermarks_on_all_screens(text="Test", opacity=0.15, spacing=400):
watermarks = []
for screen in QGuiApplication.screens():
geo = screen.geometry()
wm = WatermarkWindow(screen_geometry=geo, text=text, opacity=opacity, spacing=spacing)
wm.show()
watermarks.append(wm)
return watermarks


if __name__ == "__main__":
app = QApplication(sys.argv)
watermark_windows = create_watermarks_on_all_screens("屏幕水印测试", opacity=0.35, spacing=200)
sys.exit(app.exec())

C++ 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#include <windows.h>
#include <gdiplus.h>
#include <string>
#include <vector>
#include <Lmcons.h>

#pragma comment(lib, "gdiplus.lib")

using namespace Gdiplus;

const wchar_t CLASS_NAME[] = L"WatermarkLayeredGDIPlus";

std::wstring watermarkText;

int spacing = 300;
int fontSize = 30;
BYTE textAlpha = 200;

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
if (msg == WM_DESTROY)
PostQuitMessage(0);
return DefWindowProc(hwnd, msg, wParam, lParam);
}

void DrawWatermark(HWND hwnd, int width, int height) {
HDC hdcScreen = GetDC(nullptr);
HDC hdcMem = CreateCompatibleDC(hdcScreen);

BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = width;
bmi.bmiHeader.biHeight = -height;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;

void* pvBits = nullptr;
HBITMAP hBitmap = CreateDIBSection(hdcScreen, &bmi, DIB_RGB_COLORS, &pvBits, nullptr, 0);
HGDIOBJ oldBitmap = SelectObject(hdcMem, hBitmap);

Graphics graphics(hdcMem);
graphics.SetSmoothingMode(SmoothingModeHighQuality);
graphics.Clear(Color(0, 0, 0, 0));

FontFamily fontFamily(L"Arial");
Font font(&fontFamily, (REAL)fontSize, FontStyleBold, UnitPixel);

SolidBrush textBrush(Color(180, 255, 0, 0)); // 半透明红色

StringFormat format;
format.SetAlignment(StringAlignmentCenter);
format.SetLineAlignment(StringAlignmentCenter);

for (int y = 0; y < height; y += spacing) {
for (int x = 0; x < width; x += spacing) {
graphics.ResetTransform();
graphics.TranslateTransform((REAL)x + spacing / 2, (REAL)y + spacing / 2);
graphics.RotateTransform(-30);
graphics.DrawString(watermarkText.c_str(), -1, &font,
PointF(0, 0), &format, &textBrush);
}
}

POINT ptPos = { 0, 0 };
SIZE sizeWnd = { width, height };
POINT ptSrc = { 0, 0 };

BLENDFUNCTION blend = {};
blend.BlendOp = AC_SRC_OVER;
blend.BlendFlags = 0;
blend.SourceConstantAlpha = 50; // 整体透明度
blend.AlphaFormat = AC_SRC_ALPHA;

UpdateLayeredWindow(hwnd, hdcScreen, nullptr, &sizeWnd, hdcMem, &ptSrc, 0, &blend, ULW_ALPHA);

SelectObject(hdcMem, oldBitmap);
DeleteObject(hBitmap);
DeleteDC(hdcMem);
ReleaseDC(nullptr, hdcScreen);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) {
// 初始化 GDI+
ULONG_PTR gdiplusToken;
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);

// 获取计算机名
wchar_t computerName[MAX_COMPUTERNAME_LENGTH + 1] = { 0 };
DWORD size = MAX_COMPUTERNAME_LENGTH + 1;
if (GetComputerNameW(computerName, &size)) {
watermarkText = L"机器 - ";
watermarkText += computerName;
}
else {
watermarkText = L"机器 - Unknown PC";
}

WNDCLASS wc = {};
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
RegisterClass(&wc);

// 枚举所有显示器,为每个显示器创建水印窗口
std::vector<HWND> windows;
EnumDisplayMonitors(nullptr, nullptr,
[](HMONITOR hMon, HDC, LPRECT lprcMonitor, LPARAM lp) -> BOOL {
MONITORINFO mi = {};
mi.cbSize = sizeof(mi);
GetMonitorInfo(hMon, &mi);

int x = mi.rcMonitor.left;
int y = mi.rcMonitor.top;
int width = mi.rcMonitor.right - mi.rcMonitor.left;
int height = mi.rcMonitor.bottom - mi.rcMonitor.top;

HWND hwnd = CreateWindowExW(
WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST | WS_EX_NOACTIVATE,
CLASS_NAME, L"", WS_POPUP,
x, y, width, height,
nullptr, nullptr, (HINSTANCE)lp, nullptr);

ShowWindow(hwnd, SW_SHOW);
DrawWatermark(hwnd, width, height);

LONG exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_LAYERED | WS_EX_TRANSPARENT);

return TRUE;
},
(LPARAM)hInstance);

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

GdiplusShutdown(gdiplusToken);
return 0;
}