ImageGrab.grab()截取Tkinter画布区域偏移问题及解决
Tkinter画布ImageGrab截图偏移问题
已修复:解决方案见文末。
问题描述
我尝试使用ImageGrab.grab()方法导出、保存Tkinter画布内容,但保存的图像始终存在明显的像素偏移,测试了多个网上公开的可运行示例以及自行编写的实现代码,均无法得到正确的截取区域。
测试代码
整画布导出测试代码
以下是可运行的测试示例(采用crop裁剪实现方式),代码末尾附上了已尝试过的多种公开实现方案,运行前需将代码中图片路径替换为本地目录下的可用图片:
from tkinter import * from PIL import ImageTk, Image import tkinter as tk import pyscreenshot as ImageGrab def exportCanvas(event=1): print("exporting....") ImageGrab.grab().crop((canvas.winfo_rootx(), canvas.winfo_rooty() ,canvas.winfo_rootx()+canvas.winfo_width(), canvas.winfo_rooty()+canvas.winfo_height())).save("testImageGrab.png") print("... export ended.") # 创建主窗口与画布 root = tk.Tk() canvas = tk.Canvas(root, width=400, height=400, background="bisque") canvas.pack(fill=BOTH, expand=1) # 加载本地图片,需将路径替换为本地可用图片路径 imageTemp=Image.open("text.png") schematicImage=ImageTk.PhotoImage(imageTemp) imageAffichee=canvas.create_image(0,0,image=schematicImage) # 绑定q键触发画布保存 root.bind("<Key-q>",exportCanvas) root.mainloop() """ 已尝试但无法得到正确截图区域的方案: 1. 直接传入bbox参数调用grab box = (canvas.winfo_rootx(),canvas.winfo_rooty(),canvas.winfo_rootx()+canvas.winfo_width(),canvas.winfo_rooty() + canvas.winfo_height()) grab = ImageGrab.grab(bbox = box) grab.save("testImageGrab.png") 2. 叠加窗口与画布偏移量计算坐标后裁剪 x=root.winfo_rootx()+canvas.winfo_x() y=root.winfo_rooty()+canvas.winfo_y() x1=x+canvas.winfo_width() y1=y+canvas.winfo_height() ImageGrab.grab().crop((x,y,x1,y1)).save("testImageGrab.png") 3. 直接使用画布根坐标作为bbox参数 ImageGrab.grab(bbox=( canvas.winfo_rootx(), canvas.winfo_rooty(), canvas.winfo_rootx() + canvas.winfo_width(), canvas.winfo_rooty() + canvas.winfo_height() )).save("testImageGrab.png") 4. 叠加窗口与画布偏移量后传入bbox参数 x = root.winfo_rootx() + canvas.winfo_x() y = root.winfo_rooty() + canvas.winfo_y() xx = x + canvas.winfo_width() yy = y + canvas.winfo_height() ImageGrab.grab(bbox=(x, y, xx, yy)).save("testImageGrab.png") """
上述代码预期可以完整截取并保存所有可见的画布区域,但实际运行得到的截图存在偏移。
框选区域导出测试代码
此外我还编写了支持框选区域保存画布的程序,实现逻辑为:先截取整个屏幕(该功能单独测试可正常运行),再根据鼠标选择的区域进行裁剪。理论上event.x和event.y会返回鼠标在屏幕上的坐标值,而非画布上的坐标,符合全屏截图后裁剪的坐标需求,对应代码如下:
from tkinter import * from PIL import ImageTk, Image import tkinter as tk import pyscreenshot as ImageGrab def qPressed(event=1): global started started = 1 # 修改光标样式提示进入框选状态 root.config(cursor="cross") def B1Motion (event=1): global started, initPos, x, y if started: if (initPos==0): # 记录首次按下鼠标的坐标 x=event.x y=event.y initPos=1 def B1Release (event=1): global started, initPos, x, y if started: if initPos: print("exporting....") # 根据鼠标按下和释放的坐标裁剪截图 ImageGrab.grab().crop((x, y, event.x, event.y)).save("testImageGrab.png") print("... export ended.") # 重置参数 x=0 y=0 initPos=0 started=0 root.config(cursor="arrow") # 创建主窗口与画布 root = tk.Tk() canvas = tk.Canvas(root, width=400, height=400, background="bisque") canvas.pack(fill=BOTH, expand=1) # 初始化状态变量 started=0 initPos=0 # 加载本地图片,需将路径替换为本地可用图片路径 imageTemp=Image.open("text.png") schematicImage=ImageTk.PhotoImage(imageTemp) imageAffichee=canvas.create_image(0,0,image=schematicImage) # 绑定事件 root.bind("<Key-q>",qPressed) root.bind("<B1-Motion>",B1Motion) root.bind("<ButtonRelease-1>",B1Release) root.mainloop()
问题根因与解决方法
- 根因定位:系统开启了应用缩放(本机设置为150%),Tkinter返回的窗口、控件坐标为逻辑坐标,和截图工具读取的物理像素坐标存在固定缩放比例差,直接使用逻辑坐标裁剪就会出现像素偏移。
- 临时修复方案:将所有参与裁剪区域计算的坐标值乘以当前系统的缩放比例,即可修正偏移问题。
- 后续优化方向:可进一步研究如何直接读取系统的应用缩放值实现自动适配,无需手动配置缩放参数;在未开启自定义系统缩放的环境下,上述给出的所有代码可直接正常运行。
内容的提问来源于stack exchange,提问作者GaetanG
相关产品推荐
相关产品推荐

