[英]Tkinter save canvas and load
我编写了一个代码,它创建了 canvas 并使您能够加载图片。 您还可以在此图片中添加徽标和文本并拖动。 (类似于照片水印应用程序)毕竟,您可以将所有工作保存为 PNG。 格式。 我想要做的是,我想将所有数据保存为 canvas object,然后我可以加载这个 canvas ZA8CFDE63311BD59EB66666AC9 继续工作。 也许我想更改加载的对象/画布上的文本或徽标坐标。
我不知道把它保存为 canvas object 是对的。 如果您在这个主题上帮助我,我将不胜感激。
这是我的代码。
from PIL import Image, ImageTk, ImageGrab
import tkinter as tk
from tkinter.filedialog import askopenfilename, asksaveasfilename, asksaveasfile
def buttonpress(function, *args):
value = function(*args)
print(type(value))
def open_file():
img_path = askopenfilename(title="Select A File", filetype=(("jpeg files", "*.jpg"), ("all files", "*.*")))
if img_path:
canvas.imageList = [] # To get rid of garbage collector, empty list is added
img = Image.open(img_path)
img = img.resize((750, 500), Image.ANTIALIAS)
image = ImageTk.PhotoImage(img)
created_image_id = canvas.create_image(450, 250, image=image)
canvas.imageList.append(image)
# Change button displays
browse_button.grid_forget()
logo_button.grid(column=1, row=5)
text_button.grid(column=4, row=5)
save_button.grid(column=7, row=5)
return created_image_id
def add_logo():
logo_path = askopenfilename(title="Select A File", filetype=(("jpeg files", "*.jpg"), ("all files", "*.*")))
if logo_path:
canvas.logoList = [] # To get rid of garbage collector, empty list is added
logo = Image.open(logo_path)
logo = logo.resize((200, 200), Image.ANTIALIAS)
logo = ImageTk.PhotoImage(logo)
canvas.create_image(450, 250, image=logo)
canvas.logoList.append(logo)
# Change button displays
logo_button.grid_forget()
text_button.grid(column=3, row=5)
save_button.grid(column=6, row=5)
def on_drag(event):
if canvas.selected != 1:
# Calculate distance moved from last position
dx, dy = event.x - canvas.startxy[0], event.y - canvas.startxy[1]
# Move the selected item
canvas.move(canvas.selected, dx, dy)
# Update last position
canvas.startxy = (event.x, event.y)
def on_click(event):
selected = canvas.find_overlapping(event.x - 10, event.y - 10, event.x + 10,
event.y + 10) # List of selected items with mouse
canvas.startxy = (event.x, event.y) # Define "startxy" variable in canvas class
if selected:
canvas.selected = selected[-1] # Select the top-most item #define "selected" variable in canvas class
else:
canvas.selected = None
def add_text():
canvas.create_text(100, 10, fill="darkblue", font="Times 20 italic bold",
text="Click the bubbles that are multiples of two.")
def save_pic(widget):
file = asksaveasfilename(filetypes=[('Portable Network Graphics', '*.png')])
x = root.winfo_rootx() + 75
y = root.winfo_rooty()
x1 = x + widget.winfo_width() - 175
y1 = y + widget.winfo_height()
ImageGrab.grab().crop((x, y, x1, y1)).save(file + ".png")
root = tk.Tk()
root.geometry("900x600")
root.resizable(0, 0)
root.title("Photo Watermark Application")
canvas = tk.Canvas(root, width=900, height=500)
canvas.grid(column=0, row=0, columnspan=9, rowspan=5)
# Browse button
browse_text = tk.StringVar()
browse_button = tk.Button(root, command=lambda: buttonpress(open_file), textvariable=browse_text, font="Ariel",
bg="black",
fg="white",
height=2, width=10)
browse_text.set("Browse")
browse_button.grid(column=4, row=5)
# Add logo button
add_logo_text = tk.StringVar()
logo_button = tk.Button(root, command=add_logo, textvariable=add_logo_text, font="Ariel", bg="black", fg="white",
height=2, width=10)
add_logo_text.set("Add Logo")
# Add text button
add_text_text = tk.StringVar()
text_button = tk.Button(root, command=add_text, textvariable=add_text_text, font="Ariel", bg="black", fg="white",
height=2, width=10)
add_text_text.set("Add Text")
# Add save picture button
save_text_text = tk.StringVar()
save_button = tk.Button(root, command=lambda: save_pic(canvas), textvariable=save_text_text, font="Ariel", bg="black",
fg="white",
height=2, width=10)
save_text_text.set("Save Picture")
root.bind("<B1-Motion>", on_drag) # B1-MOTION = Dragging items using mouse
root.bind("<Button-1>", on_click) # BUTTON-1 = Left click with mouse
root.mainloop()
好的,所以我举了一个小例子来说明如何做到这一点(将逐步介绍代码):
import json
from tkinter import Tk, Canvas
class Circle:
def __init__(self, parent: "Canvas", x, y):
self.parent = parent
self.x = x
self.y = y
self.width = 50
self.height = 50
self.type = 'circle'
self.allow_move = False
self.move_offset_x = 0
self.move_offset_y = 0
self.circle = self.parent.create_oval(self.x, self.y, self.x + self.width, self.y + self.height, fill='black')
def move(self, x, y):
x, y = x - self.move_offset_x, y - self.move_offset_y
self.x, self.y = x, y
self.parent.coords(self.circle, x, y, x + self.width, y + self.height)
def check_coords(event=None):
for obj in obj_lst:
if obj.x < event.x < obj.x + obj.width and obj.y < event.y < obj.y + obj.width:
obj.allow_move = True
obj.move_offset_x = event.x - obj.x
obj.move_offset_y = event.y - obj.y
break
def move(event=None):
for obj in obj_lst:
if obj.allow_move:
obj.move(event.x, event.y)
def set_moving_false(event=None):
for obj in obj_lst:
if obj.allow_move:
obj.allow_move = False
def create_obj(event=None):
obj_lst.append(Circle(canvas, event.x, event.y))
def save(event=None):
with open('untitled.canvas', 'w') as file:
obj_dict = {f'{obj.type} {id}': (obj.x, obj.y) for id, obj in enumerate(obj_lst)}
json.dump(obj_dict, file)
root = Tk()
canvas = Canvas(root, width=500, height=400)
canvas.pack()
obj_type_dict = {'circle': Circle}
try:
with open('untitled.canvas') as file:
obj_dict = json.load(file)
obj_lst = []
for obj_type, attributes in obj_dict.items():
obj = obj_type_dict[obj_type.split()[0]](canvas, attributes[0], attributes[1])
obj_lst.append(obj)
except FileNotFoundError:
with open('untitled.canvas', 'w') as file:
pass
obj_lst = []
canvas.bind('<Button-1>', check_coords)
canvas.bind('<B1-Motion>', move)
canvas.bind('<ButtonRelease-1>', set_moving_false)
canvas.bind('<Button-3>', create_obj)
root.bind('<Control-s>', save)
root.mainloop()
首先从导入开始,我选择使用json
因为它是可能的并且pickle
有一些安全问题所以为什么不使用json
。 还有来自tkinter
的东西。
然后我定义了一个 class 来解释圆形对象(简单的对象,只有一种尺寸,没有什么花哨的)。
然后使用绑定和一些函数(只是一些移动的东西)来处理大多数。
重要的部分是读取和创建文件。
所以绑定到Control + s
是一个 function 保存文件( save()
)。 首先,它从屏幕上的对象列表中读取。 (它们通过create_obj()
函数附加在那里)它将它们的类型和坐标存储在字典中,然后将其转储到文件中。
从文件中读取也很有趣(如果文件不存在,则存在try/except
)。 当它读取它时,它会加载字典并在循环中通过使用obj_type_dict
字典将项目添加到列表中,以确定 object 的类型。 在创建对象的同时,它们也被绘制。
控制:
Ctrl + s
- 保存Click and drag with mouse button 1
- 移动 objectClick mouse button 3 (right)
- 创建 object建议:
filedialog
提供选项)无论如何,这只是如何完成的一个示例,也适用于图像如果要加载它们,则必须添加一个保存图像路径的属性,并进行其他调整,因为json
无法序列化图像(我认为)。
由于您似乎不明白如何在评论中使用我的建议,这里有一个可运行的概念验证,以证明其执行我建议的可行性 - 即关于定义一个Canvas
子类,该子类记录有关每个 object 的信息并且能够保存并将其加载到文件中。 为简单起见,我决定将数据保存为 JSON 格式,因为该文件是人类可读的(并且 Python 在其标准库中提供了对它的支持,但正如我所说,选择是你的。(正如@Matiiss 在他的回答中所建议的那样, pickle
将是另一个值得考虑的可行替代方案,并且会更紧凑)。
由于这只是一个演示,我只实现了对Canvas
线条和矩形的支持 - 但这足以让您全面了解如何实现Canvas
多边形和椭圆形小部件、椭圆形等请注意,可能无法全部完成,但我认为这不会成为问题——因为您可能不需要甚至不会使用它们。
一个值得注意的例外是图像,因为下面显示的用于保存信息的模式由于它们的image=
选项参数而不起作用。 要处理它们,您需要保存图像文件的路径或其中的数据——但是后一种选择将涉及将大量数据复制到保存文件中,并且需要对其进出 JSON 序列化形式进行编码和解码(如果您要使用该格式)。
更新
为了澄清我所说的图像是一种特殊情况(并且因为这可能是想要能够处理的事情,我已经修改了代码以显示可以完成的一种方式 - 即通过将图像信息嵌入保存的 canvas 文件,其中包括图像文件的路径)。 这似乎是与 JSON 格式结合使用的最干净的方法。 这意味着保存的画布实际上并不包含图像数据本身。
这是代码中使用的8-ball-tbgr.png图像文件。
import json
from PIL import Image, ImageTk
import tkinter as tk
from tkinter.constants import *
class MemoCanvas(tk.Canvas):
''' Canvas subclass that remembers the items drawn on it. '''
def __init__(self, parent, *args, **kwargs):
super().__init__(*args, **kwargs)
self.memo = {} # Database of Canvas item information.
# Canvas object constructors.
def create_line(self, *args, **kwargs):
id = super().create_line(*args, **kwargs)
self.memo[id] = dict(type='line', args=args, kwargs=kwargs)
return id
def create_rectangle(self, *args, **kwargs):
id = super().create_rectangle(*args, **kwargs)
self.memo[id] = dict(type='rectangle', args=args, kwargs=kwargs)
return id
def create_image(self, *args, imageinfo=None, **kwargs):
id = super().create_image(*args, **kwargs)
if not imageinfo:
raise RuntimeError('imageinfo dictionary must be supplied')
del kwargs['image'] # Don't store in memo.
self.memo[id] = dict(type='image', args=args, imageinfo=imageinfo, kwargs=kwargs)
return id
# General methods on Canvas items (not fully implemented - some don't update memo).
def move(self, *args):
super().move(*args)
def itemconfigure(self, *args, **kwargs):
super().itemconfigure(*args, **kwargs)
def delete(self, tag_or_id):
super().delete(tag_or_id)
if isinstance(tag_or_id, str) and tag_or_id.lower() != 'all':
if self.memo[tag_or_id]['type'] == 'image':
del self.imagerefs[tag_or_id]
del self.memo[tag_or_id]
else:
try:
self.memo.clear()
del self.imagerefs
except AttributeError:
pass
# General purpose utility.
def setdefaultattr(obj, name, value):
''' If obj has attribute, return it, otherwise create and assign the value
value to it first. '''
try:
return getattr(obj, name)
except AttributeError:
setattr(obj, name, value)
return value
# Demo app functions.
def draw_objects(canvas):
canvas.create_line(100, 200, 300, 400, fill='red')
canvas.create_rectangle(100, 200, 300, 400, outline='green')
# Add an image to canvas.
imageinfo = dict(imagepath=IMAGEPATH, hsize=100, vsize=100, resample=Image.ANTIALIAS)
imagepath, hsize, vsize, resample = imageinfo.values()
image = Image.open(imagepath).resize((hsize, vsize), resample)
image = ImageTk.PhotoImage(image)
id = canvas.create_image(450, 150, image=image, imageinfo=imageinfo)
imagerefs = setdefaultattr(canvas, 'imagerefs', {})
imagerefs[id] = image # Save reference to image created.
def clear_objects(canvas):
canvas.delete('all')
canvas.memo.clear()
try:
del canvas.imagerefs
except AttributeError:
pass
def save_objects(canvas, filename):
with open(filename, 'w') as file:
json.dump(canvas.memo, file, indent=4)
def load_objects(canvas, filename):
clear_objects(canvas) # Remove current contents.
# Recreate each saved canvas item.
with open(filename, 'r') as file:
items = json.load(file)
for item in items.values():
if item['type'] == 'line':
canvas.create_line(*item['args'], **item['kwargs'])
elif item['type'] == 'rectangle':
canvas.create_rectangle(*item['args'], **item['kwargs'])
elif item['type'] == 'image':
# Recreate image item from imageinfo.
imageinfo = item['imageinfo']
imagepath, hsize, vsize, resample = imageinfo.values()
image = Image.open(imagepath).resize((hsize, vsize), resample)
image = ImageTk.PhotoImage(image)
item['kwargs']['image'] = image
id = canvas.create_image(*item['args'], imageinfo=imageinfo, **item['kwargs'])
imagerefs = setdefaultattr(canvas, 'imagerefs', {})
imagerefs[id] = image # Save reference to recreated image.
else:
raise TypeError(f'Unknown canvas item type: {type!r}')
# Main
WIDTH, HEIGHT = 640, 480
FILEPATH = 'saved_canvas.json'
IMAGEPATH = '8-ball-tbgr.png'
CMDS = dict(Draw=lambda: draw_objects(canvas),
Save=lambda: save_objects(canvas, FILEPATH),
Clear=lambda: clear_objects(canvas),
Load=lambda: load_objects(canvas, FILEPATH),
Quit=lambda: root.quit())
root = tk.Tk()
root.title('Save and Load Canvas Demo')
root.geometry(f'{WIDTH}x{HEIGHT}')
canvas = MemoCanvas(root)
canvas.pack(fill=BOTH, expand=YES)
btn_frame = tk.Frame(root)
btn_frame.pack()
for text, command in CMDS.items():
btn = tk.Button(btn_frame, text=text, command=command)
btn.pack(side=LEFT)
root.mainloop()
这是它运行的屏幕截图:
这是它创建的 JSON 格式文件的截断内容(无法发布完整的内容,因为它现在太大了,因为其中包含图像数据)。
{
"1": {
"type": "line",
"args": [
100,
200,
300,
400
],
"kwargs": {
"fill": "red"
}
},
"2": {
"type": "rectangle",
"args": [
100,
200,
300,
400
],
"kwargs": {
"outline": "green"
}
},
"3": {
"type": "image",
"args": [
450,
150
],
"imageinfo": {
"imagepath": "8-ball-tbgr.png",
"hsize": 100,
"vsize": 100,
"resample": 1
},
"kwargs": {}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.