[英]Getting a screenshot from a DPI unaware app (a bug in DPI for GetDC/BitBlt / GetDC(0) ignores DPI settings)
Suppose I have a primary monitor, which starts at 0x0 and have the typical size of 1920x1080.假设我有一个主监视器,它从 0x0 开始,典型尺寸为 1920x1080。 My DPI for primary monitor is set to 125%, which results in DPI being 120.
我的主显示器的 DPI 设置为 125%,这导致 DPI 为 120。
Now, I want to take a screenshot of my monitor.现在,我想为我的显示器截屏。
I will use the following code (error checking is removed for simplicity):我将使用以下代码(为简单起见,删除了错误检查):
DC = GetDC(0);
GetDeviceCaps(DC, LOGPIXELSX); // returns 120
GetSystemMetrics(SM_CXSCREEN); // returns 1920
W = GetDeviceCaps(DC, HORZRES); // returns 1920
H = GetDeviceCaps(DC, VERTRES); // returns 1080
Bmp = CreateCompatibleBitmap(DC, W, H);
BmpDC = CreateCompatibleDC(DC);
SelectObject(BmpDC, Bmp);
BitBlt(BmpDC, 0, 0, W, H, DC, 0, 0, SRCCOPY);
This code would run fine, if my application is DPI awared (for example, via a manifest).如果我的应用程序支持 DPI(例如,通过清单),则此代码可以正常运行。
W
will be 1920, H
will be 1080, and the resulting Bmp
will hold a screenshot of my monitor. W
将是 1920, H
将是 1080,生成的Bmp
将包含我的显示器的屏幕截图。
However, if my application is NOT DPI awared (for example, does not have a manifest), then all Windows functions will report scaled values, which results in:但是,如果我的应用程序不支持 DPI(例如,没有清单),那么所有 Windows 函数都将报告缩放值,这会导致:
LOGPIXELSX = 96;
SM_CXSCREEN = 1536;
HORZRES = 1536;
VERTRES = 864;
So far so good.到目前为止,一切都很好。
However, the code above will produce a clipped result: while the resulting image will have size of 1536x864, but it will contain only partial copy of the desktop (eg pixels are one-to-one, without scaling).然而,上面的代码会产生一个裁剪的结果:虽然结果图像的大小为 1536x864,但它只包含桌面的部分副本(例如,像素是一对一的,没有缩放)。
There is a clear difference in what functions returns for GetDC(0)
(eg DPI is 96, width is 1536) vs what this DC is actually represents (DPI is 120, width is 1920). GetDC(0)
的函数返回值(例如 DPI 为 96,宽度为 1536)与此 DC 实际表示的函数(DPI 为 120,宽度为 1920)之间存在明显差异。 In other words, the bitmap associated with the GetDC(0)
is NOT scaled for the virtualized DPI.换句话说,与
GetDC(0)
关联的 bitmap 未针对虚拟化 DPI 进行缩放。 This behaviour looks clearly wrong to me.这种行为在我看来显然是错误的。
Question 1: Is this a bug?问题1:这是一个错误吗?
Question 2: How to make a screenshot of the monitor, which will work in non-DPI awared app?问题 2:如何制作监视器的屏幕截图,这将在非 DPI 感知应用程序中工作?
PS Making application DPI awared is not a solution to the question. PS 使应用程序知道 DPI 并不是问题的解决方案。
PPS I am using Windows 10.0.19044.1889. PPS 我正在使用 Windows 10.0.19044.1889。
PPPS I also tried to detect if application was scaled , but GetDPIForMonitor
returns 96, GetScaleFactorForMonitor
returns 100, GetMonitorInfo
returns 1536x864. PPPS 我也尝试检测应用程序是否已缩放,但
GetDPIForMonitor
返回 96, GetScaleFactorForMonitor
返回 100, GetMonitorInfo
返回 1536x864。 In other words, I can not find how to detect the real 1920x1080 size of the desktop for using with BitBlt
.换句话说,我找不到如何检测桌面的真实 1920x1080 尺寸以用于
BitBlt
。
PPPPS There is a similar question here , but the "solution" is to "make process DPI awared", which is not what I am asking. PPPPS 这里有一个类似的问题,但“解决方案”是“让进程知道 DPI”,这不是我要问的。
It seems LogicalToPhysicalPointForPerMonitorDPI will do the conversion you need.似乎LogicalToPhysicalPointForPerMonitorDPI会进行您需要的转换。
#define NOMINMAX
#include <Windows.h>
#include <iostream>
int main() {
auto hdc = ::GetDC(NULL);
const auto dpi = ::GetDeviceCaps(hdc, LOGPIXELSX);
const auto cx = ::GetDeviceCaps(hdc, HORZRES);
const auto cy = ::GetDeviceCaps(hdc, VERTRES);
auto corner = POINT{cx, cy};
::LogicalToPhysicalPointForPerMonitorDPI(NULL, &corner);
::ReleaseDC(NULL, hdc);
std::cout << "DPI: " << dpi << '\n'
<< "logical size: " << cx << " x " << cy << '\n'
<< "physical size: " << corner.x << " x " << corner.y
<< std::endl;
return 0;
}
On my machine I get:在我的机器上,我得到:
DPI: 96
logical size: 2194 x 1234
physical size: 3840 x 2160
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.