计算屏幕 DPI

eri*_*ric 6 python dpi

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

不是在问如何获得显示器的屏幕尺寸——比如确定你的显示器是否为 3000x2000 像素(如解决:如何在 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
Run Code Online (Sandbox Code Playgroud)

这似乎是正确的范围,但返回的数字太低。它返回 240,而我的实际 DPI 更接近 285。

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

Min*_*Jim 9

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

这最初使我想到了 ctypes,您可以在下面看到我最初的(非常复杂的)答案。最近,我找到了一种仅使用 Tk 即可完成此操作的方法(我不记得它的链接,因此将链接到具有相同内容的Stack Overflow 答案)

import tkinter
root = tkinter.Tk()
dpi = root.winfo_fpixels('1i')
Run Code Online (Sandbox Code Playgroud)

原答案

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

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

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

这些解决方案假设只有一台显示器,并且还设置了进程 DPI 感知(这不适合某些情况)。有关 Windows API 调用的详细信息,请参阅、和的ctypes文档。SetProcessDPIAwarenessGetDPIForWindowGetDCGetDeviceCaps

解决方案1

此解决方案不会返回正确的 DPI,而只是该显示器的 Window“缩放系数”。要获得该因子,您应该将返回的 DPI 除以 96(意思96是 的比例因子100%,而120意味着 的比例因子125%,等等)。

该方法用于tkinter获取有效值HWND(窗口的标识符),然后ctypes获取新窗口的 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()
Run Code Online (Sandbox Code Playgroud)

解决方案2

此方法应返回显示器的真实 DPI,但是,应注意两件事:

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

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

此方法用于tkinter获取有效的HWND(窗口的标识符),然后从中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))
Run Code Online (Sandbox Code Playgroud)


eri*_*ric 8

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

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

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

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

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

  • QScreen 中更好的 DPI 抓取方法可能是逻辑DotsPerIngh()。我发现当我使用“物理”时,当规格为 96 时,我会得到像 94.56 这样的数字。“逻辑”实际上返回 96,如果您想要进行自己的物理缩放,这是一个更好的数字。 (2认同)