在Windows下获取实际屏幕dpi/ppi

25

我想获取实际的屏幕dpi/ppi,而不是C++中用于字体的dpi设置。

我尝试使用以下代码:

版本1报告了72 dpi,这是错误的。

SetProcessDPIAware(); //true
HDC screen = GetDC(NULL);
double hSize = GetDeviceCaps(screen, HORZSIZE);
double vSize = GetDeviceCaps(screen, VERTSIZE);
double hRes = GetDeviceCaps(screen, HORZRES);
double vRes = GetDeviceCaps(screen, VERTRES);
double hPixelsPerInch = hRes / hSize * 25.4;
double vPixelsPerInch = vRes / vSize * 25.4;
ReleaseDC(NULL, screen);
return (hPixelsPerInch + vPixelsPerInch) * 0.5;

第二版报告的是96 dpi,这是Windows字体的dpi设置,但不是实际屏幕的dpi。

SetProcessDPIAware(); //true
HDC screen = GetDC(NULL);
double hPixelsPerInch = GetDeviceCaps(screen,LOGPIXELSX);
double vPixelsPerInch = GetDeviceCaps(screen,LOGPIXELSY);
ReleaseDC(NULL, screen);
return (hPixelsPerInch + vPixelsPerInch) * 0.5;

@BrianHaak:正如您在这个问题和您链接的那个问题中所指出的,EDID并不总是可用的,有时EDID中的信息是错误的。 - Adrian McCarthy
4个回答

16

我对这里的答案感到困惑。

微软有一个GetDpiForMonitor方法:

https://msdn.microsoft.com/zh-cn/library/windows/desktop/dn280510(v=vs.85).aspx

显示器会向工具公开它们的物理尺寸。使用HWiNFO64工具,您可以读取您的监视器的宽度和高度(以厘米为单位)。所以如果他们能得到它(DDI?),那么您也可以访问该信息。

即使另一篇 Stack Overflow 的帖子也提到使用WmiMonitorBasicDisplayParams来获取数据。

如何获取显示器大小

因此,顶部的回答是完全错误的。


4
GetDpiForMonitor函数提供的是逻辑英寸每个点数,而不是物理英寸。一些显示器通过EDID公开了它们的物理尺寸,但很不幸,并非所有显示器都支持此功能。WmiMonitorBasicDisplayParams文档中的备注指出,实际值可能无法获取。此外,显示器可能是投影仪,投影仪无法知道投放距离,因此即使它知道缩放设置,也无法计算物理尺寸。在所有情况下都没有可靠的方法来获取实际的DPI。抱歉。 - Adrian McCarthy
2
使用 MDT_RAW_DPI 调用 GetDpiForMonitor 函数将确实返回实际的物理 DPI(如果可用)。在我的机器上,MDT_EFFECTIVE_DPI = (144, 144),MDT_RAW_DPI = (158, 160)。 - diapir
1
并不是在当时就有问题,只是过时了。我想你提到的那些API是在Windows 8.1和10中添加的。 - O'Rooney

15
很遗憾,在一般情况下,您所要求的是不可能的。
Windows无法获取物理屏幕尺寸。虽然Windows可能知道您的屏幕具有1024x768像素,但它并不知道屏幕实际大小。您可能会将电缆从旧的13英寸屏幕上拔出并连接到19英寸监视器上,而不更改分辨率。DPI将不同,但Windows不会注意到您更换了监视器。
您可以获取打印机的真实物理尺寸和DPI(假设驱动程序没有虚假信息),但不能可靠地获取屏幕的信息。
更新:
正如其他人指出的,较新的监视器和操作系统之间存在双向通信的标准(EDID),这可能使得某些设备的此信息可用。但我尚未找到提供此信息的显示器。
即使EDID普遍可用,这仍然无法在一般情况下解决问题,因为显示器可能是视频投影仪,其中DPI取决于变焦、对焦、镜头类型和抛距。投影仪极不可能知道抛距,因此无法报告实际DPI。

5
为正确答案点赞。Windows不知道您的显示器的物理尺寸,它只知道该显示器的分辨率是“1680x1050”。它不知道这是一个22英寸(90dpi)、21英寸(94dpi)、20英寸(99dpi)还是4英寸手持设备(495dpi)。幸运的是,作为 Windows 开发者,您不需要关心显示器的物理分辨率。 - Ian Boyd
5
“幸运的是,作为Windows开发者,您不必关心显示器的物理分辨率。”但是随着4K显示器的出现,情况似乎并非如此。人们发现在Windows系统上使用4K显示器不太顺畅,因为大多数程序无法很好地处理DPI的增加。即使使用Windows缩放功能,也不能完全解决问题。我希望问题能得到解决,因为我渴望拥有一台4K显示器。 - leetNightshade
7
@leetNightshade说:Ian是对的,Windows开发人员不关心物理DPI。他们必须关心逻辑DPI。不幸的是,Windows让太多的开发人员忽略它太长时间了,所以现在分辨率有了巨大的提升,我们都受苦了。 - Adrian McCarthy
3
作为一名Windows开发者,您应该从20世纪80年代开始就一直在做的事情是现在网页开发者正在学习的。在网页上,您应该使用emen来指定图像、按钮、文本框等的大小。这样,您就不必关心监视器的分辨率。Windows几十年来一直使用与em相当的东西:对话框单元(基于您使用的字体中平均字符的宽度和高度)。 - Ian Boyd
2
到了2017年,对于许多UI/UX设计师来说,了解典型屏幕(即非投影仪)的物理尺寸是重要信息。如果您想要图标具有特定的物理尺寸,那么知道逻辑dpi有什么用呢? - jiggunjer
显示剩余8条评论

1

通过以下方法可以获取DPI信息并得到精确值。

ID2D1Factory* m_pDirect2dFactory;
D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory);
FLOAT dpiX, dpiY;
m_pDirect2dFactory->GetDesktopDpi( &dpiX, &dpiY );

6
这个问题要求实际的物理 DPI。这个 D2D 接口(仅在 Windows 7 及以上版本可用)只提供与 GetDeviceCaps(hdcScreen, LOGPIXELSX) 返回相同的逻辑 DPI。正如我在我的回答中所说,没有办法获得像 Andy Li 所要求的精确值,因为 Windows 不知道每个监视器的真实尺寸。另外请注意,你可以连接两个具有不同物理 DPI 的不同显示器,而这些方法只提供“桌面”的一个答案。 - Adrian McCarthy

0
我认为你想要的是:
``` GetDeviceCaps(hdcScreen, LOGPIXELSX); GetDeviceCaps(hdcScreen, LOGPIXELSY); ```

这对打印机 DC 很有用。对于显示器,它返回每个逻辑英寸的像素数,这很可能与物理 DPI 不匹配。 - Adrian McCarthy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接