from __future__ import annotations import io from dataclasses import dataclass from typing import Optional @dataclass(frozen=True) class CaptureResult: image_bytes: bytes # 截图区域在“屏幕坐标系”中的位置(left, top, width, height) region: tuple[int, int, int, int] # 截图图片像素坐标 -> 屏幕坐标 的缩放系数 scale_x: float scale_y: float def capture_screen(region: Optional[tuple[int, int, int, int]] = None, *, prefer_mss: bool = True) -> CaptureResult: """ 截取屏幕(全屏或局部),返回 PNG bytes + region + 像素到屏幕坐标的缩放。 - region: (left, top, width, height) 使用屏幕坐标 - scale_x/scale_y: 用于处理高 DPI 下 mss 取到的物理像素与 pyautogui 屏幕坐标不一致的问题 """ if prefer_mss: try: return _capture_with_mss(region) except Exception: # 回退到 pyautogui(更贴近“屏幕坐标系”) return _capture_with_pyautogui(region) return _capture_with_pyautogui(region) def _capture_with_pyautogui(region: Optional[tuple[int, int, int, int]]) -> CaptureResult: import pyautogui img = pyautogui.screenshot(region=region) # type: ignore[arg-type] buf = io.BytesIO() img.save(buf, format="PNG") if region is None: w, h = img.size region2 = (0, 0, int(w), int(h)) else: region2 = (int(region[0]), int(region[1]), int(region[2]), int(region[3])) return CaptureResult(image_bytes=buf.getvalue(), region=region2, scale_x=1.0, scale_y=1.0) def _capture_with_mss(region: Optional[tuple[int, int, int, int]]) -> CaptureResult: import mss import mss.tools import pyautogui with mss.mss() as sct: if region is None: mon = sct.monitors[1] # 主屏 left, top, width, height = int(mon["left"]), int(mon["top"]), int(mon["width"]), int(mon["height"]) else: left, top, width, height = (int(region[0]), int(region[1]), int(region[2]), int(region[3])) shot = sct.grab({"left": left, "top": top, "width": width, "height": height}) png_bytes = mss.tools.to_png(shot.rgb, shot.size) # DPI 缩放处理:mss 的像素尺寸可能与 pyautogui 的屏幕坐标尺寸不同 screen_w, screen_h = pyautogui.size() mon = sct.monitors[1] mon_w, mon_h = int(mon["width"]), int(mon["height"]) # 若 mss 返回的是物理像素,mon_w/mon_h 往往大于 pyautogui.size() scale_x = float(screen_w) / float(mon_w) if mon_w else 1.0 scale_y = float(screen_h) / float(mon_h) if mon_h else 1.0 return CaptureResult( image_bytes=png_bytes, region=(left, top, width, height), scale_x=scale_x, scale_y=scale_y, )