捕获屏幕的错误部分
Capturing wrong part of the screen
我创建了一个小型演示应用程序,window 如下所示:
当我 运行 这个演示应用程序,并按任意键时,我想捕获屏幕位图的一部分。
我感兴趣的屏幕部分是我的 window 占据的部分,即我的 window 中包含字母的顶部矩形的内容。捕获的屏幕位图应如下所示:
我遇到的问题是屏幕捕获代码捕获了错误的屏幕部分。
下面是完整的代码(请记住,我尽量保持最小化):
#include <Windows.h>
void foo(HWND hWnd)
{
HDC hdcScreen;
HDC hdcWindow;
hdcScreen = GetDC(NULL);
hdcWindow = GetDC(hWnd);
RECT rcClient;
GetClientRect(hWnd, &rcClient);
// map window's client coordinates to screen coordinates
// HERE IS THE PROBLEM, SOMEHOW COORDINATES ARE NOT TRANSLATED CORRECTLY
// do not know how to fix this, but I am trying :(
RECT rc1 = rcClient;
MapWindowPoints(hWnd, NULL, (LPPOINT)&rc1, 2);
// capture desktop portion of the image
// that corresponds to the window's top rectangle (the one that has letters in it)
// and blit the result in the bottom rectangle
// so result can be visually compared
if (!BitBlt(hdcWindow,
rcClient.left + 50, // coordinates of the bottom rectangle
rcClient.top + 70, // sorry for the "magic numbers"
75, 35, // I am low on time :(
hdcScreen,
rc1.left + 50, // screen coordinates of the top rectangle
rc1.top + 20, // (the one that contains letters)
SRCCOPY))
{
OutputDebugString(L"StretchBlt has failed");
ReleaseDC(NULL, hdcScreen);
ReleaseDC(hWnd, hdcWindow);
return;
}
RECT rcBottomRect; // Frame again the bottom rectangle in the window,
rcBottomRect.left = rcClient.left + 50; // to make visual comparing easier
rcBottomRect.top = rcClient.top + 70; // and to verify that I didn't screw up
rcBottomRect.right = rcClient.left + 125; // the coordinates
rcBottomRect.bottom = rcClient.top + 105;
HBRUSH br = (HBRUSH)GetStockObject(BLACK_BRUSH);
FrameRect(hdcWindow, &rcBottomRect, br);
ReleaseDC(NULL, hdcScreen);
ReleaseDC(hWnd, hdcWindow);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_KEYUP: // easiest handler to add that keeps things minimal
foo(hwnd); // capture screen
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rcClient;
GetClientRect(hwnd, &rcClient);
RECT rcTopRect;
rcTopRect.left = rcClient.left + 50;
rcTopRect.top = rcClient.top + 20;
rcTopRect.right = rcTopRect.left + 75;
rcTopRect.bottom = rcTopRect.top + 35;
HBRUSH br = (HBRUSH)GetStockObject(BLACK_BRUSH);
TextOut(hdc, 20, 30, L"Asdf ghj kkl oioio 4545 676767", ARRAYSIZE(L"Asdf ghj kkl oioio 4545 676767"));
FrameRect(hdc, &rcTopRect, br);
RECT rcBottomRect;
rcBottomRect.left = rcClient.left + 50;
rcBottomRect.top = rcClient.top + 70;
rcBottomRect.right = rcClient.left + 125;
rcBottomRect.bottom = rcClient.top + 105;
FrameRect(hdc, &rcBottomRect, br);
EndPaint(hwnd, &ps);
}
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
// BOILERPLATE CODE...
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = L"myWindowClass";
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, L"Window Registration Failed!", L"Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
// Step 2: Creating the Window
hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
L"myWindowClass",
L"MVCE",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 300, 170,
NULL, NULL, hInstance, NULL);
if (hwnd == NULL)
{
MessageBox(NULL, L"Window Creation Failed!", L"Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// Step 3: The Message Loop
while (GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
问题:
如何用 MapWindowPoints
?
修复计算“错误”(也许这不是错误,也许我误用了 API?)
更新:
我忘了说我有 2 个显示器。在第二台显示器上测试应用程序后一切正常。
在完成第一个显示器的设置后,我发现它设置为将文本、应用程序和其他项目缩放到 150%。
将其恢复到 100% 使代码可以正常工作,但现在我需要为这种情况找到解决方案,因为我可能不会强迫用户更改他们的设置。
如有任何帮助,我们将不胜感激。
您不能强迫用户更改 DPI,但您可以要求 Windows 停止在您的应用程序中弄乱坐标。为此,请将清单包含到程序的主 .exe 中。你可能需要最大。设置,true/pm 和 PerMonitorV2.
See this article 了解更多信息。
MapWindowPoints is a bit vague, but it seems like it literally wants coordinates relative to your window not your window's client area (which is what you're giving it). That seems like it would explain the symptom since your vertical offset looks to be the same size as your window's title bar. I've always used ClientToScreen的文档,更清楚。
DPI 缩放也可能是问题的来源,但它们通常不会仅在 y 轴上表现出来。确保将您的应用程序标记为 high DPI multi-monitor aware,这样系统就不会在您背后进行任何缩放。使用该设置,您可以在大部分时间使用 GDI 进行这项工作,但有一些限制。 (1) 如果你的显示器有不同的比例因子,这是非常困难的,(2) 如果在你的程序已经 运行 时动态改变比例因子,你会收到通知,但许多 API 仍然会告诉你 DPI更改前的设置。
我创建了一个小型演示应用程序,window 如下所示:
当我 运行 这个演示应用程序,并按任意键时,我想捕获屏幕位图的一部分。
我感兴趣的屏幕部分是我的 window 占据的部分,即我的 window 中包含字母的顶部矩形的内容。捕获的屏幕位图应如下所示:
我遇到的问题是屏幕捕获代码捕获了错误的屏幕部分。
下面是完整的代码(请记住,我尽量保持最小化):
#include <Windows.h>
void foo(HWND hWnd)
{
HDC hdcScreen;
HDC hdcWindow;
hdcScreen = GetDC(NULL);
hdcWindow = GetDC(hWnd);
RECT rcClient;
GetClientRect(hWnd, &rcClient);
// map window's client coordinates to screen coordinates
// HERE IS THE PROBLEM, SOMEHOW COORDINATES ARE NOT TRANSLATED CORRECTLY
// do not know how to fix this, but I am trying :(
RECT rc1 = rcClient;
MapWindowPoints(hWnd, NULL, (LPPOINT)&rc1, 2);
// capture desktop portion of the image
// that corresponds to the window's top rectangle (the one that has letters in it)
// and blit the result in the bottom rectangle
// so result can be visually compared
if (!BitBlt(hdcWindow,
rcClient.left + 50, // coordinates of the bottom rectangle
rcClient.top + 70, // sorry for the "magic numbers"
75, 35, // I am low on time :(
hdcScreen,
rc1.left + 50, // screen coordinates of the top rectangle
rc1.top + 20, // (the one that contains letters)
SRCCOPY))
{
OutputDebugString(L"StretchBlt has failed");
ReleaseDC(NULL, hdcScreen);
ReleaseDC(hWnd, hdcWindow);
return;
}
RECT rcBottomRect; // Frame again the bottom rectangle in the window,
rcBottomRect.left = rcClient.left + 50; // to make visual comparing easier
rcBottomRect.top = rcClient.top + 70; // and to verify that I didn't screw up
rcBottomRect.right = rcClient.left + 125; // the coordinates
rcBottomRect.bottom = rcClient.top + 105;
HBRUSH br = (HBRUSH)GetStockObject(BLACK_BRUSH);
FrameRect(hdcWindow, &rcBottomRect, br);
ReleaseDC(NULL, hdcScreen);
ReleaseDC(hWnd, hdcWindow);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_KEYUP: // easiest handler to add that keeps things minimal
foo(hwnd); // capture screen
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rcClient;
GetClientRect(hwnd, &rcClient);
RECT rcTopRect;
rcTopRect.left = rcClient.left + 50;
rcTopRect.top = rcClient.top + 20;
rcTopRect.right = rcTopRect.left + 75;
rcTopRect.bottom = rcTopRect.top + 35;
HBRUSH br = (HBRUSH)GetStockObject(BLACK_BRUSH);
TextOut(hdc, 20, 30, L"Asdf ghj kkl oioio 4545 676767", ARRAYSIZE(L"Asdf ghj kkl oioio 4545 676767"));
FrameRect(hdc, &rcTopRect, br);
RECT rcBottomRect;
rcBottomRect.left = rcClient.left + 50;
rcBottomRect.top = rcClient.top + 70;
rcBottomRect.right = rcClient.left + 125;
rcBottomRect.bottom = rcClient.top + 105;
FrameRect(hdc, &rcBottomRect, br);
EndPaint(hwnd, &ps);
}
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
// BOILERPLATE CODE...
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = L"myWindowClass";
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, L"Window Registration Failed!", L"Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
// Step 2: Creating the Window
hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
L"myWindowClass",
L"MVCE",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 300, 170,
NULL, NULL, hInstance, NULL);
if (hwnd == NULL)
{
MessageBox(NULL, L"Window Creation Failed!", L"Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// Step 3: The Message Loop
while (GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
问题:
如何用 MapWindowPoints
?
更新:
我忘了说我有 2 个显示器。在第二台显示器上测试应用程序后一切正常。
在完成第一个显示器的设置后,我发现它设置为将文本、应用程序和其他项目缩放到 150%。
将其恢复到 100% 使代码可以正常工作,但现在我需要为这种情况找到解决方案,因为我可能不会强迫用户更改他们的设置。
如有任何帮助,我们将不胜感激。
您不能强迫用户更改 DPI,但您可以要求 Windows 停止在您的应用程序中弄乱坐标。为此,请将清单包含到程序的主 .exe 中。你可能需要最大。设置,true/pm 和 PerMonitorV2.
See this article 了解更多信息。
MapWindowPoints is a bit vague, but it seems like it literally wants coordinates relative to your window not your window's client area (which is what you're giving it). That seems like it would explain the symptom since your vertical offset looks to be the same size as your window's title bar. I've always used ClientToScreen的文档,更清楚。
DPI 缩放也可能是问题的来源,但它们通常不会仅在 y 轴上表现出来。确保将您的应用程序标记为 high DPI multi-monitor aware,这样系统就不会在您背后进行任何缩放。使用该设置,您可以在大部分时间使用 GDI 进行这项工作,但有一些限制。 (1) 如果你的显示器有不同的比例因子,这是非常困难的,(2) 如果在你的程序已经 运行 时动态改变比例因子,你会收到通知,但许多 API 仍然会告诉你 DPI更改前的设置。