簡體   English   中英

Tkinter 保存 canvas 並加載

[英]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 - 移動 object
  • Click mouse button 3 (right) - 創建 object

建議:

  • 改進文件保存(為用戶/使用filedialog提供選項)
  • 改進 object 添加到屏幕(您會注意到 object 在測試時相對於鼠標出現的位置)
  • 添加將其保存為圖像的部分

無論如何,這只是如何完成的一個示例,也適用於圖像如果要加載它們,則必須添加一個保存圖像路徑的屬性,並進行其他調整,因為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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM