re-added all UI changes, removed global variable
88
framework.py
Normal file
@ -0,0 +1,88 @@
|
||||
"""
|
||||
Chapter 6: Paint Application
|
||||
Developing a Tiny Framework
|
||||
Tkinter GUI Application Development Blueprints
|
||||
"""
|
||||
import tkinter as tk
|
||||
|
||||
|
||||
class Framework():
|
||||
|
||||
"""
|
||||
GUIFramework is a class that provides a higher level of abstraction for
|
||||
the development of Tkinter graphic user interfaces (GUIs).
|
||||
Every class that uses this GUI framework must inherit from this class
|
||||
and should pass the root window as an argument to this class by calling
|
||||
the super method as follows:
|
||||
super().__init__(root)
|
||||
Building Menus:
|
||||
To build a menu, call build_menu() method with one argument for
|
||||
menu_definition, where menu_definition is a tuple where each item is a string of the
|
||||
format:
|
||||
'Top Level Menu Name - MenuItemName/Accelrator/Commandcallback/Underlinenumber'.
|
||||
MenuSeparator is denoted by a string 'sep'.
|
||||
For instance, passing this tuple as an argument to this method
|
||||
menu_definition = (
|
||||
'File - &New/Ctrl+N/new_file, &Open/Ctrl+O/openfile, &Save/Ctrl+S/save, Save&As//saveas, sep, Exit/Alt+F4/close',
|
||||
'Edit - Cut/Ctrl+X/cut, Copy/Ctrl+C/copy, Paste/Ctrl+V/paste, Sep',
|
||||
)
|
||||
will generate a File and Edit Menu Buttons with listed menu items for each of the buttons.
|
||||
"""
|
||||
menu_items = None
|
||||
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
|
||||
def build_menu(self, menu_definitions):
|
||||
menu_bar = tk.Menu(self.root)
|
||||
for definition in menu_definitions:
|
||||
menu = tk.Menu(menu_bar, tearoff=0)
|
||||
top_level_menu, pull_down_menus = definition.split('-')
|
||||
menu_items = map(str.strip, pull_down_menus.split(','))
|
||||
for item in menu_items:
|
||||
self._add_menu_command(menu, item)
|
||||
menu_bar.add_cascade(label=top_level_menu, menu=menu)
|
||||
self.root.config(menu=menu_bar)
|
||||
|
||||
def _add_menu_command(self, menu, item):
|
||||
if item == 'sep':
|
||||
menu.add_separator()
|
||||
else:
|
||||
menu_label, accelrator_key, command_callback = item.split('/')
|
||||
try:
|
||||
underline = menu_label.index('&')
|
||||
menu_label = menu_label.replace('&', '', 1)
|
||||
except ValueError:
|
||||
underline = None
|
||||
menu.add_command(label=menu_label, underline=underline,
|
||||
accelerator=accelrator_key, command=eval(command_callback))
|
||||
|
||||
|
||||
class TestThisFramework(Framework):
|
||||
|
||||
def new_file(self):
|
||||
print('new tested OK')
|
||||
|
||||
def open_file(self):
|
||||
print ('open tested OK')
|
||||
|
||||
def undo(self):
|
||||
print ('undo tested OK')
|
||||
|
||||
def options(self):
|
||||
print ('options tested OK')
|
||||
|
||||
def about(self):
|
||||
print ('about tested OK')
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
root = tk.Tk()
|
||||
menu_items = (
|
||||
'File- &New/Ctrl+N/self.new_file, &Open/Ctrl+O/self.open_file',
|
||||
'Edit- Undo/Ctrl+Z/self.undo, sep, Options/Ctrl+T/self.options',
|
||||
'About- About//self.about'
|
||||
)
|
||||
app = TestThisFramework(root)
|
||||
app.build_menu(menu_items)
|
||||
root.mainloop()
|
BIN
icons/canvas_top_test.png
Normal file
After Width: | Height: | Size: 388 B |
BIN
icons/delete_item.gif
Normal file
After Width: | Height: | Size: 600 B |
BIN
icons/drag_item.gif
Normal file
After Width: | Height: | Size: 605 B |
BIN
icons/draw_arc.gif
Normal file
After Width: | Height: | Size: 541 B |
BIN
icons/draw_irregular_line.gif
Normal file
After Width: | Height: | Size: 569 B |
BIN
icons/draw_line.gif
Normal file
After Width: | Height: | Size: 203 B |
BIN
icons/draw_oval.gif
Normal file
After Width: | Height: | Size: 597 B |
BIN
icons/draw_rectangle.gif
Normal file
After Width: | Height: | Size: 369 B |
BIN
icons/draw_star.gif
Normal file
After Width: | Height: | Size: 298 B |
BIN
icons/draw_super_shape.gif
Normal file
After Width: | Height: | Size: 996 B |
BIN
icons/draw_text.gif
Normal file
After Width: | Height: | Size: 281 B |
BIN
icons/draw_triangle.gif
Normal file
After Width: | Height: | Size: 303 B |
BIN
icons/duplicate_item.gif
Normal file
After Width: | Height: | Size: 329 B |
BIN
icons/enlarge_item_size.gif
Normal file
After Width: | Height: | Size: 310 B |
BIN
icons/fill_item.gif
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
icons/move_to_top.gif
Normal file
After Width: | Height: | Size: 577 B |
BIN
icons/reduce_item_size.gif
Normal file
After Width: | Height: | Size: 310 B |
52
ui.py
@ -38,12 +38,11 @@ from tkinter import filedialog
|
||||
import framework
|
||||
import decensor
|
||||
|
||||
|
||||
class PaintApplication(framework.Framework):
|
||||
|
||||
def __init__(self, root):
|
||||
super().__init__(root)
|
||||
|
||||
self.circle = 0
|
||||
self.drawn_img = None
|
||||
self.screen_width = root.winfo_screenwidth()
|
||||
self.screen_height = root.winfo_screenheight()
|
||||
@ -65,6 +64,19 @@ class PaintApplication(framework.Framework):
|
||||
self.create_gui()
|
||||
self.bind_mouse()
|
||||
|
||||
# Create blank image to avoid errors with irregular line drawing on blank canvas
|
||||
# TODO: Optimize this, seems too inefficient
|
||||
self.canvas.img = Image.new('RGB', (800,1280), (255, 255, 255))
|
||||
self.canvas.img_width, self.canvas.img_height = self.canvas.img.size
|
||||
# make reference to image to prevent garbage collection
|
||||
# https://stackoverflow.com/questions/20061396/image-display-on-tkinter-canvas-not-working
|
||||
self.canvas.tk_img = ImageTk.PhotoImage(self.canvas.img)
|
||||
self.canvas.config(width=self.canvas.img_width, height=self.canvas.img_height)
|
||||
self.canvas.create_image(self.canvas.img_width / 2.0, self.canvas.img_height / 2.0, image=self.canvas.tk_img)
|
||||
|
||||
self.drawn_img = Image.new("RGBA", self.canvas.img.size)
|
||||
self.drawn_img_draw = ImageDraw.Draw(self.drawn_img)
|
||||
|
||||
def on_new_file_menu_clicked(self, event=None):
|
||||
self.start_new_project()
|
||||
|
||||
@ -77,7 +89,7 @@ class PaintApplication(framework.Framework):
|
||||
self.open_image()
|
||||
|
||||
def open_image(self):
|
||||
self.file_name = filedialog.askopenfilename(master=self.root, filetypes = [("All Files","*.*")], title="Open...")
|
||||
self.file_name = filedialog.askopenfilename(master=self.root, title="Open...")
|
||||
print(self.file_name)
|
||||
self.canvas.img = Image.open(self.file_name)
|
||||
self.canvas.img_width, self.canvas.img_height = self.canvas.img.size
|
||||
@ -118,7 +130,7 @@ class PaintApplication(framework.Framework):
|
||||
|
||||
def on_save_as_menu_clicked(self):
|
||||
file_name = filedialog.asksaveasfilename(
|
||||
master=self.root, filetypes=[('All Files', ('*.ps', '*.ps'))], title="Save...")
|
||||
master=self.root, filetypes=[('All Files', ('*.png'))], title="Save...")
|
||||
if not file_name:
|
||||
return
|
||||
self.file_name = file_name
|
||||
@ -165,10 +177,11 @@ class PaintApplication(framework.Framework):
|
||||
def on_decensor_menu_clicked(self, event=None):
|
||||
combined_img = Image.alpha_composite(self.canvas.img.convert('RGBA'), self.drawn_img)
|
||||
decensorer = decensor.Decensor()
|
||||
decensorer.decensor_image(combined_img.convert('RGB'), self.file_name + ".png")
|
||||
print(self.file_name)
|
||||
decensorer.decensor_image(combined_img.convert('RGB'),combined_img.convert('RGB'), self.file_name + ".png")
|
||||
save_path = self.file_name + ".png"
|
||||
messagebox.showinfo(
|
||||
"Decensoring", "Decensoring complete!")
|
||||
|
||||
"Decensoring", "Decensoring complete! image saved to {save_path}".format(save_path=save_path))
|
||||
def on_about_menu_clicked(self, event=None):
|
||||
# messagebox.showinfo(
|
||||
# "Decensoring", "Decensoring in progress.")
|
||||
@ -218,11 +231,30 @@ class PaintApplication(framework.Framework):
|
||||
|
||||
self.canvas.bind("<B1-Motion>", self.draw_irregular_line_update_x_y)
|
||||
|
||||
# Creates circular indicator for brush size, modified from https://stackoverflow.com/questions/42631060/draw-a-defined-size-circle-around-cursor-in-tkinter-python
|
||||
def motion(self, event=None):
|
||||
x, y = event.x, event.y
|
||||
# the addition is just to center the oval around the center of the mouse
|
||||
# remove the the +3 and +7 if you want to center it around the point of the mouse
|
||||
|
||||
|
||||
self.canvas.delete(self.circle) # to refresh the circle each motion
|
||||
|
||||
radius = self.brush_width/2.0 # change this for the size of your circle
|
||||
|
||||
x_max = x + radius
|
||||
x_min = x - radius
|
||||
y_max = y + radius
|
||||
y_min = y - radius
|
||||
|
||||
self.circle = self.canvas.create_oval(x_max, y_max, x_min, y_min, outline="black")
|
||||
|
||||
def draw_irregular_line_update_x_y(self, event=None):
|
||||
self.start_x, self.start_y = self.end_x, self.end_y
|
||||
self.end_x, self.end_y = self.adjust_canvas_coords(event.x, event.y)
|
||||
# self.motion(event)
|
||||
self.draw_irregular_line()
|
||||
|
||||
self.motion(event)
|
||||
def draw_irregular_line_options(self):
|
||||
self.create_fill_options_combobox()
|
||||
self.create_width_options_combobox()
|
||||
@ -414,19 +446,23 @@ class PaintApplication(framework.Framework):
|
||||
self.start_x = self.end_x = self.canvas.canvasx(event.x)
|
||||
self.start_y = self.end_y = self.canvas.canvasy(event.y)
|
||||
self.execute_selected_method()
|
||||
self.motion(event)
|
||||
|
||||
def on_mouse_button_pressed_motion(self, event):
|
||||
self.end_x = self.canvas.canvasx(event.x)
|
||||
self.end_y = self.canvas.canvasy(event.y)
|
||||
self.canvas.delete(self.current_item)
|
||||
self.motion(event)
|
||||
self.execute_selected_method()
|
||||
|
||||
def on_mouse_button_released(self, event):
|
||||
self.end_x = self.canvas.canvasx(event.x)
|
||||
self.end_y = self.canvas.canvasy(event.y)
|
||||
self.motion(event)
|
||||
|
||||
def on_mouse_unpressed_motion(self, event):
|
||||
self.show_current_coordinates(event)
|
||||
self.motion(event)
|
||||
|
||||
def create_gui(self):
|
||||
self.create_menu()
|
||||
|