目錄
1 語言、開發環境以及框架 1
2 數據結構 1
2.1 圖元 2
2.2 GUI框架 3
2.3 CLI類 3
3 圖元繪制、變換算法原理、實現及分析 4
3.1 線段的DDA算法 4
3.2 線段的Bresanham算法 6
3.3 中點橢圓算法 8
3.4 多邊形的繪制方法 11
3.5 Bezier曲線的繪制 12
3.6 B樣條曲線的繪制 15
3.7 圖元的平移 17
3.8 圖元的旋轉 17
3.9 圖元的縮放 18
3.10 線段的裁剪的Cohen-Sutherland算法 20
3.11 線段的裁剪的梁友棟-Barsky算法 24
4 框架設計 26
4.1 CLI設計 27
4.2 命令行文件的讀取和執行 27
4.3 GUI設計 29
4.4 鼠標交互進行圖元繪制及變換 30
5 其他功能 41
5.1 拖動畫布邊界以調整畫布尺寸 41
5.2 顯示曲線控制點并隨時拖動控制點以調節曲線 41
References: 43
1 語言、開發環境以及框架
本程序使用python3.7編寫,在windows運行;圖形界面部分使用tkinter框架。
2 數據結構
本程序的主要涉及到三種數據結構,第一個是用于存放圖元信息的Primitive類及其派生類Line,Circle,Polygon等,第二個是GUI類,用于實現GUI交互邏輯,以及畫板和圖元信息的存放。第三個是CLI類,它是精簡的GUI類,刪去了和GUI相關的部分,保留繪圖部分。
2.1 圖元
所有的圖元都是對象,他們的基類是Primitive類,包括以下數據屬性:
1.def init(self, vertex, pno, color):
2. self.vertex = vertex
3. self.pixels = []
4. self.pno = pno
5. self.color = color
self.vertex list類型,其中每個元素是一個二元組,表示該圖元的頂點;
self.pixels list類型,元素類型同上,這個圖元光柵化后的所有像素坐標都存在;
self.pno int類型,是圖元的id
self.color 長度為3的list, 表示這個圖元的RGB值。
還包括以下方法:
self.rasterization(self): 使用圖元的頂點等信息,來求出該圖元在畫布中所有像素的位置,并將其存放在self.pixels數據屬性中;
self.get_pixels(self):返回self.pixels;若為空,則先調用上一個函數進行光柵化然后再返回。
self.get_color(self): 將self.color整理成一個24位的數字返回,供繪圖使用。
·直線類:Line繼承自Primitive類, 還有以下數據屬性:
1.def init(self, vertex, pno, method, color):
2. super().init(vertex, pno, color)
3. self.vertical = 0 # 是否存在斜率
4. self.slope = 0
5. self.method = method
self.vertical int類型,用來表示是否垂直;
self.slope int類型,是線段的斜率;若垂直,則將斜率定為10000;
self.method int類型,用來表示畫圖的算法(DDA或Bresenham)
還有以下方法:
self.DDA(self): DDA算法,用于在self.rasterization(self)中調用。
self.Bresenham(self): Bresenham算法,同上。
·橢圓類:Circle, 繼承自Primitives類,還具有以下的數據屬性:
1.def init(self, vertex, pno, color):
2. super().init(vertex, pno, color)
3. self.rx = vertex[1][0]
4. self.ry = vertex[1][1]
self.rx, self.ry int類型,長半軸的短半軸(ps. 橢圓的中心存放在self.vertex的第一個元素中)
·多邊形類:Polygon,繼承自Primitive類,還具有以下數據屬性:
1.def init(self, vertex, pno, method, is_done, color): # is_done:命令行直接完成,圖形界面要等待完成
2. super().init(vertex, pno, color)
3. self.method = method
4. self.is_done = is_done
5. self.lines = []
6. self.last_point = vertex[0]
7. self.is_updating = 0
8. self.new_point = [0, 0]
self.method int類型,用來表示畫圖的算法(DDA或Bresenham);
self.is_done int類型,用于區分命令行繪制還是鼠標繪制;
self.lines list類型,元素為Line。用來表示構成多邊形的直線;
self.last_point, self.new_point, list類型,self.is_updating int類型,均用于鼠標繪制多邊形過程中的操作。
還具有以下方法,均用于鼠標繪制多邊形:
self.updating(self, point): 鼠標拖動過程,用于動態顯示當前繪制的邊;
self.update_rasterization(self, point):鼠標松開后,用于添加剛剛繪制好的頂點和邊;
self.done(self):完成繪制后,連接第一個和最后一個點。
·曲線類:Curve,繼承自Primitive類,還具有以下數據屬性
self.alg 字符串,表示曲線繪制的算法,取值為‘Bezier’或‘B-spline’
各接口的詳細功能和具體實現將在后續算法和框架部分詳細描述
2.2 GUI框架
GUI框架是GUI類的一個對象,包括以下數據成員:
·窗口以及組件
self.top:主窗口
self.paper:tkinter的畫布
以及各種按鈕:self.draw_line, self.draw_circle, self.clean, self.close, self.save, self.line_DDA , self.line_Bre, self.owl, self.polygon, 等等,用來輸入坐標以繪制圖元或者改變鼠標繪制圖元的類型。
·圖元相關信息
self.primitives 由Primitive類的對象構成的list,存放著畫布中所有圖元的信息
·畫布相關信息
self.image 圖片文件,顯示在tkinter的畫布上
self.color_r, self.color_g, self.color_b 畫筆顏色
self.size_x, self.size_y 畫布大小
self.save_name 要保存的文件名
self.draw 畫筆
等等。
以及各種運行時需要的函數。各函數的功能以及實現的過程將在框架設計部分詳細描述。
2.3 CLI類
CLI類是進行文件輸入和繪制的對象,包括以下數據成員:
self.image 圖片文件,顯示在tkinter的畫布上
self.color_r, self.color_g, self.color_b 畫筆顏色
self.size_x, self.size_y 畫布大小
self.primitives 由Primitive類的對象構成的list,存放著畫布中所有圖元的信息
self.save_name 要保存的文件名
self.draw 畫筆
以及用來實現解析指令、繪制圖元等功能的函數
3 圖元繪制、變換算法原理、實現及分析
此部分基于Primitive類,本文轉載自http://www.biyezuopin.vip/onews.asp?id=15216圖元的繪制使用圖元的基本信息,如線段的頂點,橢圓的圓心的長短半軸,多邊形的頂點等等,來獲得圖元的需要在畫布上占據的所有像素點的坐標,并將其返回給框架。而圖元的變換(待添加)在原圖元的基礎上,對圖元進行修改并返回新的像素點坐標。
from base import*
from line import*
from circle import*
from polygon import*
from curve import*class GUI:def __init__(self):# 初始化self.init_basic()# 初始化GUI框架self.init_GUI()# 圖像顯示相關self.init_papers()# 鼠標事件相關self.init_mouse()# 基礎按鈕等self.init_buttons_basic()# 鼠標圖元變換相關self.init_change()# 命令行等其他的按鍵self.init_buttons_files()# 菜單欄:self.menubar = Menu(self.top)self.init_menubar()# 曲線的控制點顯示和拖拽self.init_curve_drawing()# packself.pack_n_run()def init_basic(self):self.color_r = 0self.color_g = 0self.color_b = 0self.size_x = 500self.size_y = 500self.primitives = [] # 已經創建的圖元 用于撤銷等操作self.save_name = "temp.bmp" # 要保存的文件名self.image = Image.new("RGB", (self.size_x, self.size_y), (255, 255, 255))self.draw = ImageDraw.Draw(self.image)def init_GUI(self):self.top = Tk()self.top.title("Painting_v2.4.0")self.top.geometry("1000x600+200+0")def init_papers(self):self.is_image_scaling = 0 # 1 for left, 2 for down, 3 for bothself.photo = ImageTk.PhotoImage(self.image)self.paper = Canvas(self.top, width=1000, height=600, bg="gray")self.paper.create_image(2, 2, image=self.photo, anchor=NW)def init_buttons_basic(self):self.tmp_icon0 = self.getIcon(0)self.line_DDA = Button(self.top, command=lambda: self.set_type(self.line_type), text="直線", image=self.tmp_icon0)# self.line_Bre = Button(self.top, command=lambda: self.set_type(1), text="Bresenham直線")self.tmp_icon1 = self.getIcon(1)self.owl = Button(self.top, command=lambda: self.set_type(2), text="橢圓", image=self.tmp_icon1)self.tmp_icon2 = self.getIcon(2) # next line: 3 for dda 4 for bresenhamself.polygon = Button(self.top, command=lambda: self.set_type(3), text="多邊形", image=self.tmp_icon2)self.tmp_icon3 = self.getIcon(3)self.curve = Button(self.top, command=lambda: self.set_type(4), text="曲線", image=self.tmp_icon3)self.tmp_icon4 = self.getIcon(4)self.translate = Button(self.top, command=lambda: self.set_type(5), text="平移", image=self.tmp_icon4)self.tmp_icon5 = self.getIcon(5)self.rotate = Button(self.top, command=lambda: self.set_type(6), text="旋轉", image=self.tmp_icon5)self.tmp_icon6 = self.getIcon(6)self.scale = Button(self.top, command=lambda: self.set_type(7), text="縮放", image=self.tmp_icon6)self.tmp_icon11 = self.getIcon(11)self.clip = Button(self.top, command=lambda: self.set_type(8), text="裁剪", image=self.tmp_icon11)# self.save_but = Button(self.top, command=self.save_canvas, text="保存")self.is_polygon_painting = 0self.is_curve_painting = 0self.polygon_last_point = [0, 0]self.map = np.full((self.size_x, self.size_y), -1)def init_buttons_files(self):self.tmp_icon8 = self.getIcon(8)self.cl = Button(self.top, command=self.cmd_line_window, text="打開文件", image=self.tmp_icon8)self.tmp_icon7 = self.getIcon(7)self.colorboard = Button(self.top, command=self.color_board_window, text='調色板', image=self.tmp_icon7)# 基礎按鍵設置# self.draw_line = Button(self.top, command=self.draw_line_window, text="畫直線")# self.draw_circle = Button(self.top, command=self.draw_circle_window, text="畫橢圓")self.tmp_icon9 = self.getIcon(9)self.clean = Button(self.top, command=self.clean_pic, text="清除畫布", image=self.tmp_icon9)self.tmp_icon10 = self.getIcon(10)self.close = Button(self.top, command=self.top.destroy, text="關閉", image=self.tmp_icon10)def init_change(self):self.primitive_changing = -1self.last_point = [-1, -1]self.is_translating = 0self.rotate_point = [-1, -1]self.is_rotating = 0# self.is_primitive_rotating = 0self.start_angle = 0 # 弧度self.scale_point = [-1, -1]self.is_scaling = 0self.start_distance = 0# self.primitive_clipping = -1self.is_clipping = 0self.clip_point = [-1, -1]self.clip_alg = 'Cohen-Sutherland'self.clipped = 0self.tmp_cut_line = Line([[0, 0], [0, 0]], -1, 1, 0)# self.def init_mouse(self):self.cur = self.primitives.__len__() # 當前正在繪制的圖元的數組下標self.start_x = 0 # 圖元初始坐標self.start_y = 0self.type = 1 # 種類 0 for DDA line, 1 for Bresenham Lineself.paper.bind('<Button-1>', self.leftdown)self.paper.bind('<B1-Motion>', self.leftmove)self.paper.bind('<ButtonRelease-1>', self.leftrelease)self.paper.bind('<Double-Button-1> ', self.double_left_click)self.line_type = 1self.polygon_type = 1self.curve_type = 1def init_menubar(self):filemenu = Menu(self.menubar, tearoff=0)self.menubar.add_cascade(label='文件', menu=filemenu)filemenu.add_cascade(label='打開命令行文件', command=self.cmd_line_window)filemenu.add_command(label='清除畫布', command=self.clean_pic)filemenu.add_command(label='保存', command=self.save_by_mouse)filemenu.add_command(label='退出', command=self.top.destroy)drawmenu = Menu(self.menubar, tearoff=0)self.menubar.add_cascade(label="繪圖", menu=drawmenu)def set_draw_type(pri, type_t):if pri=='line':self.line_type = type_telif pri=='polygon':self.polygon_type = type_telif pri=='curve':if self.curve_type != type_t:self.is_curve_painting = 0self.curve_type = type_telif pri=='clip':self.clip_alg = 'Cohen-Sutherland' if type_t== 1 else 'Liang-Barsky'sub_menu_line = Menu(drawmenu, tearoff=0)drawmenu.add_cascade(label="直線繪制算法", menu=sub_menu_line)sub_menu_line.add_radiobutton(label="DDA", command=lambda: set_draw_type('line', 1)) # commandsub_menu_line.add_radiobutton(label="Bresenham", command=lambda: set_draw_type('line', 2))sub_menu_polygon = Menu(drawmenu, tearoff=0)drawmenu.add_cascade(label="多邊形繪制算法", menu=sub_menu_polygon)sub_menu_polygon.add_radiobutton(label="DDA", command=lambda: set_draw_type('polygon', 1)) # commandsub_menu_polygon.add_radiobutton(label="Bresenham", command=lambda: set_draw_type('polygon', 2))sub_menu_curve = Menu(drawmenu, tearoff=0)drawmenu.add_cascade(label="曲線繪制算法", menu=sub_menu_curve)sub_menu_curve.add_radiobutton(label="Bezier", command=lambda: set_draw_type('curve', 1)) # commandsub_menu_curve.add_radiobutton(label="B-spline", command=lambda: set_draw_type('curve', 2))sub_menu_clip = Menu(drawmenu, tearoff=0)drawmenu.add_cascade(label="直線裁剪算法", menu=sub_menu_clip)sub_menu_clip.add_radiobutton(label="Cohen-Sutherland", command=lambda: set_draw_type('clip', 1)) # commandsub_menu_clip.add_radiobutton(label="梁友棟-Barsky", command=lambda: set_draw_type('clip', 2))def init_curve_drawing(self):self.display_ctrl_point = 0self.ctrl_point_check = Checkbutton(self.top, text="顯示控制點", command=self.change_dis_ctrl_point)self.map_ctrl_point = np.full((self.size_x, self.size_y, 2), -1)self.is_curve_modifying = 0self.curve_modify_num = [-1, -1]def change_dis_ctrl_point(self):self.display_ctrl_point = 1 if self.display_ctrl_point==0 else 0self.refresh()def pack_dis_ctrl_point(self, p):if p==1:self.ctrl_point_check.grid(row=0, column=20)else:self.ctrl_point_check.grid_forget()def pack_n_run(self):# pack, TODO:位置設置self.line_DDA.grid(row=0, column=0)# self.line_DDA.place(x=0, y=0)# self.line_Bre.grid(row=0, column=1)self.owl.grid(row=0, column=1)self.polygon.grid(row=0, column=2)self.curve.grid(row=0, column=3)self.translate.grid(row=0, column=4)self.rotate.grid(row=0, column=5)self.scale.grid(row=0, column=6)self.clip.grid(row=0, column=7)self.colorboard.grid(row=0, column=8)# self.paper.grid(row=1, column=5)self.paper.place(x=0, y=30)self.cl.grid(row=0, column=9)self.clean.grid(row=0, column=10)self.close.grid(row=0, column=11)self.pack_dis_ctrl_point(1)self.pack_dis_ctrl_point(0)# self.draw_line.grid(row=3, column=0)# self.draw_circle.grid(row=3, column=1)self.top.config(menu=self.menubar)os.remove("tmp.ico")self.top.mainloop()@staticmethoddef getIcon(s):tmp = open("tmp.ico", "wb+")tmp.write(base64.b64decode(imgs.icons[s]))tmp.close()return PhotoImage(file='tmp.ico')def cmd_line_window(self):in_put = Toplevel()# in_put.wm_attributes('-topmost', 1)# in_put.withdraw()in_put.protocol('WM_DELETE_WINDOW', in_put.withdraw)in_put.title("打開文件")in_put.geometry("330x100+605+300")Label(in_put, text="文件名:").grid(row=0, column=0)Label(in_put, text="輸出圖元保存位置:").grid(row=1, column=0)file_name_path = StringVar()save_path_path = StringVar()file_name = Entry(in_put, textvariable=file_name_path)save_path = Entry(in_put, textvariable=save_path_path)file_name.grid(row=0, column=1)save_path.grid(row=1, column=1)open_file = Button(in_put, command=lambda: self.open_file(file_name_path), text="打開文件")chose = Button(in_put, command=lambda: self.select_dir(save_path_path), text="選擇文件夾")begin = Button(in_put, command=lambda: self.cmd_line_act(file_name, save_path), text="開始")close = Button(in_put, command=in_put.destroy, text="關閉")open_file.grid(row=0, column=2)chose.grid(row=1, column=2)begin.grid(row=2, column=0)close.grid(row=2, column=1)in_put.mainloop()def open_file(self, path):name = tkfl.askopenfilename(filetypes=[("文本文檔", ".txt")])print(name)path.set(name)def select_dir(self, path):name = tkfl.askdirectory()print(name)path.set(name)def save_by_mouse(self):path = StringVar()path = tkfl.asksaveasfilename(filetypes=[('bmp文件', '.bmp')])print(path)if path != '':self.save_name = pathself.save_canvas()def color_board_window(self):tmp = colorchooser.askcolor(parent=self.top, title='選擇畫筆顏色', color='blue')if tmp != (None, None):color_t =tmpself.color_r = int(color_t[0][0])self.color_g = int(color_t[0][1])self.color_b = int(color_t[0][2])print(self.color_r, self.color_g, self.color_b, color_t)def cmd_line_act(self, file_name, save_path):file = file_name.get()if file=='':file = 'input.txt'path = save_path.get()# 保存時 修改self.save_namecmd_file = open(file, "r")lines = 0 # 跨行時的參數 下面三個也是line2_id = 0line2_n = 0line2_alg = ""for cmd in cmd_file.readlines():# 如果涉及到跨行, 即多邊形和曲線, 設置一個多余的參數color = [self.color_r, self.color_g, self.color_b]words = cmd.split()if words[0] == "resetCanvas":print("reset")self.size_x = int(words[1])self.size_y = int(words[2])self.clean_pic()elif words[0] == "saveCanvas":print("save")if path != '':self.save_name = path + "/" + words[1] + ".bmp"else:self.save_name = words[1] + ".bmp"self.save_canvas()elif words[0] == "setColor":print("setColor")self.color_r = int(words[1])self.color_g = int(words[2])self.color_b = int(words[3])elif words[0] == "drawLine":print("Line")vertex = [[int(words[2]), int(words[3])], [int(words[4]), int(words[5])]]pid = int(words[1])alg = 1 if words[6]=="DDA" else 2line_2b_drawn = Line(vertex, pid, alg, color)self.primitives.append(line_2b_drawn)# print(self.primitives.__len__())self.refresh()elif words[0] == "drawPolygon":print("Polygon")lines = 1line2_id = int(words[1])line2_n = int(words[2])line2_alg = 1 if words[3]=="DDA" else 2elif words[0] == "drawEllipse":print("Ellipse")vertex = [[int(words[2]), int(words[3])], [int(words[4]), int(words[5])]]pid = int(words[1])circle_2b_drawn = Circle(vertex, pid, color)self.primitives.append(circle_2b_drawn)self.refresh()elif words[0] == "drawCurve":print("Curve")lines = 2line2_id = int(words[1])line2_n = int(words[2])# TODO: algline2_alg = 1 if words[3] == "Bezier" else 2elif words[0] == "translate":print("translate")for i in range(len(self.primitives)):if int(words[1]) == self.primitives[i].get_id():# num = iself.primitives[i].translate(int(words[2]), int(words[3]))breakself.refresh()elif words[0] == "rotate":print("rotate")for i in range(len(self.primitives)):if int(words[1]) == self.primitives[i].get_id():# num = iself.primitives[i].rotate(int(words[2]), int(words[3]), int(words[4]))breakself.refresh()elif words[0] == "scale":print("scale")for i in range(len(self.primitives)):if int(words[1]) == self.primitives[i].get_id():self.primitives[i].scale(int(words[2]), int(words[3]), float(words[4]))breakself.refresh()elif words[0] == "clip":print("clip")for i in range(len(self.primitives)):if int(words[1]) == self.primitives[i].get_id():self.primitives[i].clip(int(words[2]), int(words[3]), int(words[4]), int(words[5]), words[6])breakself.refresh()elif lines == 1: # polygonprint("Polygon, 2nd line")vertex = []for i in range(line2_n):point = [int(words[2*i]), int(words[2*i+1])]vertex.append(point)polygon_2b_drawn = Polygon(vertex, line2_id, line2_alg, 1, color)self.primitives.append(polygon_2b_drawn)self.refresh()lines = 0elif lines == 2: # curveprint("Curve, 2nd line")vertex = []for i in range(line2_n):point = [int(words[2 * i]), int(words[2 * i + 1])]vertex.append(point)alg = 'Bezier' if line2_alg == 1 else 'B-spline'curve_2b_drawn = Curve(vertex, line2_id, alg, 1, color)self.primitives.append(curve_2b_drawn)self.refresh()lines = 0else:notification = "指令\""+words[0] + "\"解析失敗"tkinter.messagebox.showinfo("提示", notification)breakdef clean_pic(self):self.paper.delete(ALL)self.primitives = []self.rotate_point = [-1, -1]self.scale_point = [-1, -1]self.primitive_changing = -1self.refresh()self.is_curve_painting = 0self.is_polygon_painting = 0self.is_rotating = 0# self.image = Image.new("RGB", (self.size_x, self.size_y), (255, 255, 255))# self.draw = ImageDraw.Draw(self.image)# self.paper.create_image(0, 0, image=self.photo, anchor=NW)print("clean_pic")def save_canvas(self):# file_name = "temp.bmp"self.image.save(self.save_name)def refresh(self):# t1 = int(round(time.time() * 1000))# self.image.show()# print("refresh!")# if self.display_ctrl_point==1:# print(1111)# else:# print("0000")self.paper.delete(ALL)self.image = Image.new("RGB", (self.size_x, self.size_y), (255, 255, 255))self.draw = ImageDraw.Draw(self.image)self.map = np.full((self.size_x, self.size_y), -1)i = 0for primitive in self.primitives:pixels = primitive.get_pixels()for point in pixels:if (point[0] >= 0) and (point[1] >= 0) and (point[0] <= self.size_x-1) and (point[1] <= self.size_y-1):# self.draw.point((point[0], point[1]), fill=self.color_r + self.color_g*256 + self.color_b*256*256)self.draw.point((point[0], point[1]), fill=primitive.get_color())if self.map[point[0]][point[1]] == -1:self.map[point[0]][point[1]] = ielse:self.map[point[0]][point[1]] = -2i = i + 1# print("drawn", primitive.pno)self.photo = ImageTk.PhotoImage(self.image)self.paper.create_image(2, 2, image=self.photo, anchor=NW)if self.rotate_point != [-1, -1]:self.paper.create_oval(self.rotate_point[0]-2, self.rotate_point[1]-2,self.rotate_point[0]+2, self.rotate_point[1]+2,fill='red')if self.scale_point != [-1, -1]:self.paper.create_oval(self.scale_point[0]-2, self.scale_point[1]-2,self.scale_point[0]+2, self.scale_point[1]+2,fill='green')if self.type>=4 and self.display_ctrl_point==1: # 繪制曲線控制點和虛線self.map_ctrl_point = np.full((self.size_x, self.size_y, 2), -1)for i in range(self.primitives.__len__()):if self.primitives[i].__class__.__name__ == 'Curve':vertex = self.primitives[i].get_vertexes()for j in range(vertex.__len__()):self.paper.create_oval(vertex[j][0]-2, vertex[j][1]-2, vertex[j][0]+2, vertex[j][1]+2)if 0<=vertex[j][0]<=self.size_x-1 and 0<=vertex[j][1]<=self.size_y-1:self.map_ctrl_point[vertex[j][0]][vertex[j][1]] = [i, j]if j >= 1:self.paper.create_line(vertex[j-1][0], vertex[j-1][1], vertex[j][0], vertex[j][1],fill='red', dash=(4, 4))if self.type == 8 and self.primitive_changing != -1 and self.primitives[self.primitive_changing].__class__.__name__=='Line':vertex = self.primitives[self.primitive_changing].get_vertexes()self.paper.create_oval(vertex[0][0] - 2, vertex[0][1] - 2,vertex[0][0] + 2, vertex[0][1] + 2,fill='blue')self.paper.create_oval(vertex[1][0] - 2, vertex[1][1] - 2,vertex[1][0] + 2, vertex[1][1] + 2,fill='blue')if self.type == 8 and self.primitive_changing != -1 and self.is_clipping==1:vertex = self.tmp_cut_line.get_vertexes()if self.tmp_cut_line.is_deleted != 1:self.paper.create_line(vertex[0][0], vertex[0][1], vertex[1][0], vertex[1][1], fill='red', width=3)vertex = [self.last_point, self.clip_point]xmax = max(vertex[0][0], vertex[1][0])xmin = min(vertex[0][0], vertex[1][0])ymax = max(vertex[0][1], vertex[1][1])ymin = min(vertex[0][1], vertex[1][1])x = [xmin, xmin, xmax, xmax]y = [ymin, ymax, ymax, ymin]for i in range(4):self.paper.create_line(x[i], y[i], x[(i+1)%4], y[(i+1)%4], fill='red', dash=(4, 4))# t2 = int(round(time.time() * 1000))# print("timecost:", t2-t1)def leftdown(self, event):self.cur = self.primitives.__len__()self.start_x = event.xself.start_y = event.ydef find(point, map_t):res = -1for i in range(point[0] - 5, point[0] + 5):for j in range(point[1] - 5, point[1] + 5):if (i >= 0) and (i <= self.size_x-1) and (j > 0) and (j <= self.size_y-1):if map_t[i][j] >= 0:if res == -1:res = map_t[i][j]elif map_t[i][j] != res:res = -1return res# print("select ", res)return resdef find_curve(point, map_t):res = [-1, -1]for i in range(point[0] - 5, point[0] + 5):for j in range(point[1] - 5, point[1] + 5):if (i >= 0) and (i <= self.size_x-1) and (j > 0) and (j <= self.size_y-1):if map_t[i][j][0] >=0:if res == [-1, -1]:res = map_t[i][j]elif map_t[i][j][0] != res[0]:res = [-1, -1]return res# print("select ", res)return rescolor = [self.color_r, self.color_g, self.color_b]if self.size_x-5 <= event.x <= self.size_x+5 and self.size_y-5 <= event.y <= self.size_y+5:self.is_image_scaling = 3elif self.size_x-5 <= event.x <= self.size_x+5:self.is_image_scaling = 1elif self.size_y-5 <= event.y <= self.size_y+5:self.is_image_scaling = 2# elif 0 <= event.x <=self.size_x and 0 <= event.y <= self.size_y:elif self.type == 0 or self.type == 1:temp_list = [[self.start_x, self.start_y], [self.start_x, self.start_y]]line_being_drawn = Line(temp_list, self.primitives.__len__(), self.line_type, color)self.primitives.append(line_being_drawn)elif self.type == 2:owl_being_drawn = Circle([[self.start_x, self.start_y], [0, 0]], self.primitives.__len__(), color)self.primitives.append(owl_being_drawn)elif self.type ==3:if self.is_polygon_painting == 0:polygon_being_drawn = Polygon([[self.start_x, self.start_y]], self.primitives.__len__(),self.polygon_type, 0, color)self.primitives.append(polygon_being_drawn)self.is_polygon_painting = 1else:self.cur -= 1self.primitives[self.cur].updating([event.x, event.y])elif self.type==4: # curvetmp = [-1, -1]if self.display_ctrl_point == 1:tmp = find_curve([event.x, event.y], self.map_ctrl_point)if tmp[0] != -1:self.is_curve_modifying = 1self.curve_modify_num = tmp# print(tmp)self.is_curve_painting = 0elif self.is_curve_painting == 0:curve_being_drawn = Curve([[self.start_x, self.start_y],[self.start_x, self.start_y]],self.primitives.__len__(),('Bezier' if self.curve_type==1 else 'B-spline'), 0, color)self.primitives.append(curve_being_drawn)self.is_curve_painting = 1else:self.cur -= 1self.primitives[self.cur].begin_update([event.x, event.y])self.refresh()elif self.type ==5:# print(self.map[50])self.primitive_changing = find([event.x, event.y], self.map)if self.primitive_changing >= 0:self.is_translating = 1self.last_point = [event.x, event.y]print("found")elif self.type == 6:print("rotate")if self.is_rotating == 0:self.rotate_point = [event.x, event.y]self.is_rotating = 1self.refresh()else:self.primitive_changing = find([event.x, event.y], self.map)if self.primitive_changing >= 0:# self.is_rotating = 1self.primitives[self.primitive_changing].change(1)if event.x == self.rotate_point[0]:self.start_angle = math.pi/2 if event.y - self.rotate_point[1] > 0 else -math.pi/2else:self.start_angle = math.atan((event.y - self.rotate_point[1])/(event.x - self.rotate_point[0]))if event.x - self.rotate_point[0] < 0:self.start_angle = self.start_angle + math.pi# self.last_point = [event.x, event.y]elif self.type == 7:if self.is_scaling == 0:self.scale_point = [event.x, event.y]self.is_scaling = 1self.refresh()else:self.primitive_changing = find([event.x, event.y], self.map)self.primitives[self.primitive_changing].change(1)if self.primitive_changing >= 0:print('a')self.start_distance = math.sqrt(pow(event.x - self.scale_point[0], 2) +pow(event.y - self.scale_point[1], 2))elif self.type == 8:if self.is_clipping == 1:# print("yyy")self.last_point = [event.x, event.y]self.clip_point = [event.x, event.y]else:self.primitive_changing = find([event.x, event.y], self.map)if self.primitive_changing >= 0 and self.primitives[self.primitive_changing].__class__.__name__=='Line':self.is_clipping = 1self.refresh()# print("press", event.x, event.y)def leftmove(self, event):x = event.xy = event.y# print("pass", x, y)# temp_list = []if self.is_image_scaling > 0:self.cur -= 1if self.is_image_scaling == 1:self.size_x = 1000 if x>=1000 else xelif self.is_image_scaling == 2:self.size_y = 600 if y>=600 else yelse:self.size_x = 1000 if x>=1000 else xself.size_y = 600 if y>=600 else yself.set_type(self.type)elif self.type == 0 or self.type == 1:temp_list = [[self.start_x, self.start_y], [x, y]]self.primitives[self.cur].vertex = temp_listself.primitives[self.cur].rasterization()elif self.type == 2:x_mid = int((x + self.start_x) / 2)y_mid = int((y + self.start_y) / 2)rx = int((abs(x - self.start_x)) / 2)ry = int((abs(y - self.start_y)) / 2)temp_list = [[x_mid, y_mid], [rx, ry]]self.primitives[self.cur].vertex = temp_listself.primitives[self.cur].rasterization()elif self.type == 3:self.primitives[self.cur].updating([x, y])# self.refresh()elif self.type == 4:if self.is_curve_modifying == 1:self.primitives[self.curve_modify_num[0]].modify(self.curve_modify_num[1], [x, y])else:self.primitives[self.cur].updating([x, y])# print(self.primitives[self.cur].vertex)# print(self.primitives[self.cur].pixels)elif self.type == 5 and self.is_translating == 1:self.primitives[self.primitive_changing].translate(x - self.last_point[0], y - self.last_point[1])self.last_point = [x, y]elif self.type == 6 and self.primitive_changing != -1:if x == self.rotate_point[0]:angle = math.pi/2 if y - self.rotate_point[1] > 0 else -math.pi/2else:angle = math.atan((y - self.rotate_point[1])/(x - self.rotate_point[0]))if x - self.rotate_point[0] < 0:angle = angle + math.piself.primitives[self.primitive_changing].rotate(self.rotate_point[0], self.rotate_point[1],(angle - self.start_angle)*180 / math.pi)self.start_angle = angle # 這里不應該叫"start"而是"last"elif self.type == 7 and self.primitive_changing != -1:cur_dis = math.sqrt(pow(event.x - self.scale_point[0], 2) +pow(event.y - self.scale_point[1], 2))self.primitives[self.primitive_changing].scale(self.scale_point[0], self.scale_point[1],cur_dis/self.start_distance)self.start_distance = cur_diselif self.type == 8 and self.primitive_changing != -1 and self.is_clipping==1:self.clip_point = [x, y]self.tmp_cut_line = Line(self.primitives[self.primitive_changing].get_vertexes(), -1,self.primitives[self.primitive_changing].get_method(), 0)# tmp_line = self.primitives[self.primitive_changing]print(self.primitives[self.primitive_changing].get_vertexes())# print(self.last_point, self.clip_point)self.tmp_cut_line.clip(self.last_point[0], self.last_point[1],self.clip_point[0], self.clip_point[1], self.clip_alg)self.clipped = 1self.refresh()# print("refreshed")def leftrelease(self, event):if self.is_image_scaling >0:self.is_image_scaling = 0elif self.type==0 or self.type == 1 or self.type == 2:self.cur = self.primitives.__len__()elif self.type == 3:self.primitives[self.cur].update_rasterization([event.x, event.y])self.polygon_last_point = [event.x, event.y]self.refresh()elif self.type == 4:if self.is_curve_modifying == 1:self.primitives[self.curve_modify_num[0]].modify(self.curve_modify_num[1], [event.x, event.y])self.is_curve_modifying = 0else:self.primitives[self.cur].updating([event.x, event.y])self.refresh()elif self.type == 5:self.is_translating = 0self.primitive_changing = -1elif self.type == 6:self.primitive_changing = -1self.primitives[self.primitive_changing].change(0)elif self.type == 7:self.primitive_changing = -1self.primitives[self.primitive_changing].change(0)elif self.type == 8 and self.clipped == 1:self.primitives[self.primitive_changing].clip(self.last_point[0], self.last_point[1],self.clip_point[0], self.clip_point[1],self.clip_alg)self.clipped = 0self.last_point = [-1, -1]self.clip_point = [-1, -1]self.is_clipping = 0self.primitive_changing = -1self.tmp_cut_line = Line([[0, 0], [0, 0]], -1, 1, 0)self.refresh()# print("release")def double_left_click(self, event): # unused# print("double!")if self.type == 3 and self.is_polygon_painting == 1:self.primitives[self.cur].update_rasterization([event.x, event.y])self.polygon_last_point = [event.x, event.y]self.refresh()def set_type(self, type_t): # 設置鼠標畫圖的類型self.type = type_tif type_t>=4 and type_t !=8:self.pack_dis_ctrl_point(1)else:self.pack_dis_ctrl_point(0)if self.is_polygon_painting == 1: # 完成多邊形的繪制self.is_polygon_painting = 0self.primitives[self.cur].done()self.refresh()if self.is_curve_painting == 1:self.is_curve_painting = 0self.refresh()if self.is_rotating:self.is_rotating = 0self.rotate_point = [-1, -1]self.refresh()if self.is_scaling:self.is_scaling = 0self.scale_point = [-1, -1]self.refresh()if self.is_clipping:self.is_clipping = 0self.last_point = [-1, -1]self.clip_point = [-1, -1]self.refresh()# self.primitive_changing = -1if __name__ == '__main__':gui = GUI()
總結
以上是生活随笔為你收集整理的基于pythonGUI的图形绘图及图元编辑系统的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。