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()
main.py
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     
core.py

3. 下载:

x01.lab/py/paint

 

posted on 2020-05-24 18:42  x01  阅读(309)  评论(0编辑  收藏  举报

导航