簡體   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