我正在尝试用 C++ 创建一个不会出现在屏幕截图中的叠加层。覆盖层应该对用户可见,但不会被屏幕截图捕获(这也将由代码完成)。我读到这可以通过 DirectComposition 和 Direct2D 实现,但我无法达到预期的效果。
当前行为:叠加层显示在屏幕上,但也会在屏幕截图中捕获。
期望的行为:我希望叠加层在屏幕上可见,但不在屏幕截图中捕获。
这是我正在使用的代码:
#include <windows.h>
#include <dcomp.h>
#include <d2d1.h>
#include <thread>
#include <iostream>
#include <fstream>
#pragma comment(lib, "dcomp.lib")
#pragma comment(lib, "d2d1.lib")
void OverlayThreadFunction() {
const wchar_t CLASS_NAME[] = L"DirectCompWindowClass";
WNDCLASSW wc = {};
wc.lpfnWndProc = DefWindowProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = CLASS_NAME;
wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND);
RegisterClassW(&wc);
HWND hwnd = CreateWindowExW(
WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TRANSPARENT,
CLASS_NAME,
L"DirectComposition Overlay",
WS_POPUP | WS_VISIBLE,
0, 0, 100, 100,
// 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), (for fullscreen)
nullptr, nullptr, GetModuleHandle(NULL), nullptr
);
if (!hwnd) {
return;
}
// Set window transparency
SetLayeredWindowAttributes(hwnd, RGB(0, 0, 0), 0, LWA_COLORKEY);
// Initialize DirectComposition
IDCompositionDevice *pDCompDevice = nullptr;
HRESULT hr = DCompositionCreateDevice(nullptr, __uuidof(IDCompositionDevice),
reinterpret_cast<void **>(&pDCompDevice));
if (FAILED(hr)) {
return;
}
IDCompositionTarget *pDCompTarget = nullptr;
hr = pDCompDevice->CreateTargetForHwnd(hwnd, TRUE, &pDCompTarget);
if (FAILED(hr)) {
return;
}
IDCompositionVisual *pDCompVisual = nullptr;
hr = pDCompDevice->CreateVisual(&pDCompVisual);
if (FAILED(hr)) {
return;
}
// Initialize Direct2D
ID2D1Factory *pD2DFactory = nullptr;
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pD2DFactory);
if (FAILED(hr)) {
return;
}
ID2D1HwndRenderTarget *pRT = nullptr;
D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties();
D2D1_HWND_RENDER_TARGET_PROPERTIES hwndProps = D2D1::HwndRenderTargetProperties(hwnd, D2D1::SizeU(
GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)));
hr = pD2DFactory->CreateHwndRenderTarget(props, hwndProps, &pRT);
if (FAILED(hr)) {
return;
}
ID2D1SolidColorBrush *pBrush = nullptr;
hr = pRT->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Red, 1.0f), &pBrush);
if (FAILED(hr)) {
return;
}
pDCompVisual->SetContent(pRT);
// Set the visual as the root of the composition target
hr = pDCompTarget->SetRoot(pDCompVisual);
if (FAILED(hr)) {
return;
}
// Commit the composition
hr = pDCompDevice->Commit();
if (FAILED(hr)) {
return;
}
// Draw overlay
pRT->BeginDraw();
pRT->Clear(D2D1::ColorF(D2D1::ColorF::Black, 0.0f)); // Fully transparent background
pRT->FillRectangle(D2D1::RectF(100, 100, 400, 300), pBrush); // Solid red rectangle
pRT->EndDraw();
MSG msg = {};
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Clean up
pBrush->Release();
pRT->Release();
pD2DFactory->Release();
pDCompVisual->Release();
pDCompTarget->Release();
pDCompDevice->Release();
}
bool SaveBitmap(HBITMAP hBitmap, const char *filename) {
BITMAP bmp;
GetObject(hBitmap, sizeof(BITMAP), &bmp);
LONG height = (bmp.bmHeight > 0) ? -bmp.bmHeight : bmp.bmHeight;
BITMAPFILEHEADER bmfHeader;
BITMAPINFOHEADER bi;
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = bmp.bmWidth;
bi.biHeight = height;
bi.biPlanes = 1;
bi.biBitCount = 32; // Change this to 24 if your bitmap doesn't have alpha channel
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
DWORD dwBmpSize = ((bmp.bmWidth * bi.biBitCount + 31) / 32) * 4 * abs(height);
// Add the size of the headers to the size of the bitmap to get the total file size
DWORD dwSizeofDIB = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwBmpSize;
// Offset to where the actual bitmap bits start.
bmfHeader.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + (DWORD) sizeof(BITMAPINFOHEADER);
// Size of the file
bmfHeader.bfSize = dwSizeofDIB;
// bfType must always be BM for Bitmaps
bmfHeader.bfType = 0x4D42; //BM
// Create the file
std::ofstream file(filename, std::ios::out | std::ios::binary);
if (!file) {
std::cerr << "Error: Unable to create file: " << filename << std::endl;
return false;
}
// Write the Bitmap file header
file.write(reinterpret_cast<const char *>(&bmfHeader), sizeof(BITMAPFILEHEADER));
// Write the Bitmap info header
file.write(reinterpret_cast<const char *>(&bi), sizeof(BITMAPINFOHEADER));
// Get the bitmap bits
BYTE *pBits = new BYTE[dwBmpSize];
GetBitmapBits(hBitmap, dwBmpSize, pBits);
// Write the Bitmap bits
file.write(reinterpret_cast<const char *>(pBits), dwBmpSize);
// Clean up
delete[] pBits;
file.close();
return true;
}
int main() {
std::thread overlayThread(OverlayThreadFunction);
Sleep(2000); //Sleep for 2secs. to ensure that overlay is rendered
// Make screenshot
HWND hDesktopWnd = GetDesktopWindow();
HDC hDesktopDC = GetDC(hDesktopWnd);
HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
HBITMAP hCaptureBitmap = CreateCompatibleBitmap(hDesktopDC, GetSystemMetrics(SM_CXSCREEN),
GetSystemMetrics(SM_CYSCREEN));
SelectObject(hCaptureDC, hCaptureBitmap);
BitBlt(hCaptureDC, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), hDesktopDC, 0, 0, SRCCOPY);
SaveBitmap(hCaptureBitmap, "desktop_screenshot.bmp");
DeleteObject(hCaptureBitmap);
DeleteDC(hCaptureDC);
ReleaseDC(hDesktopWnd, hDesktopDC);
std::cout << "Screenshot captured and saved\n";
overlayThread.join();
return 0;
}
我所说的“屏幕截图”并不一定是指 Windows 提供的标准屏幕截图功能。我也愿意接受涉及自定义屏幕截图代码的解决方案。如果有办法通过自编程的截图方法来实现这种隐形,那也是可以接受的。
任何有关如何使覆盖层在屏幕截图中不可见的建议将不胜感激!
SetWindowDisplayAffinity()
与 WDA_EXCLUDEFROMCAPTURE
标志一起使用:
该窗口仅显示在监视器上。在其他地方,根本不会出现该窗口。
这种亲和力的一个用途是用于显示视频录制控件的窗口,以便这些控件不包含在捕获中。
在 Windows 10 Version 2004 中引入。请参阅有关以前版本的 Windows 的兼容性的备注。