繁体   English   中英

如何将 tkinter 画布保存为图像

[英]How to save a tkinter canvas as an image

我想将我的绘图保存在 tkinter 画布上作为图像,以便我可以打开它以供以后使用。 我目前使用这篇文章中的这个保存系统,但这对我来说不是一个好方法。 首先,我需要添加一个偏移量,其次,如果我设置应用程序,那么实际上只有画布的某些部分是可见的,而在保存图像时,画布不可见的部分会显示为黑色。

你好

实际上只有画布的一部分是可见的。 如果我打开保存的图像,这就是它的样子在此处输入图像描述 只有可见的东西实际上是存在的(整个图像在保存之前是黄色的)。

保存图像的代码。

def save(widget(canvas), filelocation):
    x=root.winfo_rootx()+widget.winfo_x() + 74
    y=root.winfo_rooty()+widget.winfo_y() + 109
    x1=x+widget.winfo_width()
    y1=y+widget.winfo_height()
    ImageGrab.grab().crop((x,y,x1,y1)).save(filelocation)

主意

从这篇文章阅读后,它解释说我可以重新创建我在画布上绘制的所有东西。 所以我的想法是把我画的所有东西都放在画布上,比如我在一个不可见的图层上创建的线条,然后将它粘贴到图像上。 但是我不知道这是否可能(可能使用PILnumpycv2

代码(最小可重现)

import tkinter as tk
from tkinter import colorchooser, Canvas, N
from tkinter.ttk import *
from PIL import Image, ImageTk, ImageGrab
import keyboard

def save(widget, filelocation):
    x=root.winfo_rootx()+widget.winfo_x()
    y=root.winfo_rooty()+widget.winfo_y()
    x1=x+widget.winfo_width()
    y1=y+widget.winfo_height()
    ImageGrab.grab().crop((x,y,x1,y1)).save(filelocation)

def type_of(color):
    type_pen = 'marker'
    if type_pen == 'marker':
        pencil_motion_marker(color = color)

#pixel pen
def pencil_motion_marker(color):
    stage.bind('<Button-1>', get_pos_marker)
    stage.bind('<B1-Motion>', lambda event, color = color: pencil_draw_marker(event, color))

def get_pos_marker(event):
    global lastx, lasty
    
    lastx, lasty = event.x, event.y

def pencil_draw_marker(event, color):
    stage.create_line((lastx, lasty, event.x, event.y), width = width.get(), fill = color, capstyle = 'round')
    get_pos_marker(event)

def choose_pen_color():
    pencilcolor = colorchooser.askcolor(title = 'Pencil Color')
    type_of(pencilcolor[1])

##
        
def pencil_click():
    global width, opacity

    Whitepencolb = Button(optionsframe, text = 'Whitepencolimg', style = 'COLBG.TButton', command = lambda m = 'White': type_of(m))
    Whitepencolb.grid(row = 0, column = 0, padx = 10, pady = 1)
    
    Redpencolb = Button(optionsframe, text = 'Redpencolimg', style = 'COLBG.TButton', command = lambda m = 'Red': type_of(m))
    Redpencolb.grid(row = 1, column = 0, padx = 10, pady = 1)
    
    Magentapencolb = Button(optionsframe, text = 'Magentapencolimg', style = 'COLBG.TButton', command = lambda m = 'Magenta': type_of(m))
    Magentapencolb.grid(row = 0, column = 1, padx = 10, pady = 1)

    Limegreenpencolb = Button(optionsframe, text = 'Limegreenpencolimg', style = 'COLBG.TButton', command = lambda m = 'Lime': type_of(m))
    Limegreenpencolb.grid(row = 1, column = 1, padx = 10, pady = 1)
    
    Greenpencolb = Button(optionsframe, text = 'Greenpencolimg', style = 'COLBG.TButton', command = lambda m = 'Green': type_of(m))
    Greenpencolb.grid(row = 0, column = 2, padx = 10, pady = 1)
    
    Bluepencolb = Button(optionsframe, text = 'Bluepencolimg', style = 'COLBG.TButton', command = lambda m = 'Blue': type_of(m))
    Bluepencolb.grid(row = 1, column = 2, padx = 10, pady = 1)
    
    Cyanpencolb = Button(optionsframe, text = 'Cyanpencolimg', style = 'COLBG.TButton', command = lambda m = 'Cyan': type_of(m))
    Cyanpencolb.grid(row = 0, column = 3, padx = 10, pady = 1)
    
    Yellowpencolb = Button(optionsframe, text = 'Yellowpencolimg', style = 'COLBG.TButton', command = lambda m = 'Yellow': type_of(m))
    Yellowpencolb.grid(row = 1, column = 3, padx = 10, pady = 1)

    Orangepencolb = Button(optionsframe, text = 'Orangepencolimg', style = 'COLBG.TButton', command = lambda m = 'Orange': type_of(m))
    Orangepencolb.grid(row = 0, column = 4, padx = 10, pady = 1)

    Graypencolb = Button(optionsframe, text = 'Graypencolimg', style = 'COLBG.TButton', command = lambda m = 'Gray': type_of(m))
    Graypencolb.grid(row = 1, column = 4, padx = 10, pady = 1)

    Blackpencolb = Button(optionsframe, text = 'Blackpencolimg', style = 'COLBG.TButton', command = lambda m = 'Black': type_of(m))
    Blackpencolb.grid(row = 0, column = 5, padx = 10, pady = 1)

    Createnewpencolb = Button(optionsframe, text = 'Createnewpencolimg', style = 'COLBG.TButton', command = choose_pen_color)
    Createnewpencolb.grid(row = 1, column = 5, padx = 10, pady = 1)

    widthlabel = Label(optionsframe, text = 'Width: ', style = 'LABELBG.TLabel')
    width = Scale(optionsframe, from_ = 1, to = 20, style = 'SCALEBG.Horizontal.TScale')
    widthlabel.grid(row = 0, column = 6)
    width.grid(row = 0, column = 7)
    width.set(20)

    opacitylabel = Label(optionsframe, text = 'Opacity: ', style = 'LABELBG.TLabel')
    opacity = Scale(optionsframe, from_ = 0, to = 1.0, style = 'SCALEBG.Horizontal.TScale')
    opacitylabel.grid(row = 1, column = 6)
    opacity.grid(row = 1, column = 7)
    opacity.set(1.0)

def setup(filelocation):
    global stage, img_id, optionsframe, draw
    
    for widgets in root.winfo_children():
        widgets.destroy()

    root.config(bg = '#454545')
    iconsframewidth = int(screen_width / 20)
    
    frames = Style()
    frames.configure('FRAMES.TFrame', background = '#2a2a2a')
    sep = Style()
    sep.configure('SEP.TFrame', background = '#1a1a1a')
    style = Style()
    style.configure('STAGE.TFrame', background = '#454545')
    icon = Style()
    icon.configure('ICON.TButton', background = '#2a2a2a', foreground = '#2a2a2a')
    
    iconsframe = Frame(root, width = iconsframewidth, style = 'FRAMES.TFrame')
    iconsframe.pack(side = 'left', expand = False, fill = 'y')
    iconsframe.pack_propagate(0)
    sep1frame = Frame(root, style = 'SEP.TFrame', width = 5)
    sep1frame.pack(side = 'left', expand = False, fill = 'y')
    optionsframe = Frame(root, style = 'FRAMES.TFrame', height = 100)
    optionsframe.pack(side = 'top', expand = False, fill = 'x')
    optionsframe.pack_propagate(0)
    sep2frame = Frame(root, style = 'SEP.TFrame', height = 5)
    sep2frame.pack(side = 'top', expand = False, fill = 'x')
    propertyframe = Frame(root, style = 'FRAMES.TFrame', width = 150)
    propertyframe.pack(side = 'right', expand = False, fill = 'y')
    propertyframe.pack_propagate(0)
    sep3frame = Frame(root, style = 'SEP.TFrame', width = 5)
    sep3frame.pack(side = 'right', expand = False, fill = 'y')
    stageframe = Frame(root, style = 'STAGE.TFrame')
    stageframe.pack(side = 'top', expand = True, fill = 'both')
    stageframe.pack_propagate(0)

    image = Image.open(filelocation)
    width, height = image.size

    stage = Canvas(stageframe, width = width, height = height)
    stage.pack(side="top", anchor = 'c', expand=True)

    root.update()

    keyboard.add_hotkey("ctrl+s", lambda widget = stage, filelocation = filelocation: save(widget, filelocation))

    pencilbutton = Button(iconsframe, text = 'pencilimg', command = pencil_click, style = 'ICON.TButton')
    pencilbutton.pack(anchor = N, pady = 10)

    imgtk = ImageTk.PhotoImage(Image.open(filelocation)) 
    img_id = stage.create_image(stage.winfo_width() / 2, stage.winfo_height() / 2, image = imgtk)
    stage.image = imgtk

root = tk.Tk()
root.title('App')

screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()

w = 1150
h = 600
x = (screen_width / 2) - (w / 2)
y = (screen_height / 2) - (h / 2)

root.geometry('%dx%d+%d+%d' % (w, h, x, y))
root.minsize(1150, 600)

setup('Test.png')
root.mainloop()

图片

在此处输入图像描述

小问题

回复@Claudio:我现在正在使用屏幕截图技术将 Canvas 作为图像保存到文件中。 我注意到保存的画布图像在角落看起来像这样在此处输入图像描述 保存并重新打开图像后,它看起来像这样在此处输入图像描述 (画布的边框增加了画布图像的大小)。

更新 2. 2022 年 6 月:已接受答案中提供的更新代码解决了小问题

如何将 tkinter Canvas 图形保存为图像?

tkinter 似乎没有提供直接的方法来获取 Canvas 图形的图像以将其保存到图像文件中,这似乎是一个事实。 有两种方法可以解决这个问题,只需要导入 Python PIL 模块(Pillow)。

其中一种方法是在 Canvas 区域执行绘画的屏幕截图,这可以使用PIL.ImageGrab.grab()或任何其他执行(裁剪)屏幕截图并将它们保存到图像文件的各种方法来完成(参见例如Python 屏幕一小部分的快速屏幕截图,用于 Python 屏幕截图模块,速度足够快,可以在 Canvas 上制作进度绘画的视频)。

另一种方法是在 Python PIL 图像上绘画,使用修改后的 PIL 图像更新 tkinter Canvas,然后使用可用于保存 PIL 图像对象的.save()方法将其保存到文件中。

如果save()同时使用 Frame ( stageframe ) 和 Canvas ( stage ) 小部件来获取正确的 x,y 值以裁剪屏幕截图,则问题中提供的代码通常按预期工作,以防 Canvas 放置在如果用于裁剪屏幕截图的边界框考虑到 tkinter Canvas 小部件大小包括 Canvas 边框和 Canvas 高亮边框,则框架和。

下面的代码是问题中提供的代码,带有一些添加的注释和适当的修改。 它不需要键盘模块,并通过单击由pencilbutton pencil_click()函数处理的最左上角铅笔按钮将修改后的Canvas 保存为图像文件。 它提供了将 tkinter Canvas 的图形保存到图像文件的两种方法。 通过为全局method变量( method = 'screenshot'method = 'imagepaint' )分配适当的值来选择其中之一:

# https://stackoverflow.com/questions/72459847/how-to-save-a-tkinter-canvas-as-an-image
from tkinter     import Tk, colorchooser, Canvas, N, PhotoImage
from tkinter.ttk import Style, Frame, Button, Label, Scale
from PIL import Image, ImageTk   # required to load images in tkinter
# method = 'screenshot' or 'imagepaint'
method = 'screenshot'
borderthickness_bd = 2
highlightthickness = 1
if method == 'imagepaint': 
    from PIL import ImageDraw  # required to draw on the image 
if method == 'screenshot': 
    from PIL import ImageGrab  # required for the screenshot
filelocation = 'Test.png'
savelocation = 'Test_.png'
def save(stageframe, stage, savelocation):
    if method == 'imagepaint': 
        global image
        image.save(savelocation)
    if method == 'screenshot':
        global borderthickness_bd,  highlightthickness
        brdt = borderthickness_bd + highlightthickness
        # +1 and -2 because of thicknesses of Canvas borders (bd-border and highlight-border):
        x=root.winfo_rootx()+stageframe.winfo_x()+stage.winfo_x() +1*brdt
        y=root.winfo_rooty()+stageframe.winfo_y()+stage.winfo_y() +1*brdt
        x1=x+stage.winfo_width() -2*brdt
        y1=y+stage.winfo_height()-2*brdt
        ImageGrab.grab().crop((x,y,x1,y1)).save(savelocation)

def type_of(color):
    type_pen     = 'marker'
    if type_pen == 'marker':
        pencil_motion_marker(color = color)

#pixel pen
def pencil_motion_marker(color):
    stage.bind('<Button-1>' , get_pos_marker)
    stage.bind('<B1-Motion>', lambda event, color = color: pencil_draw_marker(event, color))

def get_pos_marker(event):
    global lastx, lasty
    lastx, lasty = event.x, event.y

def pencil_draw_marker(event, color):
    global method, lastx, lasty, draw, image, img_id
    # print( (lastx, lasty, event.x, event.y), color, int(width.get()) )
    if method == 'screenshot': 
        stage.create_line((lastx, lasty, event.x, event.y), width = width.get(), fill = color, capstyle = 'round')
        get_pos_marker(event)
    if method == 'imagepaint':
        w12 = int(width.get()/2)
        draw.ellipse( (event.x-w12, event.y-w12, event.x+w12, event.y+w12), fill=color )
        imgtk  = ImageTk.PhotoImage(image)
        stage.itemconfig(img_id, image=imgtk)
        stage.image = imgtk

def choose_pen_color():
    pencilcolor = colorchooser.askcolor(title = 'Pencil Color')
    type_of(pencilcolor[1])

##
        
def pencil_click():
    
    global width, opacity, stageframe, stage, savelocation

    # imgToSave = stage.image                                      # gives a PhotoImage object
    # imgToSave._PhotoImage__photo.write("Test.gif", format='gif') # which can be saved, but ...
    #                                ^--- ... with no painting done on Canvas - only the image.

    save(stageframe, stage, savelocation)
    
    Whitepencolb = Button(optionsframe, text = 'Whitepencolimg', style = 'COLBG.TButton', command = lambda m = 'White': type_of(m))
    Whitepencolb.grid(row = 0, column = 0, padx = 10, pady = 1)
    
    Redpencolb = Button(optionsframe, text = 'Redpencolimg', style = 'COLBG.TButton', command = lambda m = 'Red': type_of(m))
    Redpencolb.grid(row = 1, column = 0, padx = 10, pady = 1)
    
    Magentapencolb = Button(optionsframe, text = 'Magentapencolimg', style = 'COLBG.TButton', command = lambda m = 'Magenta': type_of(m))
    Magentapencolb.grid(row = 0, column = 1, padx = 10, pady = 1)

    Limegreenpencolb = Button(optionsframe, text = 'Limegreenpencolimg', style = 'COLBG.TButton', command = lambda m = 'Lime': type_of(m))
    Limegreenpencolb.grid(row = 1, column = 1, padx = 10, pady = 1)
    
    Greenpencolb = Button(optionsframe, text = 'Greenpencolimg', style = 'COLBG.TButton', command = lambda m = 'Green': type_of(m))
    Greenpencolb.grid(row = 0, column = 2, padx = 10, pady = 1)
    
    Bluepencolb = Button(optionsframe, text = 'Bluepencolimg', style = 'COLBG.TButton', command = lambda m = 'Blue': type_of(m))
    Bluepencolb.grid(row = 1, column = 2, padx = 10, pady = 1)
    
    Cyanpencolb = Button(optionsframe, text = 'Cyanpencolimg', style = 'COLBG.TButton', command = lambda m = 'Cyan': type_of(m))
    Cyanpencolb.grid(row = 0, column = 3, padx = 10, pady = 1)
    
    Yellowpencolb = Button(optionsframe, text = 'Yellowpencolimg', style = 'COLBG.TButton', command = lambda m = 'Yellow': type_of(m))
    Yellowpencolb.grid(row = 1, column = 3, padx = 10, pady = 1)

    Orangepencolb = Button(optionsframe, text = 'Orangepencolimg', style = 'COLBG.TButton', command = lambda m = 'Orange': type_of(m))
    Orangepencolb.grid(row = 0, column = 4, padx = 10, pady = 1)

    Graypencolb = Button(optionsframe, text = 'Graypencolimg', style = 'COLBG.TButton', command = lambda m = 'Gray': type_of(m))
    Graypencolb.grid(row = 1, column = 4, padx = 10, pady = 1)

    Blackpencolb = Button(optionsframe, text = 'Blackpencolimg', style = 'COLBG.TButton', command = lambda m = 'Black': type_of(m))
    Blackpencolb.grid(row = 0, column = 5, padx = 10, pady = 1)

    Createnewpencolb = Button(optionsframe, text = 'Createnewpencolimg', style = 'COLBG.TButton', command = choose_pen_color)
    Createnewpencolb.grid(row = 1, column = 5, padx = 10, pady = 1)

    widthlabel = Label(optionsframe, text = 'Width: ', style = 'LABELBG.TLabel')
    width = Scale(optionsframe, from_ = 1, to = 100, style = 'SCALEBG.Horizontal.TScale')
    widthlabel.grid(row = 0, column = 6)
    width.grid(row = 0, column = 7)
    width.set(20)

    opacitylabel = Label(optionsframe, text = 'Opacity: ', style = 'LABELBG.TLabel')
    opacity = Scale(optionsframe, from_ = 0, to = 1.0, style = 'SCALEBG.Horizontal.TScale')
    opacitylabel.grid(row = 1, column = 6)
    opacity.grid(row = 1, column = 7)
    opacity.set(1.0)

def setup(filelocation):
    global stage, stageframe, img_id, optionsframe, draw, image, img_id, method
    global borderthickness_bd, highlightthickness
    
    for widgets in root.winfo_children():
        widgets.destroy()

    root.config(bg = '#454545')
    iconsframewidth = int(screen_width / 20)
    
    frames = Style()
    frames.configure('FRAMES.TFrame', background = '#2a2a2a')
    sep = Style()
    sep.configure('SEP.TFrame', background = '#1a1a1a')
    style = Style()
    style.configure('STAGE.TFrame', background = '#454545')
    icon = Style()
    icon.configure('ICON.TButton', background = '#2a2a2a', foreground = '#2a2a2a')
    
    iconsframe = Frame(root, width = iconsframewidth, style = 'FRAMES.TFrame')
    iconsframe.pack(side = 'left', expand = False, fill = 'y')
    iconsframe.pack_propagate(0)
    sep1frame = Frame(root, style = 'SEP.TFrame', width = 5)
    sep1frame.pack(side = 'left', expand = False, fill = 'y')
    optionsframe = Frame(root, style = 'FRAMES.TFrame', height = 100)
    optionsframe.pack(side = 'top', expand = False, fill = 'x')
    optionsframe.pack_propagate(0)
    sep2frame = Frame(root, style = 'SEP.TFrame', height = 5)
    sep2frame.pack(side = 'top', expand = False, fill = 'x')
    propertyframe = Frame(root, style = 'FRAMES.TFrame', width = 150)
    propertyframe.pack(side = 'right', expand = False, fill = 'y')
    propertyframe.pack_propagate(0)
    sep3frame = Frame(root, style = 'SEP.TFrame', width = 5)
    sep3frame.pack(side = 'right', expand = False, fill = 'y')
    stageframe = Frame(root, style = 'STAGE.TFrame')
    stageframe.pack(side = 'top', expand = True, fill = 'both')
    stageframe.pack_propagate(0)

    image = Image.open(filelocation)
    width, height = image.size
    if method == 'imagepaint': 
        draw = ImageDraw.Draw(image)

    imgtk  = ImageTk.PhotoImage(image)
    # width, height = imgtk._PhotoImage__size
    
    # imgtk  = PhotoImage(filelocation)
    #   ^--- no width, hight information ???
    
    stage = Canvas(stageframe, width = width, height = height, bd=borderthickness_bd, highlightthickness=highlightthickness) # default: bd=2, highlightthickness=1
    stage.pack(side="top", anchor = 'c', expand=True)

    root.update()

    # keyboard.add_hotkey("ctrl+s", lambda widget = stageframe, filelocation = filelocation: save(widget, filelocation))

    pencilbutton = Button(iconsframe, text = 'pencilimg', command = pencil_click, style = 'ICON.TButton')
    pencilbutton.pack(anchor = N, pady = 10)

    img_id = stage.create_image(stage.winfo_width() / 2, stage.winfo_height() / 2, image = imgtk)
    stage.image = imgtk

root = Tk()
root.title('App')

screen_width  = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()

w = 1150
h =  600
x = (screen_width  / 2) - (w / 2)
y = (screen_height / 2) - (h / 2)

root.geometry('%dx%d+%d+%d' % (w, h, x, y))
root.minsize(1150, 600)

setup(filelocation)
root.mainloop()

裁剪屏幕截图作为保存 tkinter Canvas 图形的一种方式比在更新 tkinter Canvas 的 PIL 图像上绘画更可取,因为后者具有减慢图形速度的副作用,因此绘画平滑度会受到影响。

要查看如何更改savebutton pencilbutton ,请查看Python tkinter: error _tkinter.TclError: bad window path name ".!button2"以了解它是如何实现的完毕。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM