You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Pillow库ImageGrab.crop方法未按指定坐标裁剪如何解决?

问题根因

这个偏移是Windows系统高DPI缩放导致的坐标不匹配:

  • 你用Tkinter的winfo_rootx()/winfo_width()等方法拿到的是系统做了缩放适配后的逻辑坐标,不是真实物理像素坐标
  • Pillow的ImageGrab.grab()抓取的是屏幕真实物理像素的坐标,两者之间差了一个系统缩放系数(比如你开了150%缩放,逻辑坐标和物理坐标的比值就是1:1.5),所以裁剪区域越靠右靠下,偏差就会越明显。

解决方案

方案1:给程序添加DPI感知(最省心)

在初始化Tkinter根窗口前,调用Windows API声明程序支持DPI适配,让Tkinter直接返回物理像素坐标,后续所有坐标计算都不用改:

import ctypes

# 适配高DPI,优先用这个,支持不同显示器不同缩放比例
ctypes.windll.shcore.SetProcessDpiAwareness(2)
# 旧版Windows系统可以用下面的兼容方法
# ctypes.windll.user32.SetProcessDPIAware()

# 之后再初始化你的root = Tk()等逻辑

注意:开启DPI感知后,Tkinter窗口和控件可能会比之前小,这是因为原来系统给你做了强制放大,你只要调整控件的宽高参数到预期大小即可。

方案2:手动计算缩放系数换算坐标

如果不想改程序的DPI适配逻辑,可以手动获取系统缩放比例,把你计算出来的坐标统一乘以缩放系数后再传给crop():

# 先获取系统缩放比例
def get_dpi_scale():
    import ctypes
    user32 = ctypes.windll.user32
    gdi32 = ctypes.windll.gdi32
    dc = user32.GetDC(None)
    phy_width = gdi32.GetDeviceCaps(dc, 118)
    log_width = gdi32.GetDeviceCaps(dc, 8)
    user32.ReleaseDC(None, dc)
    return phy_width / log_width

# 调整你的裁剪坐标计算逻辑
def get_drawing():
    widget = cv
    scale = get_dpi_scale()
    x = int((root.winfo_rootx() + widget.winfo_x()) * scale)
    y = int((root.winfo_rooty() + widget.winfo_y()) * scale)
    x1 = int(x + widget.winfo_width() * scale)
    y1 = int(y + widget.winfo_height() * scale)

    ImageGrab.grab().crop((x, y, x1, y1)).save("test.png")

方案3:直接导出Canvas内容(完全避开截图坐标问题)

你要提取的是Canvas上的绘制内容,其实完全不用截图,直接用Tkinter自带的postscript方法导出画布内容,坐标100%匹配:

def get_drawing():
    # 直接导出画布内容为postscript格式
    cv.postscript(file="draw_temp.ps", colormode="gray")
    # 用Pillow转成PNG
    from PIL import Image
    img = Image.open("draw_temp.ps")
    img.save("test.png")

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 22:54:04