计算屏幕 DPI

Calculate screen DPI

我正在使用 Python 构建一个图像渲染应用程序,将 numpy 数组渲染为图像。我需要一种方法来自动计算屏幕 DPI,因为我在不同的显示器上生成相同尺寸的图像(例如 2x2 英寸)。我在 Windows 10, Python 3.7.

不是 询问如何获得显示器的 屏幕尺寸 -- 比如确定您的显示器是否为 3000x2000 像素(如解决于:How do I get monitor resolution in Python?).

相反,我想要专门计算 DPI 的东西,并考虑用户应用的任何缩放设置,这些设置可能会改变他们系统的 DPI。例如,我将比例设置为 250%。

在专家交流中遵循这个答案 (https://www.experts-exchange.com/questions/21794611/Detecting-system-DPI-settings-in-Python.html),我尝试使用这个:

from ctypes import windll

def get_ppi():
    LOGPIXELSX = 88
    LOGPIXELSY = 90
    user32 = windll.user32
    user32.SetProcessDPIAware()
    dc = user32.GetDC(0)   
    pix_per_inch = windll.gdi32.GetDeviceCaps(dc, LOGPIXELSX)
    print("Horizontal DPI is", windll.gdi32.GetDeviceCaps(dc, LOGPIXELSX))
    print("Vertical DPI is", windll.gdi32.GetDeviceCaps(dc, LOGPIXELSY))
    user32.ReleaseDC(0, dc)
    return pix_per_inch

这似乎是正确的大概,但是 returns 这个数字太低了。它 returns 240,而我的实际 DPI 接近 285。

我宁愿避免为此使用像 Qt 这样的 GUI 框架,因为我想尽量减少开销,但如果这是唯一的方法,那么我会的!如果我必须使用框架,我更喜欢 PyQt5。

虽然我说过我想避免它,但有一种非常简单的方法可以使用 PyQt5 来解决这个问题。我考虑得越多,就越认为这可能是最好的解决方案,因为它在很大程度上独立于平台:

import sys
from PyQt5.QtWidgets import QApplication
app = QApplication(sys.argv)
screen = app.screens()[0]
dpi = screen.physicalDotsPerInch()
app.quit()

请注意 app.screens() returns 屏幕列表。在我的例子中,我只连接了一个,但你可能有多个,所以一定要知道你需要从哪个屏幕获得 dpi。如果你将所有这些都包含在一个函数中,它就不会用 PyQt 垃圾弄乱你的命名空间。

此外,有关 QScreen 的更多信息(sc 是 QScreen 对象),请参阅此文档页面:
https://doc.qt.io/qt-5/qscreen.html

您可以从中提取各种很酷的东西。

虽然 答案有效,但我宁愿避免离开标准库(忽略不包含 ctypes 的 Python 的分布)。

这最初引导我使用 ctypes,您可以在下面看到我最初的(非常复杂的)答案。最近我找到了一种只用 Tk 的方法(我不记得 link 到它所以 link 到具有相同内容的 Stack Overflow answer

import tkinter
root = tkinter.Tk()
dpi = root.winfo_fpixels('1i')

原回答

以下答案提供了两种解决方案:

  1. 第一个是 Windows' 由于用户的显示缩放而报告的 DPI

  2. 第二个是通过找到显示器的物理尺寸和分辨率计算出的显示器的真实 DPI

这些解决方案假定只有一台显示器并且还设置了进程 DPI 感知(这不适用于某些情况)。有关 ctypes 进行的 Windows API 调用的详细信息,请参阅 SetProcessDPIAwareness, GetDPIForWindow, GetDC and GetDeviceCaps.

的文档

解决方案 1

此解决方案没有 return 适当的 DPI,而只是 Window 该显示器的“缩放系数”。要获得该因子,您应该将 returned DPI 除以 96(意思是 96100% 的比例因子,而 120120 的比例因子125%,等等)。

该方法使用 tkinter 获取有效的 HWND(window 的标识符),然后 ctypes 获取新的 [=95] 的 DPI =].

# Import the libraries
import ctypes
import tkinter

# Set process DPI awareness
ctypes.windll.shcore.SetProcessDpiAwareness(1)
# Create a tkinter window
root = tkinter.Tk()
# Get the reported DPI from the window's HWND
dpi = ctypes.windll.user32.GetDpiForWindow(root.winfo_id())
# Print the DPI
print(dpi)
# Destroy the window
root.destroy()

解决方案 2

此方法应该return 显示器的真实 DPI,但是,需要注意两点:

  1. 显示器的物理尺寸(偶尔)报告不正确。因此,这将导致完全不正确的 DPI 值,尽管我不确定如何防止这种情况发生,除非添加一个检查以确保它在合理的值之间(无论那是什么意思!)。

  2. Windows 使用的分辨率通常与显示器的实际分辨率不同。此方法计算显示器的真实 DPI,但如果您想计算每物理英寸的虚拟像素数,您可以将 dwdh 的分配替换为 root.winfo_screenwidth()root.winfo_screenheight()(分别)

此方法使用 tkinter 获取有效的 HWND(window 的标识符),然后 ctypes 获取设备上下文 (DC) 和硬件详情来自此。

# Our convertion from millimeters to inches
MM_TO_IN = 0.0393700787

# Import the libraries
import ctypes
import math
import tkinter

# Set process DPI awareness
ctypes.windll.shcore.SetProcessDpiAwareness(1)
# Create a tkinter window
root = tkinter.Tk()
# Get a DC from the window's HWND
dc = ctypes.windll.user32.GetDC(root.winfo_id())
# The the monitor phyical width
# (returned in millimeters then converted to inches)
mw = ctypes.windll.gdi32.GetDeviceCaps(dc, 4) * MM_TO_IN
# The the monitor phyical height
mh = ctypes.windll.gdi32.GetDeviceCaps(dc, 6) * MM_TO_IN
# Get the monitor horizontal resolution
dw = ctypes.windll.gdi32.GetDeviceCaps(dc, 8)
# Get the monitor vertical resolution
dh = ctypes.windll.gdi32.GetDeviceCaps(dc, 10)
# Destroy the window
root.destroy()

# Horizontal and vertical DPIs calculated
hdpi, vdpi = dw / mw, dh / mh
# Diagonal DPI calculated using Pythagoras
ddpi = math.hypot(dw, dh) / math.hypot(mw, mh)
# Print the DPIs
print(round(hdpi, 1), round(vdpi, 1), round(ddpi, 1))

如果您不想使用 Qt,可以使用您最后的方法,因为在我的例子中,Qt 解决方案和 ctypes 解决方案之间存在 45.95 dpi 的差异。我认为所有笔记本电脑都是一样的。每次使用 ctypes 时只需在结果上添加 (45.96 dpi)。