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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 01:03:17