基于pythonGUI的图形绘图及图元编辑系统

发布于:2022-12-24 ⋅ 阅读:(328) ⋅ 点赞:(0)

目录
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()
        # pack
        self.pack_n_run()

    def init_basic(self):
        self.color_r = 0
        self.color_g = 0
        self.color_b = 0
        self.size_x = 500
        self.size_y = 500
        self.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 both
        self.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 bresenham
        self.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 = 0
        self.is_curve_painting = 0
        self.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 = -1
        self.last_point = [-1, -1]
        self.is_translating = 0
        self.rotate_point = [-1, -1]
        self.is_rotating = 0
        # self.is_primitive_rotating = 0
        self.start_angle = 0  # 弧度
        self.scale_point = [-1, -1]
        self.is_scaling = 0
        self.start_distance = 0
        # self.primitive_clipping = -1
        self.is_clipping = 0
        self.clip_point = [-1, -1]
        self.clip_alg = 'Cohen-Sutherland'
        self.clipped = 0
        self.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 = 0
        self.type = 1  # 种类 0 for DDA line, 1 for Bresenham Line
        self.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 = 1
        self.polygon_type = 1
        self.curve_type = 1

    def 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_t
            elif pri=='polygon':
                self.polygon_type = type_t
            elif pri=='curve':
                if self.curve_type != type_t:
                    self.is_curve_painting = 0
                self.curve_type = type_t
            elif 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))  # command
        sub_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))  # command
        sub_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))  # command
        sub_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))  # command
        sub_menu_clip.add_radiobutton(label="梁友栋-Barsky", command=lambda: set_draw_type('clip', 2))

    def init_curve_drawing(self):
        self.display_ctrl_point = 0
        self.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 = 0
        self.curve_modify_num = [-1, -1]

    def change_dis_ctrl_point(self):
        self.display_ctrl_point = 1 if self.display_ctrl_point==0 else 0
        self.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()

    @staticmethod
    def 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 = path
            self.save_canvas()

    def color_board_window(self):
        tmp = colorchooser.askcolor(parent=self.top, title='选择画笔颜色', color='blue')
        if tmp != (None, None):
            color_t =tmp
            self.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_name
        cmd_file = open(file, "r")
        lines = 0  # 跨行时的参数 下面三个也是
        line2_id = 0
        line2_n = 0
        line2_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 2
                line_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 = 1
                line2_id = int(words[1])
                line2_n = int(words[2])
                line2_alg = 1 if words[3]=="DDA" else 2
            elif 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 = 2
                line2_id = int(words[1])
                line2_n = int(words[2])
                # TODO: alg
                line2_alg = 1 if words[3] == "Bezier" else 2
            elif words[0] == "translate":
                print("translate")
                for i in range(len(self.primitives)):
                    if int(words[1]) == self.primitives[i].get_id():
                        # num = i
                        self.primitives[i].translate(int(words[2]), int(words[3]))
                        break
                self.refresh()
            elif words[0] == "rotate":
                print("rotate")
                for i in range(len(self.primitives)):
                    if int(words[1]) == self.primitives[i].get_id():
                        # num = i
                        self.primitives[i].rotate(int(words[2]), int(words[3]), int(words[4]))
                        break
                self.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]))
                        break
                self.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])
                        break
                self.refresh()
            elif lines == 1:  # polygon
                print("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 = 0
            elif lines == 2:  # curve
                print("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 = 0
            else:
                notification = "指令\""+words[0] + "\"解析失败"
                tkinter.messagebox.showinfo("提示", notification)
                break

    def clean_pic(self):
        self.paper.delete(ALL)
        self.primitives = []
        self.rotate_point = [-1, -1]
        self.scale_point = [-1, -1]
        self.primitive_changing = -1
        self.refresh()
        self.is_curve_painting = 0
        self.is_polygon_painting = 0
        self.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 = 0
        for 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]] = i
                    else:
                        self.map[point[0]][point[1]] = -2
            i = 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.x
        self.start_y = event.y
        def find(point, map_t):
            res = -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:
                            if res == -1:
                                res = map_t[i][j]
                            elif map_t[i][j] != res:
                                res = -1
                                return res
            # print("select ", res)
            return res
        def 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 res
        color = [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 = 3
        elif self.size_x-5 <= event.x <= self.size_x+5:
            self.is_image_scaling = 1
        elif 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 = 1
            else:
                self.cur -= 1
                self.primitives[self.cur].updating([event.x, event.y])
        elif self.type==4:  # curve
            tmp = [-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 = 1
                self.curve_modify_num = tmp
                # print(tmp)
                self.is_curve_painting = 0
            elif 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 = 1
            else:
                self.cur -= 1
                self.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 = 1
                self.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 = 1
                self.refresh()
            else:
                self.primitive_changing = find([event.x, event.y], self.map)
                if self.primitive_changing >= 0:
                    # self.is_rotating = 1
                    self.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/2
                    else:
                        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 = 1
                self.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 = 1
            self.refresh()




        # print("press", event.x, event.y)

    def leftmove(self, event):
        x = event.x
        y = event.y
        # print("pass", x, y)
        # temp_list = []
        if self.is_image_scaling > 0:
            self.cur -= 1
            if self.is_image_scaling == 1:
                self.size_x = 1000 if x>=1000 else x
            elif self.is_image_scaling == 2:
                self.size_y = 600 if y>=600 else y
            else:
                self.size_x = 1000 if x>=1000 else x
                self.size_y = 600 if y>=600 else y
            self.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_list
            self.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_list
            self.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/2
            else:
                angle = math.atan((y - self.rotate_point[1])/(x - self.rotate_point[0]))
                if x - self.rotate_point[0] < 0:
                    angle = angle + math.pi

            self.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_dis
        elif 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 = 1
        self.refresh()
        # print("refreshed")

    def leftrelease(self, event):
        if self.is_image_scaling >0:
            self.is_image_scaling = 0
        elif 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 = 0
            else:
                self.primitives[self.cur].updating([event.x, event.y])
            self.refresh()
        elif self.type == 5:
            self.is_translating = 0
            self.primitive_changing = -1
        elif self.type == 6:
            self.primitive_changing = -1
            self.primitives[self.primitive_changing].change(0)
        elif self.type == 7:
            self.primitive_changing = -1
            self.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 = 0
            self.last_point = [-1, -1]
            self.clip_point = [-1, -1]
            self.is_clipping = 0
            self.primitive_changing = -1
            self.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_t
        if 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 = 0
            self.primitives[self.cur].done()
            self.refresh()
        if self.is_curve_painting == 1:
            self.is_curve_painting = 0
            self.refresh()
        if self.is_rotating:
            self.is_rotating = 0
            self.rotate_point = [-1, -1]
            self.refresh()
        if self.is_scaling:
            self.is_scaling = 0
            self.scale_point = [-1, -1]
            self.refresh()
        if self.is_clipping:
            self.is_clipping = 0
        self.last_point = [-1, -1]
        self.clip_point = [-1, -1]
        self.refresh()
        # self.primitive_changing = -1

if __name__ == '__main__':
    gui = GUI()

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述