[英]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.