From 3ab32da9703f04d3b1a5cc37b5c41e8206df489e Mon Sep 17 00:00:00 2001 From: smethan Date: Thu, 1 Nov 2018 09:27:59 -0500 Subject: [PATCH] re-added all UI changes, removed global variable --- framework.py | 88 ++++++++++++++++++++++++++++++++++ icons/canvas_top_test.png | Bin 0 -> 388 bytes icons/delete_item.gif | Bin 0 -> 600 bytes icons/drag_item.gif | Bin 0 -> 605 bytes icons/draw_arc.gif | Bin 0 -> 541 bytes icons/draw_irregular_line.gif | Bin 0 -> 569 bytes icons/draw_line.gif | Bin 0 -> 203 bytes icons/draw_oval.gif | Bin 0 -> 597 bytes icons/draw_rectangle.gif | Bin 0 -> 369 bytes icons/draw_star.gif | Bin 0 -> 298 bytes icons/draw_super_shape.gif | Bin 0 -> 996 bytes icons/draw_text.gif | Bin 0 -> 281 bytes icons/draw_triangle.gif | Bin 0 -> 303 bytes icons/duplicate_item.gif | Bin 0 -> 329 bytes icons/enlarge_item_size.gif | Bin 0 -> 310 bytes icons/fill_item.gif | Bin 0 -> 1039 bytes icons/move_to_top.gif | Bin 0 -> 577 bytes icons/reduce_item_size.gif | Bin 0 -> 310 bytes ui.py | 52 ++++++++++++++++---- 19 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 framework.py create mode 100644 icons/canvas_top_test.png create mode 100644 icons/delete_item.gif create mode 100644 icons/drag_item.gif create mode 100644 icons/draw_arc.gif create mode 100644 icons/draw_irregular_line.gif create mode 100644 icons/draw_line.gif create mode 100644 icons/draw_oval.gif create mode 100644 icons/draw_rectangle.gif create mode 100644 icons/draw_star.gif create mode 100644 icons/draw_super_shape.gif create mode 100644 icons/draw_text.gif create mode 100644 icons/draw_triangle.gif create mode 100644 icons/duplicate_item.gif create mode 100644 icons/enlarge_item_size.gif create mode 100644 icons/fill_item.gif create mode 100644 icons/move_to_top.gif create mode 100644 icons/reduce_item_size.gif diff --git a/framework.py b/framework.py new file mode 100644 index 0000000..7773360 --- /dev/null +++ b/framework.py @@ -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() \ No newline at end of file diff --git a/icons/canvas_top_test.png b/icons/canvas_top_test.png new file mode 100644 index 0000000000000000000000000000000000000000..34c331df3d01840cdf517f846903d292689ef9b2 GIT binary patch literal 388 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7#M*h!yV?A;XsNd-O<;Pfnj4m_n$;oAYUQb zBgmJ5p-PQ`p`nF=;TKS-;RORjsR0ASs{{rHs~HRo;stYd1=;{5n3BBRT^Jbeb_p*5 z^4Lo}eO=ifamlf$XsoL`2@>k@ba4!+xb^moAuj_15A%Xw=6AbpY-M4YAm^$ez`?@Q z=s+*(LAin1ul6F|Y1(E$cd3@RMwFx^mZVxG7o`Fz1|tJQ3tdA4U1Ng~14}DoGb)78&qol`;+09K1wc>n+a literal 0 HcmV?d00001 diff --git a/icons/delete_item.gif b/icons/delete_item.gif new file mode 100644 index 0000000000000000000000000000000000000000..f04bcd07bc763a9ba1b4941ddbb1e49663cb64d8 GIT binary patch literal 600 zcmZ?wbhEHb6k!lyI2OWCQ&ZE}*x1z6)ZE1alCQY6^ zdCHV2vu4ejJ$v??IdkUCn>T;{{Dlh_E?Tr`@#4iRSFT*OYSo%GYu2t^yKddOjT<*^ z+O%o&=FMBSY}vYX>$Yv%wr}6QW50FJHcV{rdIWw{Jgu`0(-L$IqWXfBEv|{~!R0KUo;L80r~x7=Qp2 zCk*Uc8|s^yTUy)NJ33qJ%)8objEz*44Xpc|ogGX>1>}{KG&}8^o%EE{RFtI6I;^yv zn;lFwr1*?F93&Mi>YdCqC3x6uJC%jRq{T(JnHX4{I&~#fG(|-@SQuG1bm+?Hn=7mE zva#Im)R8fGsKUp_{QR!0p@oV%|ND>ada_2A%35+V3Iz{;W9OY6( zl%|=<+O$nGH!AhSN0O#tYV(1Q9oyL5yxn=-ZQtrY=<_dpuExhjMDqO*KLpK1;$^^V zgd_#rYK8PhxTAo~R!COSX==Ft$|g|2-4+li9egEZchP(e2((VV7V;F_!&W9&ZG51m zb9>;CihkV9WDHW-Uzvh_(~~b`-XxJfMO1cJL?3Kbot(65QK~V%(5*c0U8~WrH0Ty( zdPmJiXAx==q84!3NE7@%Vr zI#)rv%4yd?V+7q2)otQ>%}x!;byI-;Wc$nL+*C@L)zmrE+dCyoX^lLj61m0a5E=k8zUziY=JfQZrke{A^#?h`gGB%U literal 0 HcmV?d00001 diff --git a/icons/draw_arc.gif b/icons/draw_arc.gif new file mode 100644 index 0000000000000000000000000000000000000000..3a76d9e54251f7c95a8f2fc11b57824badf4f0c7 GIT binary patch literal 541 zcmZ?wbhEHb6k!lyIOf1$n77QedaY~Cy1A@AbL+Z%#k?cpiwJeYx=9&BcdrFTD72 z;lQ1V@kIQrgT71e zq|7{>zx;Ob!pm7(9_Q?QnzQR^{_bb_d!FU)eO|u#Vb%6W726(nM2gPM7jH?_@65EE zQEb{@8rW45HKEXHRjdDoK8I~n+;&b6*)cnIXM$ z!PFHCXRcm0f780C)YQh*fQ%jfT2IdS>&-otWFV5V2aqiZOtM^{reE9nAqc_j)9)5J|{KH!pAKbb2^xoBn zcW*p+@ZiIT5C6#pia%KxxftphbQpjD6ekSq(;DiVnp;}i+B-TsQ|x2A+LFzA`5gM2 zlZ-_9nJlK&Cz^^0F*AltOE41`W?`_I7H=jdz{YMlEza0Vkd?!3p0$YxH>d5qrT{LU znEtegjHW=X(0+F(M`tg8!>E35O%G3XHT|GzF0QF=N-{b=fsvh=8pnDuYi1;Q`f~E?CRp5j{<2FnS%9CFiNP8GWIRk@ literal 0 HcmV?d00001 diff --git a/icons/draw_line.gif b/icons/draw_line.gif new file mode 100644 index 0000000000000000000000000000000000000000..69f4804b3ed439b8ab12ae1830b85fb6aad187e8 GIT binary patch literal 203 zcmZ?wbhEHb6k!lyXc1#DDBWvQd%(HzkZ0?Wu)dSAlTIg3KU*;WV(p5nT^nys+j)2H z-uoL)J>7fx<&o>JPu+fd@xlA+Pd?s%@#V?eZ?8Z8`0(Mwf4o5PCkrDNgFJ%{0}z1h zWMK7ppx&2~IWJ??x}4Qo9yJmru{=K%6p{}A>1{9ID3t6!#5FLGEi gBq$X9+2^d7qU6aUT#O9X0JuA@AbL+Z%#k?cpiwJeYx=9&BcdrFTD72 z;l+Z^i=m!DhXDvcal*j9siD59xuvzOy`!_MyQf`@ zm1*K6R6=N zR$sqbT7EMS;AoLvrjqpI!NKI?eR3v2E)pLWUT9_HRa+q8Sjf`F!_OAupyKG*$e_S1 S5)rU);>pPx!CG7_4Auag5}Hl` literal 0 HcmV?d00001 diff --git a/icons/draw_rectangle.gif b/icons/draw_rectangle.gif new file mode 100644 index 0000000000000000000000000000000000000000..0549e2282639cc303a63b2ed53eae9a22b584aee GIT binary patch literal 369 zcmZ?wbhEHb6k!lySgOv@vFXy3UDv1VzA=06jhXvy&fIrv&c2)T58PaE@aDQR57(c4 zwCUXAEf<~~yYc+Qt>?!ce?Ip3%dsb)k3IQv>e0s&Pd=XnGM;_8_V~?>CvPr2{dnQU z*XysoUVHuR=F3kvUwyv$=IhNj-|oEqcK_YCyYId~eEpL*Nz(Yr>|HP7_lPXDk9V<$tY!f>89GnimtrBKn-za#(=iG}DwF?r$ zW>ec)ggR~HL{7C`kTc_w7UCBWC~pIqR}ky(7Zsf#-<8a&C^c*L9K~sMtV%MA zmn>DD-@~e+yk_k>l@<29OtNy@w(pQ->b8&M<~eZi5O<=BI5XSHQ>RZdi~C-A@AbL+Z%#k?cpiwJeYx=9&BcdrFTD72 z;l?!bfo%EEGhD2G3Y;+ zE-WzhAH#xIlV>f`TNx3+<#MiS;thU|R;BEVk3LEF2^~x*&X6uSFMY7VC&9VdE4`&I MtgE}n+mXQ<0O)$Yp#T5? literal 0 HcmV?d00001 diff --git a/icons/draw_super_shape.gif b/icons/draw_super_shape.gif new file mode 100644 index 0000000000000000000000000000000000000000..2b4111ce6474765441f4a6633fa7d224d7b0a7d5 GIT binary patch literal 996 zcmeH`+e?!H9ERU-?wj9q<{X=N%&mQH>YOt|at_49vRMiVO)N;Zbj^mOc$lRvCeD-) zl#)guOX{XB5^biV!14rp&0Smdm>oismAq z6gUN(0jz)>xC}IaX#`xrHJ}Y>2Rf169#vf(+i?*kO{~KKn@!l#0#`HF+KP@2xZUXP zhQ|Y|Rn4-$X;mC|0k8oMFYqWa@wG}dr&iBpX1>VEn##?6uhlLa4C^}GhF-r?Tuerz zpEr})Oco2()R5Imb#+u%~;t;*xXcXeUEIKHaF3MzU0>LMa`yvp8MWTI;<|ofbJRTYtpucPU?|*4P z^$u%9gyn$JYX#!HTLu>Cqy|QqKOJu>!|=K3`g^ v>?!ce?Ip3%dsb)k3IQv>e0s&Pd=XnGM;_8_V~?>CvPr2{dnQU z*XysoUVHuR=F3kvUwyv$=IhNj-|oEqcK_YCyYId~eEA@AbL+Z%#k?cpiwJeYx=9&BcdrFTD72 z;l?!bfo%EEGe>#G3aFs zc;FdxV9u3zk#94D)^5-)m40V*GI5?!ce?Ip3%dsb)k3IQv>e0s&Pd=XnGM;_8_V~?>CvPr2{dnQU z*XysoUVHuR=F3kvUwyv$=IhNj-|oEqcK_YCyYId~eE2+%Zj%+lL>xlwpFOZajB#XG&%#$A2;EaA2) z|JHL=7Pa%U#H%D~0@GEvc^lHWT^Tqz7`QWXyL)^mCg*bcOq%IcB(Y%Ota*JL%a)Y| LtXggF$Y2csi>BJd literal 0 HcmV?d00001 diff --git a/icons/enlarge_item_size.gif b/icons/enlarge_item_size.gif new file mode 100644 index 0000000000000000000000000000000000000000..01f4ace6a7a4e06a879f23301955d965ff98e309 GIT binary patch literal 310 zcmZ?wbhEHb6k!lySgOj?!ce?Ip3%dsb)k3IQv>e0s&Pd=XnGM;_8_V~?>CvPr2{dnQU z*XysoUVHuR=F3kvUwyv$=IhNj-|oEqcK_YCyYId~eEbHBp_RK7IW`lf0)+n?7TzBZD;ntu)j& literal 0 HcmV?d00001 diff --git a/icons/fill_item.gif b/icons/fill_item.gif new file mode 100644 index 0000000000000000000000000000000000000000..d111d9870c399faea51e73d19bf5947b75b940fb GIT binary patch literal 1039 zcmZ?wbhEHb6k!lyc;3OFudi=lU|?cmVs373V`F1)Z|~&j=Eq+$ z=jRt392^o75*8K~9v&VU85tE76%!K^A0J;C8J>`kkeZsBmX?;Do}QJJm7SfPlarI1 zo134XUr~=^oq6EEfg?wb96NUG#E~N> zj~+d7;>5|5C(oWed+yx1ix)0jx^(IC<;z#DT)BGn>Wv#WZr{Fr_wL<$_wL=lfB(UQ z2ag^-di?nDlP6D}K7IP^*|X=*pTBtV;^oViuV24@`}Xa-ckjMGd-~zsyASW*e*uyo zK79K0>GS8$U%q_#_U+q`A3uKn{Q2wGuRnkO{Qdj)-@kwVM*&?zK=CIFBNxMe1|0?< z0Obh=jyDYdIb}RHEI8QAA?x&Uk?=^-QBy?EK*8bAA_oltu?P);1MCvr z{sI;XOi713_+vz7z5JlyF+WHRu}DI6P{?7|@ADvkH_{6%0r?xyjz47_k%`eVv`E_~0 zpKFW%-dy(Y_KN>^SABT(^uvb_{|6IL{K>+|#Zb?n!vF-JIALI)*HGWo+|t_C-qG3B z-Q=$7-qY-^f1=Nz_+#j-p4mf{(z`rH%^Svff;4*UNT`d9iNUCe>ji;lk}~ z>8$N6?kv$EY%js#aQcjcaEG9sC8w>qzLutrnP7*TkcGUNsfLWSyasQl6~Bpzp4wA+ z6`n3*c3oZ7r*e`a-G(d~9XHY$*pwM%{VHxeNN`vp%gErtb3l30(e4%|Mh0sD<-KBG literal 0 HcmV?d00001 diff --git a/icons/reduce_item_size.gif b/icons/reduce_item_size.gif new file mode 100644 index 0000000000000000000000000000000000000000..5cf6f03a918a7a8d1c9a24834667e469ff198e8a GIT binary patch literal 310 zcmZ?wbhEHb6k!lySgOj?!ce?Ip3%dsb)k3IQv>e0s&Pd=XnGM;_8_V~?>CvPr2{dnQU z*XysoUVHuR=F3kvUwyv$=IhNj-|oEqcK_YCyYId~eE*4V9%y0=bZ~f~EqUQW r!J(JB%IoHYvvSo$CbT4lL`O#k`^5OR`FA__xBE<;HhqS(BZD;n$>7x> literal 0 HcmV?d00001 diff --git a/ui.py b/ui.py index 59e3d6b..2595394 100644 --- a/ui.py +++ b/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("", 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()