Suppose I have a primary monitor, which starts at 0x0 and have the typical size of 1920x1080. My DPI for primary monitor is set to 125%, which results in DPI being 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). W
will be 1920, H
will be 1080, and the resulting Bmp
will hold a screenshot of my monitor.
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:
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 (e.g. pixels are one-to-one, without scaling).
There is a clear difference in what functions returns for GetDC(0)
(e.g. DPI is 96, width is 1536) vs what this DC is actually represents (DPI is 120, width is 1920). In other words, the bitmap associated with the GetDC(0)
is NOT scaled for the virtualized DPI. This behaviour looks clearly wrong to me.
Question 1: Is this a bug?
Question 2: How to make a screenshot of the monitor, which will work in non-DPI awared app?
P.S. Making application DPI awared is not a solution to the question.
P.P.S. I am using Windows 10.0.19044.1889.
P.P.P.S. I also tried to detect if application was scaled, but GetDPIForMonitor
returns 96, GetScaleFactorForMonitor
returns 100, GetMonitorInfo
returns 1536x864. In other words, I can not find how to detect the real 1920x1080 size of the desktop for using with BitBlt
.
P.P.P.P.S. There is a similar question here, but the "solution" is to "make process DPI awared", which is not what I am asking.
It seems LogicalToPhysicalPointForPerMonitorDPI will do the conversion you need.
#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