用Python shelve将canvas图形图像保存为文件及从文件读出图形图像重新显示的方法

除了处理位图的程序,例如win10画图程序,还有一类程序是处理图形图像类实例,例如画线路板的PCB EDA程序。电子产品都有印刷电路板(PCB),其用途是将所需元器件焊接到PCB上,用PCB上覆铜导线将元器件连接起来。设计印刷电路板的程序称为PCB EDA程序。根据产品的外形,PCB尺寸是限定的,例如手机对PCB尺寸要求是很苛刻的。为了在有限面积上摆放所有元器件,元器件摆放位置和连接元器件的线要不断调整。为了移动方便,PCB EDA设计图上的元器件和连线都必须是图形或图像类实例。在PCB EDA设计未完成前,需要保存已完成图形图像为文件,以便以后从文件取出已完成图形图像,在此基础上继续设计工作,只有在设计完成后才会生成位图文件。
用Python设计一个大型EDA程序可能不现实,但设计一些简单的类似EDA程序还是可能的。例如设计海报、广告和电子板报等,现在一般不用手工完成,而是采用软件设计,用现成素材,加上相应文字,然后排版后完成。为了排版方便,素材和文字应是类实例。python可以实现这个功能,在tkinter Canvas上的图形图像,它们都是图形或图像类实例,很容易移动、放大、缩小等。虽然python可将Canvas上的图形图像保存为postscript类型文件,用于高质量打印,但python并没有提供读该类型文件方法。本文提供一种方法,用shelve将canvas上已完成的图形图像保存到文件中,并能从文件取出已完成的图形图像重新在空Canvas上显示,方便继续工作。
实现的基本思路是:首先得到某图形或图像类型,例如类型"oval"是椭圆(圆);然后得到其坐标;最后得到其配置,例如:“fill”、“tags”、“image"和"anchor"等;用shelve把这些数据保存到文件。重新显示时打开用shelve保存的文件,根据保存的图形或图像类型调用Canvas的相应创建图形或图像方法,该方法使用文件中坐标参数和配置参数在空Canvas上重画这个图形或图像。
现在介绍程序。程序在Canvas上创建了一个椭圆、一条线段和一个小猫图像。增加3个按钮,按钮标题分别是:保存、打开和删除所有图形。单击保存按钮,调用事件处理函数saveImage,用shelve将Canvas上椭圆、线段和小猫图像等实例类型、坐标和配置保存到文件中。单击删除所有图形按钮,删除Canvas上椭圆、线段和小猫图像。单击打开按钮,调用事件处理函数loadImage,取出用shelve保存的文件,根据该文件中类型、坐标和配置数据用Canvas方法重新创建被保存的椭圆、线段和小猫图像。
事件处理函数saveImage中(第12到33行),用shelve保存Canvas上所有图形和图像数据到文件中。首先创建列表data,列表的每一项都包括类型、坐标和配置3个键值对(第16到20行)。坐标键值对的值是坐标元组,长度可为2(例如图形中心点坐标)、4(例如矩形2个顶点坐标)或多个(例如多条线段);配置键值对的值包括若干(个数不定)键值对。注意,shelve模块中,key(键)必须为字符串,值可以是python所有数据类型。如图形或图像类实例类型为"image”(第15行),用第16行语句为列表data增加1项,如是其它类型,用第24行语句为列表data增加1项。最后用shelve将列表data保存为文件。
事件处理函数loadImage中(第37到51行),从用shelve保存的文件中读取Canvas上所有图形图像数据,并在空canvas上重新显示。这里*args可以看做是多个变量组成的列表(list),参数个数决定于列表长度。**args可以看做是个字典,参数个数决定于字典长度。需注意,第18行配置中的"image",并没有把图形数据保存到文件中,感觉是保存了变量名,因此重新创建图像时,第10行语句必须存在。
如在第24行语句的"config"键值对中增加"image" 和"anchor"键值对,删除第14到23行,仅用第24行语句将椭圆、线段和小猫图像3个类实例的数据增加到列表data中,将会报错。这是因为椭圆和线段没有属性(即方法的参数) “image"和"anchor”,当在列表data中增加椭圆或线段数据时,使用itemcget得到"image" 和"anchor"值将报错,小猫image图像没有属性"fill"和 “dash”,使用itemcget得到"fill"和"dash"值也将报错。因此,当不同类型的图形使用相同语句增加数据到列表中时,如某图形使用了属性A,例如第8条语句的画线方法的dash属性,使用相同语句增加数据的其它图形(例如第9条语句的画椭圆方法)也必须有属性A(例如dash),即使创建其它图形时,并没有使用属性A,在其它图形使用itemcget得到属性A时,将得到默认值,不会影响其它图形的正确显示。不过,canvas只有8个创建图形和图像的方法,如用8个if或elif语句,每条语句单独为某种类型图形或图像在列表增加数据,也就不用考虑这些问题了。
用canvas方法创建图形或图像,每个方法都有很多参数,例如椭圆(圆)有22个参数,它们都有默认值。如果仅用来画图,使用的参数就少多了,常用的有dash、fill、 outline、stipple和width等。因此在类似EDA画图程序中为保存图形图像类实例,可编写一个通用保存数据方法,使用8个if或elif语句,每个if或elif语句仅将某一种类型图形或图像常用参数保存到列表中,如果实际调用Canvas方法时,某个参数并没使用,则取默认值。
另外,图形图像之间可能会出现重叠。canvas为了记录这种重叠关系,建立一个显示列表,该列表决定当两个图形图像重叠时是如何覆盖的(默认情况下新创建的会覆盖旧的图形图像的重叠部分,即位于显示列表上方的图形图像将覆盖下方那个)。当然,显示列表中的图形图像可以被重新排序。而find_all方法按照显示列表的顺序返回所有图形图像的ID,返回格式是一个元组,按照显示列表依次重画图形图像,将能正确建立原图的重叠关系。
完整程序如下。被拷贝程序运行前,要先创建一个300*300、文件名为"cat.gif"的文件,并保存到被拷贝程序所在文件夹中。

