You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

Python窗口捕获适配多显示器DPI缩放问题求助

适配多显示器DPI缩放以正确捕获窗口坐标

这个问题的核心是Windows的DPI虚拟化机制:如果你的Python程序没有声明DPI感知,系统会自动对窗口坐标进行缩放,返回的是逻辑像素而非实际物理像素,这就导致你看到的坐标和实际显示器分辨率不匹配。下面提供两种可行的解决思路:

方法一:启用程序的DPI感知

让程序告诉系统它能处理高DPI显示器,这样Win32 API就会直接返回物理像素坐标,不需要手动转换。可以通过ctypes调用Windows API来实现:

import ctypes
from win32 import win32gui

# 设置进程DPI感知为每显示器感知(推荐)
try:
    # Windows 10 1607+ 支持的DPI感知上下文
    PROCESS_PER_MONITOR_DPI_AWARE = 2
    ctypes.windll.shcore.SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE)
except:
    # 兼容旧版本Windows
    PROCESS_DPI_AWARE = 1
    ctypes.windll.user32.SetProcessDPIAware()

# 你的原有代码
def enum_cb(hwnd, results):
    winlist.append((hwnd, win32gui.GetWindowText(hwnd)))

def get_screens():
    win32gui.EnumWindows(enum_cb, winlist)
    return [(hwnd, title) for hwnd, title in winlist if title]

def get_screensbyname(screen_name):
    winlist = get_screens()
    screens = [(hwnd, title) for hwnd, title in winlist if screen_name in title.lower()]
    while len(screens) <= 0:
        winlist = get_screens()
        screens = [(hwnd, title) for hwnd, title in winlist if screen_name in title.lower()]
    return screens

winlist = []
windows = get_screensbyname('firefox')
window = windows[0][0]
wRect = win32gui.GetWindowRect(window)
cRect = win32gui.GetClientRect(window)
print('GetWindowRect ', wRect)
print('GetCLientRect ', cRect)

启用DPI感知后,你获取的坐标就会是实际物理像素,和你手动计算的结果一致。

方法二:手动获取显示器缩放比例并转换坐标

如果无法修改程序的DPI感知设置,可以通过获取窗口所在显示器的DPI缩放因子,手动将逻辑坐标转换为物理像素:

import ctypes
from win32 import win32gui, win32api

def get_monitor_dpi(hwnd):
    # 获取窗口所在的显示器句柄
    monitor = win32api.MonitorFromWindow(hwnd, win32api.MONITOR_DEFAULTTONEAREST)
    # 获取显示器的DPI
    try:
        # Windows 10 1607+ 使用shcore.GetDpiForMonitor
        dpi_x = ctypes.c_uint()
        dpi_y = ctypes.c_uint()
        ctypes.windll.shcore.GetDpiForMonitor(monitor, 0, ctypes.byref(dpi_x), ctypes.byref(dpi_y))
        return dpi_x.value / 96, dpi_y.value / 96  # 96是默认DPI,缩放因子=实际DPI/96
    except:
        # 旧版本Windows使用GetDeviceCaps
        hdc = win32api.GetDC(monitor)
        dpi_x = win32api.GetDeviceCaps(hdc, win32api.LOGPIXELSX)
        dpi_y = win32api.GetDeviceCaps(hdc, win32api.LOGPIXELSY)
        win32api.ReleaseDC(monitor, hdc)
        return dpi_x / 96, dpi_y / 96

# 原有代码部分
def enum_cb(hwnd, results):
    winlist.append((hwnd, win32gui.GetWindowText(hwnd)))

def get_screens():
    win32gui.EnumWindows(enum_cb, winlist)
    return [(hwnd, title) for hwnd, title in winlist if title]

def get_screensbyname(screen_name):
    winlist = get_screens()
    screens = [(hwnd, title) for hwnd, title in winlist if screen_name in title.lower()]
    while len(screens) <= 0:
        winlist = get_screens()
        screens = [(hwnd, title) for hwnd, title in winlist if screen_name in title.lower()]
    return screens

winlist = []
windows = get_screensbyname('firefox')
window = windows[0][0]

# 获取缩放因子
scale_x, scale_y = get_monitor_dpi(window)

# 获取逻辑坐标并转换为物理像素
wRect_logical = win32gui.GetWindowRect(window)
wRect_physical = (
    int(wRect_logical[0] * scale_x),
    int(wRect_logical[1] * scale_y),
    int(wRect_logical[2] * scale_x),
    int(wRect_logical[3] * scale_y)
)

cRect_logical = win32gui.GetClientRect(window)
cRect_physical = (
    int(cRect_logical[0] * scale_x),
    int(cRect_logical[1] * scale_y),
    int(cRect_logical[2] * scale_x),
    int(cRect_logical[3] * scale_y)
)

print('物理像素 GetWindowRect ', wRect_physical)
print('物理像素 GetCLientRect ', cRect_physical)

这个方法会根据窗口所在显示器的实际缩放比例,把Win32 API返回的逻辑坐标转换为真实的物理像素,适配不同显示器的缩放设置。

补充说明

  • 优先推荐方法一,因为启用DPI感知后,所有Win32 API都会直接返回物理像素,不需要额外的转换逻辑,更简洁可靠。
  • 如果你需要捕获窗口客户区内容,转换坐标后,就可以用这些物理像素值来正确截取画面,比如使用win32gui.BitBlt或者其他截图工具时,传入正确的尺寸和坐标。

内容的提问来源于stack exchange,提问作者Daniel Martin

火山引擎 最新活动