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

使用PIL Image/ImageDraw保存Tkinter Canvas绘图为图片的问题

问题解决:Tkinter Canvas绘图无法正确保存为图片

问题根源

你代码里的核心问题有两个:

  1. 初始化时机错误:创建PIL Image时,Tkinter的Canvas还没完成布局渲染,draw_panel.winfo_width()和draw_panel.winfo_height()返回的初始值是1x1,导致生成的Image尺寸极小,保存后只显示一个点。
  2. 尺寸未同步:就算窗口后续调整大小,PIL Image的尺寸也不会自动更新,始终保持初始的1x1。

解决方案一:直接导出Canvas内容(推荐)

不需要维护PIL的绘图层,直接利用Tkinter Canvas自带的postscript方法导出内容,再转成PIL Image保存。这种方法不需要同步两个绘图逻辑,更简洁可靠。

修改后的完整代码:

from tkinter import Tk, Canvas
from PIL import Image

X = 0
Y = 0

app = Tk()
app.geometry("800x600")  # 提前设置窗口尺寸,可选

def set_xy(click):
    global X, Y
    X, Y = click.x, click.y

def draw_line(drag):
    global X, Y
    draw_panel.create_line((X, Y, drag.x, drag.y), fill="black", capstyle="round", smooth=True)
    X, Y = drag.x, drag.y

def save(event=None):
    # 导出Canvas为postscript格式
    ps = draw_panel.postscript(colormode='color')
    # 转成PIL Image
    img = Image.open(ps)
    # 保存为图片
    img.save("canvas_drawing.png", "png")

# App's drawing panel
draw_panel = Canvas(app, background="white", cursor="dot")
draw_panel.place(x=0, relwidth=1.0, y=0, relheight=1.0)

# Mouse and Key bindings
draw_panel.bind('<Button-1>', set_xy)
draw_panel.bind('<B1-Motion>', draw_line)
app.bind('<s>', save)  # 按's'保存

app.mainloop()

解决方案二:同步PIL Image与Canvas尺寸

如果你坚持要使用双绘图层(同时在Canvas和PIL Image上绘图),需要修正初始化时机,并添加窗口大小变化的同步逻辑:

from tkinter import Tk, Canvas
from PIL import Image, ImageDraw

X = 0
Y = 0
image = None
draw = None

app = Tk()
app.geometry("800x600")

def update_image_size(event=None):
    global image, draw
    # 获取Canvas当前尺寸
    width = draw_panel.winfo_width()
    height = draw_panel.winfo_height()
    # 重新创建Image和Draw对象
    image = Image.new("RGB", (width, height), "white")
    draw = ImageDraw.Draw(image)

def set_xy(click):
    global X, Y
    X, Y = click.x, click.y

def draw_line(drag):
    global X, Y
    draw_panel.create_line((X, Y, drag.x, drag.y), fill="black", capstyle="round", smooth=True)
    draw.line([(X, Y), (drag.x, drag.y)], fill="black", joint="curve")
    X, Y = drag.x, drag.y

def save(event=None):
    image.save("image.png", "png")

# App's drawing panel
draw_panel = Canvas(app, background="white", cursor="dot")
draw_panel.place(x=0, relwidth=1.0, y=0, relheight=1.0)

# 等窗口渲染完成后初始化Image
app.after(100, update_image_size)
# 窗口大小变化时同步Image尺寸
draw_panel.bind('<Configure>', update_image_size)

# Mouse and Key bindings
draw_panel.bind('<Button-1>', set_xy)
draw_panel.bind('<B1-Motion>', draw_line)
app.bind('<s>', save)

app.mainloop()

改动说明

  1. 使用app.after(100, update_image_size)延迟初始化Image,确保Canvas已经完成布局,能获取正确的尺寸。
  2. 添加<Configure>事件绑定,当Canvas大小变化时自动更新PIL Image的尺寸。

验证说明

两种方案都能完整保存Canvas上的所有绘图内容,解决你遇到的“仅显示一个点”的问题。方案一更简洁,不需要维护额外的绘图逻辑,优先推荐。

内容的提问来源于stack exchange,提问作者passion-lab

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 01:15:52