x01.paint: 绘图程序
绘图程序结构简单,逻辑也不复杂,例如在工具栏 (tool_frame) 中选择画线 (draw_line), 在选项栏(top_frame) 设置,然后在画布 (canvas_frame) 中进行绘制即可。其他如画方画园等,无论是操作还是实现,都基本类同。
1. 效果图:
2. 代码:
import os import sys import tkinter as tk from tkinter import colorchooser, messagebox, filedialog # 为引用 utils,在 site-packages 目录下新建 mypath.pth 文件, # 添加所需导入模块的目录路径, 如 ‘x01.lab/py/’ 所在路径。 import utils from paint.core import CanvasFrame, R, ToolFrame, TopFrame sys.path.append(utils.R.CurrentDir) class MainWindow(tk.Tk): def __init__(self): super().__init__() self.title('x01.paint') utils.R.win_center(self) self.menu = tk.Menu(self) utils.R.generate_menus(self,['file', 'edit', 'help'], sepindies=(2, 4)) self.configure(menu=self.menu) self.top_frame = TopFrame(self) self.top_frame.configure(height=25) self.top_frame.pack(side='top', fill='x', pady=2) self.tool_frame = ToolFrame(self) self.tool_frame.configure(width=80, relief='raised') self.tool_frame.pack(side='left', fill='y', pady=3) self.canvas_frame = CanvasFrame(self) self.canvas_frame.pack(side='right', fill='both', expand='yes') self.tool_frame.tool_click(0) def file_1_new(self): self.canvas_frame.canvas.delete(tk.ALL) self.canvas_frame.canvas.config(bg='white') def file_2_save(self): filename = filedialog.asksaveasfilename(master=self, title='Save', filetypes=[('postscript file', '*.ps'), ('All Files', '*.*')]) if not filename: return self.canvas_frame.canvas.postscript(file=filename, colormode='color') messagebox.showinfo(title=self.title, message=filename + ' Save success!') def file_3_quit(self): self.destroy() def edit_1_undo(self): items = list(self.canvas_frame.canvas.find('all')) try: last_item = items.pop() except IndexError: return self.canvas_frame.canvas.delete(last_item) def edit_2_zoom_in(self): self.canvas_frame.canvas.scale('all', 0,0,1.2,1.2) self.canvas_frame.canvas.config(scrollregion=self.canvas_frame.canvas.bbox(tk.ALL)) def edit_3_zoom_out(self): self.canvas_frame.canvas.scale('all', 0,0,0.8,0.8) self.canvas_frame.canvas.config(scrollregion=self.canvas_frame.canvas.bbox(tk.ALL)) def help_1_about(self): messagebox.showinfo('x01.paint', '绘图程序,版权属于x01(黄雄)所有。') if __name__ == "__main__": win = MainWindow() win.mainloop()
import cmath import math import os import sys import tkinter as tk from tkinter import colorchooser, ttk import utils class R: Functions = ( "draw_line", "draw_oval", "draw_rectangle", "draw_arc", "draw_triangle", "draw_star", "draw_irregular_line", "draw_super_shape", "draw_text", "delete_item", "fill_item", "duplicate_item", "move_to_top", "drag_item", "enlarge_item_size", "reduce_item_size" ) SuperShapes = { "shape A": (1.5, 1.5, 5, 2, 7, 7), "shape B": (1.5, 1.5, 3, 5, 18, 18), "shape C": (1.4, 1.4, 4, 2, 4, 13), "shape D": (1.6, 1.6, 7, 3, 4, 17), "shape E": (1.9, 1.9, 7, 3, 6, 6), "shape F": (4, 4, 19, 9, 14, 11), "shape G": (12, 12, 1, 15, 20, 3), "shape H": (1.5, 1.5, 8, 1, 1, 8), "shape I": (1.2, 1.2, 8, 1, 5, 8), "shape J": (8, 8, 3, 6, 6, 6), "shape K": (8, 8, 2, 1, 1, 1), "shape L": (1.1, 1.1, 16, 0.5, 0.5, 16) } class CanvasFrame(tk.Frame): current_item = None fill = 'red' outline = 'red' width = 2.0 number_of_spokes = 5 arrow = None dash = None x1,y1,x2,y2 = 0,0,0,0 selected_super_shape = 'shape A' def __init__(self, master): super().__init__(master) self.master = master self.init_canvas() self.bind_events() def bind_events(self): self.canvas.bind('<Button-1>', self.mouse_left_pressed) self.canvas.bind('<Button1-Motion>', self.mouse_pressed_motion) self.canvas.bind('<Motion>', self.mouse_unpressed_motion) def mouse_left_pressed(self, e=None): self.x1 = self.x2 = e.x self.y1 = self.y2 = e.y self.execute_selected_method() def mouse_pressed_motion(self, e=None): self.x2 = e.x self.y2 = e.y self.canvas.delete(self.current_item) self.execute_selected_method() def mouse_unpressed_motion(self, e=None): self.master.tool_frame.current_coordinate_label.config(text='x:{}\ny:{}'.format(e.x,e.y)) def init_canvas(self): self.canvas = tk.Canvas(self, background='white', width=500, height=500, scrollregion=(0,0,800,800)) x_scroll = tk.Scrollbar(self, orient='horizontal') x_scroll.pack(side='bottom', fill='x') x_scroll.config(command=self.canvas.xview) y_scroll = tk.Scrollbar(self, orient='vertical') y_scroll.pack(side='right', fill='y') y_scroll.config(command=self.canvas.yview) self.canvas.config(xscrollcommand=x_scroll.set, yscrollcommand=y_scroll.set) self.canvas.pack(side='right', fill='both', expand='yes') def execute_selected_method(self): self.current_item = None func = getattr(self, self.master.tool_frame.selected_function, self.function_not_defined) func() def function_not_defined(self): pass def draw_line(self): self.current_item = self.canvas.create_line(self.x1, self.y1, self.x2, self.y2, fill=self.fill, width=self.width, arrow=self.arrow, dash=self.dash) def draw_oval(self): self.current_item = self.canvas.create_oval(self.x1, self.y1, self.x2, self.y2, outline=self.outline, fill=self.fill, width=self.width) def draw_rectangle(self): self.current_item = self.canvas.create_rectangle(self.x1, self.y1, self.x2, self.y2, outline=self.outline, fill=self.fill, width=self.width) def draw_arc(self): self.current_item = self.canvas.create_arc(self.x1,self.y1,self.x2, self.y2, outline=self.outline, fill=self.fill, width=self.width) def draw_triangle(self): dx = self.x2 - self.x1 dy = self.y2 - self.y1 z = complex(dx,dy) radius, angle0 = cmath.polar(z) edges = 3 points = list() for edge in range(edges): angle = angle0 + edge * (2*math.pi)/edges points.append(self.x1 + radius * math.cos(angle)) points.append(self.y1 + radius * math.sin(angle)) self.current_item = self.canvas.create_polygon(points, outline=self.outline, fill=self.fill, width=self.width) def draw_star(self): dx = self.x2 - self.x1 dy = self.y2 - self.y1 z = complex(dx,dy) radius_out, angle0 = cmath.polar(z) radius_in = radius_out / 2 points = [] for edge in range(self.number_of_spokes): angle = angle0 + edge * (2*math.pi) / self.number_of_spokes points.append(self.x1 + radius_out * math.cos(angle)) points.append(self.y1 + radius_out * math.sin(angle)) angle += math.pi / self.number_of_spokes points.append(self.x1 + radius_in * math.cos(angle)) points.append(self.y1 + radius_in * math.sin(angle)) self.current_item = self.canvas.create_polygon(points, outline=self.outline, fill=self.fill, width=self.width) def draw_irregular_line(self): self.current_item = self.canvas.create_line(self.x1,self.y1,self.x2,self.y2, fill=self.fill, width=self.width) self.canvas.bind('<B1-Motion>', self.update_irregular_line) def update_irregular_line(self, e=None): self.x1, self.y1 = self.x2, self.y2 self.x2,self.y2 = e.x, e.y self.draw_irregular_line() def draw_super_shape(self): points = self.get_shape_points(self.selected_super_shape) self.current_item = self.canvas.create_polygon(points, fill=self.fill, outline=self.outline, width=self.width) def get_shape_points(self, key): a,b,m,n1,n2,n3 = R.SuperShapes[key] points = [] for i in self.float_range(0, 2*math.pi, 0.01): raux = (abs(1 / a * abs(math.cos(m*i/4))) ** n2 + abs(1 / b * abs(math.sin(m*i/4))) ** n3) r = abs(raux) ** (-1/n1) x = self.x2 + r * math.cos(i) y = self.y2 + r * math.sin(i) points.extend((x,y)) return points def float_range(self, start, end, step): while start < end: yield start start += step def draw_text(self): text = self.master.top_frame.text_entry.get() fontsize = self.master.top_frame.fontsize_spinbox.get() self.current_item = self.canvas.create_text(self.x2,self.y2, text=text, font=('', fontsize), fill=self.fill) def delete_item(self): self.current_item = None self.canvas.delete('current') def fill_item(self): try: self.canvas.itemconfig('current', fill=self.fill, outline=self.outline) except TclError: self.canvas.itemconfig('current', fill=self.fill) def duplicate_item(self): try: function_name = 'create_' + self.canvas.type('current') except TypeError: return coordinates = tuple(map(lambda i: i+10, self.canvas.coords('current'))) configs = self.get_configs() self.function_wrapper(function_name, coordinates, configs) def get_configs(self): configs = {} for k,v in self.canvas.itemconfig('current').items(): if v[-1] and v[-1] not in ['0', '0.0', '0,0', 'current']: configs[k] = v[-1] return configs def function_wrapper(self, function_name, *arg, **kwargs): func = getattr(self.canvas, function_name) func(*arg, **kwargs) def move_to_top(self): self.current_item = None self.canvas.tag_raise('current') def drag_item(self): self.canvas.move('current', self.x2-self.x1, self.y2-self.y1) self.canvas.bind('<B1-Motion>', self.drag_update) def drag_update(self, e=None): self.x1, self.y1 = self.x2, self.y2 self.x2, self.y2 = e.x, e.y self.drag_item() def enlarge_item_size(self): self.current_item = None if self.canvas.find_withtag('current'): self.canvas.scale('current', self.x2, self.y2, 1.2, 1.2) self.canvas.config(scrollregion=self.canvas.bbox(tk.ALL)) def reduce_item_size(self): self.current_item = None if self.canvas.find_withtag('current'): self.canvas.scale('current', self.x2, self.y2, 0.8, 0.8) self.canvas.config(scrollregion=self.canvas.bbox(tk.ALL)) class ToolFrame(tk.Frame): def __init__(self, master): super().__init__(master=master) self.master = master self.selected_function = R.Functions[0] self.foreground = 'red' self.background = 'white' self.create_tool_buttons() self.create_color_palette() self.create_current_coordinate_label() def create_current_coordinate_label(self): self.current_coordinate_label = tk.Label(self, text='x:0\ny:0') self.current_coordinate_label.grid(row=11,column=1, columnspan=2,padx=1, sticky='w') def create_color_palette(self): self.color_palette = tk.Canvas(self, height=55, width=55) self.color_palette.grid(row=10,column=1,columnspan=2, pady=5, padx=5) self.background_rect = self.color_palette.create_rectangle(15,15,40,40, outline=self.background, fill=self.background) self.foreground_rect = self.color_palette.create_rectangle(1,1,33,33, outline=self.foreground, fill=self.foreground) self.bind_color_palette() def bind_color_palette(self): self.color_palette.tag_bind(self.background_rect, '<Button-1>', self.set_background_color) self.color_palette.tag_bind(self.foreground_rect, '<Button-1>', self.set_foreground_color) def set_foreground_color(self, e=None): self.foreground = colorchooser.askcolor(title='select foreground color')[-1] self.color_palette.itemconfig(self.foreground_rect, outline=self.foreground, fill=self.foreground) def set_background_color(self, e=None): self.background = colorchooser.askcolor(title='select background color')[-1] self.color_palette.itemconfig(self.background_rect, outline=self.background, fill=self.background) def create_tool_buttons(self): for i, name in enumerate(R.Functions): icon = tk.PhotoImage(file=os.path.join(utils.R.CurrentDir, 'paint/icons/'+name+'.gif')) self.button = tk.Button(self, image=icon, command=lambda i=i: self.tool_click(i)) self.button.grid(row=i//2,column=1+i%2, sticky='nsew') self.button.image=icon def tool_click(self, i): self.selected_function = R.Functions[i] self.remove_top_function() self.add_top_function() self.master.canvas_frame.bind_events() def remove_top_function(self): for child in self.master.top_frame.winfo_children(): child.destroy() def add_top_function(self): name = self.selected_function.replace('_', ' ').capitalize() + ':' tk.Label(self.master.top_frame, text=name).pack(side='left', padx=5) icon = tk.PhotoImage(file=os.path.join(utils.R.CurrentDir, 'paint/icons/'+self.selected_function+'.gif')) label = tk.Label(self.master.top_frame, image=icon) label.image = icon label.pack(side='left') self.master.top_frame.create_options(self.selected_function) class TopFrame(tk.Frame): def __init__(self, master): super().__init__(master=master) def create_options(self, selected_function): name = '{}_options'.format(selected_function) func = getattr(self, name, self.function_not_defined) func() def function_not_defined(self): pass def draw_line_options(self): self.create_fill_combobox() self.create_width_combobox() self.create_arrow_combobox() self.create_dash_combobox() def draw_oval_options(self): self.create_fill_combobox() self.create_outline_combobox() self.create_width_combobox() def draw_rectangle_options(self): self.create_fill_combobox() self.create_outline_combobox() self.create_width_combobox() def draw_arc_options(self): self.create_fill_combobox() self.create_outline_combobox() self.create_width_combobox() def draw_triangle_options(self): self.create_fill_combobox() self.create_outline_combobox() self.create_width_combobox() def draw_star_options(self): self.create_spokes_combobox() self.create_fill_combobox() self.create_outline_combobox() self.create_width_combobox() def draw_irregular_line_options(self): self.create_fill_combobox() self.create_width_combobox() def draw_super_shape_options(self): self.create_shapes_combobox() self.create_fill_combobox() self.create_outline_combobox() self.create_width_combobox() def draw_text_options(self): self.create_text_entry() self.create_fontsize_spinbox() self.create_fill_combobox() def fill_item_options(self): self.create_fill_combobox() self.create_outline_combobox() def create_fill_combobox(self): tk.Label(self,text='Fill:').pack(side='left', padx=5) self.fill_combobox = ttk.Combobox(self, state='readonly', width=5) self.fill_combobox.pack(side='left') self.fill_combobox['values'] = ('none', 'fg', 'bg', 'black', 'white') self.fill_combobox.bind('<<ComboboxSelected>>', self.set_fill) self.fill_combobox.set(self.master.canvas_frame.fill) def create_width_combobox(self): tk.Label(self, text='Width:').pack(side='left', padx=5) self.width_combobox = ttk.Combobox(self, state='readonly', width=3) self.width_combobox.pack(side='left') self.width_combobox['values'] = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) self.width_combobox.bind('<<ComboboxSelected>>', self.set_width) self.width_combobox.set(1) def create_arrow_combobox(self): tk.Label(self, text='Arrow:').pack(side='left', padx=5) self.arrow_combobox = ttk.Combobox(self, state='readonly', width=5) self.arrow_combobox.pack(side='left') self.arrow_combobox['values'] = ('none', 'first', 'last', 'both') self.arrow_combobox.bind('<<ComboboxSelected>>', self.set_arrow) self.arrow_combobox.set('none') def create_dash_combobox(self): tk.Label(self, text='Dash:').pack(side='left', padx=5) self.dash_combobox = ttk.Combobox(self, state='readonly', width=5) self.dash_combobox.pack(side='left') self.dash_combobox['values'] = ('none', 'small', 'mediun', 'large') self.dash_combobox.bind('<<ComboboxSelected>>', self.set_dash) self.dash_combobox.set('none') def create_outline_combobox(self): tk.Label(self, text='Outline:').pack(side='left', padx=5) self.outline_combobox = ttk.Combobox(self, state='readonly', width=5) self.outline_combobox.pack(side='left') self.outline_combobox['values'] = ('none', 'fg', 'bg', 'black', 'white') self.outline_combobox.bind('<<ComboboxSelected>>', self.set_outline) self.outline_combobox.set(self.master.canvas_frame.outline) def create_spokes_combobox(self): tk.Label(self,text='Spokes:').pack(side='left', padx=5) self.spokes_combobox = ttk.Combobox(self, state='readonly', width=5) self.spokes_combobox.pack(side='left') self.spokes_combobox['values'] = tuple(i for i in range(5,50)) self.spokes_combobox.bind('<<ComboboxSelected>>', self.set_spokes) self.spokes_combobox.set(5) def create_shapes_combobox(self): tk.Label(self,text='Shapes:').pack(side='left', padx=5) self.shapes_combobox = ttk.Combobox(self, state='readonly', width=8) self.shapes_combobox.pack(side='left') self.shapes_combobox['values'] = sorted(tuple(i for i in R.SuperShapes.keys())) self.shapes_combobox.bind('<<ComboboxSelected>>', self.set_shapes) self.shapes_combobox.set(self.master.canvas_frame.selected_super_shape) def create_text_entry(self): tk.Label(self, text='Text:').pack(side='left', padx=5) self.text_entry = tk.Entry(self, width=20) self.text_entry.pack(side='left') def create_fontsize_spinbox(self): tk.Label(self, text='Font size:').pack(side='left', padx=5) v = tk.IntVar() v.set(12) self.fontsize_spinbox = tk.Spinbox(self, from_=2,to=100,width=3, textvariable=v) self.fontsize_spinbox.pack(side='left') def set_width(self, e=None): self.master.canvas_frame.width = float(self.width_combobox.get()) def set_fill(self, e=None): clr = self.fill_combobox.get() if clr == 'none': self.master.canvas_frame.fill = '' elif clr == 'fg': self.master.canvas_frame.fill = self.master.tool_frame.foreground elif clr == 'bg': self.master.canvas_frame.fill = self.master.tool_frame.background else : self.master.canvas_frame.fill = clr def set_arrow(self, e=None): self.master.canvas_frame.arrow = self.arrow_combobox.get() def set_dash(self, e=None): dash = self.fill_combobox.get() if dash == 'none': self.master.canvas_frame.dash = None elif dash == 'small': self.master.canvas_frame.dash = 1 elif dash == 'medium': self.master.canvas_frame.dash = 15 elif dash == 'large': self.master.canvas_frame.dash = 100 def set_outline(self, e=None): v = self.outline_combobox.get() if v == 'none': self.master.canvas_frame.outline = '' elif v == 'fg': self.master.canvas_frame.outline = self.master.tool_frame.foreground elif v == 'bg': self.master.canvas_frame.outline = self.master.tool_frame.background else: self.master.canvas_frame.outline = v def set_spokes(self, e=None): self.master.canvas_frame.number_of_spokes = int(self.spokes_combobox.get()) def set_shapes(self, e=None): self.master.canvas_frame.selected_super_shape = self.shapes_combobox.get() if __name__ == "__main__": pass
3. 下载: