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
相关产品推荐
相关产品推荐

