目录
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()