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


    目录
    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('', self.leftdown)
            self.paper.bind('', self.leftmove)
            self.paper.bind('', self.leftrelease)
            self.paper.bind(' ', 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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423
    • 424
    • 425
    • 426
    • 427
    • 428
    • 429
    • 430
    • 431
    • 432
    • 433
    • 434
    • 435
    • 436
    • 437
    • 438
    • 439
    • 440
    • 441
    • 442
    • 443
    • 444
    • 445
    • 446
    • 447
    • 448
    • 449
    • 450
    • 451
    • 452
    • 453
    • 454
    • 455
    • 456
    • 457
    • 458
    • 459
    • 460
    • 461
    • 462
    • 463
    • 464
    • 465
    • 466
    • 467
    • 468
    • 469
    • 470
    • 471
    • 472
    • 473
    • 474
    • 475
    • 476
    • 477
    • 478
    • 479
    • 480
    • 481
    • 482
    • 483
    • 484
    • 485
    • 486
    • 487
    • 488
    • 489
    • 490
    • 491
    • 492
    • 493
    • 494
    • 495
    • 496
    • 497
    • 498
    • 499
    • 500
    • 501
    • 502
    • 503
    • 504
    • 505
    • 506
    • 507
    • 508
    • 509
    • 510
    • 511
    • 512
    • 513
    • 514
    • 515
    • 516
    • 517
    • 518
    • 519
    • 520
    • 521
    • 522
    • 523
    • 524
    • 525
    • 526
    • 527
    • 528
    • 529
    • 530
    • 531
    • 532
    • 533
    • 534
    • 535
    • 536
    • 537
    • 538
    • 539
    • 540
    • 541
    • 542
    • 543
    • 544
    • 545
    • 546
    • 547
    • 548
    • 549
    • 550
    • 551
    • 552
    • 553
    • 554
    • 555
    • 556
    • 557
    • 558
    • 559
    • 560
    • 561
    • 562
    • 563
    • 564
    • 565
    • 566
    • 567
    • 568
    • 569
    • 570
    • 571
    • 572
    • 573
    • 574
    • 575
    • 576
    • 577
    • 578
    • 579
    • 580
    • 581
    • 582
    • 583
    • 584
    • 585
    • 586
    • 587
    • 588
    • 589
    • 590
    • 591
    • 592
    • 593
    • 594
    • 595
    • 596
    • 597
    • 598
    • 599
    • 600
    • 601
    • 602
    • 603
    • 604
    • 605
    • 606
    • 607
    • 608
    • 609
    • 610
    • 611
    • 612
    • 613
    • 614
    • 615
    • 616
    • 617
    • 618
    • 619
    • 620
    • 621
    • 622
    • 623
    • 624
    • 625
    • 626
    • 627
    • 628
    • 629
    • 630
    • 631
    • 632
    • 633
    • 634
    • 635
    • 636
    • 637
    • 638
    • 639
    • 640
    • 641
    • 642
    • 643
    • 644
    • 645
    • 646
    • 647
    • 648
    • 649
    • 650
    • 651
    • 652
    • 653
    • 654
    • 655
    • 656
    • 657
    • 658
    • 659
    • 660
    • 661
    • 662
    • 663
    • 664
    • 665
    • 666
    • 667
    • 668
    • 669
    • 670
    • 671
    • 672
    • 673
    • 674
    • 675
    • 676
    • 677
    • 678
    • 679
    • 680
    • 681
    • 682
    • 683
    • 684
    • 685
    • 686
    • 687
    • 688
    • 689
    • 690
    • 691
    • 692
    • 693
    • 694
    • 695
    • 696
    • 697
    • 698
    • 699
    • 700
    • 701
    • 702
    • 703
    • 704
    • 705
    • 706
    • 707
    • 708
    • 709
    • 710
    • 711
    • 712
    • 713
    • 714
    • 715
    • 716
    • 717
    • 718
    • 719
    • 720
    • 721
    • 722
    • 723
    • 724
    • 725
    • 726
    • 727
    • 728
    • 729
    • 730
    • 731
    • 732
    • 733
    • 734
    • 735
    • 736
    • 737
    • 738
    • 739
    • 740
    • 741
    • 742
    • 743
    • 744
    • 745
    • 746
    • 747
    • 748
    • 749
    • 750
    • 751

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

  • 相关阅读:
    计算机毕业设计ssm高校求职招聘智能推荐1875f系统+程序+源码+lw+远程部署
    sass的常用语法总结
    协议-序列化-http-Cookie-Session-https
    Jdbc初测试(一)
    新华三H3CNE网络工程师认证—路由基础
    WPF中在MVVM模式下实现导航功能
    MySQL并行复制
    Java并发编程异步操作Future和FutureTask
    计算机毕业设计(附源码)python语言学习系统
    git常用命令
  • 原文地址:https://blog.csdn.net/sheziqiong/article/details/126760380