I would like to save my drawing on a tkinter canvas as an image so I can open it for later use. I currently use this save system from this post however this is not a good way for me. First I would need to add an offset and second if i set the application so only some part of the canvas is actually visible, the part where the canvas is not visible appears black when saving the image.
only part of the canvas is actually visible. If I open the saved image this is what it looks like only what was visible is actually there(the entire image was yellow before saving it).
The code of saving the image.
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)
Idea
After reading from this post it explains i could recreate all the stuff i drew on the canvas. So my idea is to put all the stuff i drew on the canvas such as lines i created on an invisible layer and paste it on the image. However i dont know if this is possible(may be possible with PIL
, numpy
or cv2
)
Code(Is minimal reproducable)
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()
Image
Small Problem
Replying to @Claudio : I am using the screenshot technique for saving Canvas as an image to a file right now. I noticed that the saved canvas image looks like this at the corner and after saving and reopening the image it looks like this
( the border of the canvas increases the size of the canvas image ).
Update 2. June 2022: Small Problem Solved by the updated code provided in the accepted answer.