import tkinter as tk
import shelve
root = tk.Tk()
root.geometry('400x350')
cv = tk.Canvas(root, width=400, height=300,bg='white')
cv.pack()
filepath = "myData"
cv.create_line(10,120,90,210,fill = "green",tags=('L'),dash=(3,5))
cv.create_oval(10,10,100,100,fill = "red",tags=('L'))
p = tk.PhotoImage(file = "cat.gif") #支持png和gif格式,不支持jpg格式,当调用loadImage重新显示,该语句必须存在
cv.create_image(100, 10, anchor="nw", image=p,tags=('L'))
def saveImage():
    data = []
    for itemID in cv.find_all():            #得到canvas上所有图形和图像类实例的ID
        if cv.type(itemID) == "image":
            data.append({"type": cv.type(itemID),"coords": cv.coords(itemID),
                         "config": {
                             "image": cv.itemcget(itemID,"image"),
                             "anchor": cv.itemcget(itemID,"anchor"),
                             "tags": cv.itemcget(itemID,"tags"),
                    }
                })
        else:
            data.append({                   #每个画布上图形类实例的数据为1个列表项
                    "type": cv.type(itemID),#返回画布上图形类实例类型,例如椭圆为"oval"
#返回画布上图形类实例坐标元组,长度可为2(例如图形位置)、4(例如矩形2个顶点坐标)或多个(例如多个线段)
                    "coords": cv.coords(itemID),#shelve模块中,key必须为字符串,值可以是python所有数据类型。
                    "config": {
                        "fill": cv.itemcget(itemID,"fill"),
                        "tags": cv.itemcget(itemID,"tags"),
                        "dash": cv.itemcget(itemID,"dash"),
                    }
                })
    with shelve.open('myData') as f:
        f['cvData'] = data              # 保存列表

def loadImage():
    #data=[]
    with shelve.open('myData') as f:
        data=f.get('cvData')
    for item in data:
        #根据type的种类而调用Canvas的不同方法
        if item["type"] == "oval":
            #itemID = cv.create_oval(*item["coords"])
            #cv.itemconfig(itemID, **item["config"])
#*item和**item用于函数参数个数不能确定时。*item可以看做是多个变量组成的list。**item可以看做是个字典
            cv.create_oval(*item["coords"], **item["config"])
        if item["type"] == "image":
            cv.create_image(*item["coords"], **item["config"])
        if item["type"] == "line":
            cv.create_line(*item["coords"], **item["config"])
def deleteAllImage():
    cv.delete('L')
but1=tk.Button(root,command=saveImage,text='保存')
but1.pack(side="left")
but2=tk.Button(root,command=loadImage,text='打开')
but2.pack(side="left")
but3=tk.Button(root,command=deleteAllImage,text='删除所有图形')
but3.pack(side="left")
root.mainloop()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值