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




