def _make_resizing(self): """ converts width, height values \\ calls subservient resizing functions """ file_names = self.lstbox.get(0, tk.END) if not file_names: return try: width = int(self.width_entry.variable) except ValueError: width = None try: height = int(self.height_entry.variable) except ValueError: height = None filetype = 'PNG' if self.bs_file_type.variable else 'JPEG' if self.bs_sizes.variable == True: MergerScripts.resize_percents(file_names, self.direntry.variable, width, height, filetype) else: MergerScripts.resize_pixels(file_names, self.direntry.variable, width, height, filetype)
def _make_concatenation(self): """ depending on activated flags different combination of scripts is called """ file_names = self.lstbox.get(0, tk.END) if len(file_names) < 2: return image_list = [Image.open(e) for e in file_names] if self.bs_resize.variable: if self.bs_concat.variable: max_width = MergerScripts.find_max_width(image_list) image_list = MergerScripts.resize_all_tomax(image_list, max_width, Image, is_vertical=True) else: max_height = MergerScripts.find_max_height(image_list) image_list = MergerScripts.resize_all_tomax(image_list, max_height, Image, is_vertical=False) if self.bs_concat.variable: result_image = MergerScripts.concatenate_v(image_list, self.bg_color, Image) else: result_image = MergerScripts.concatenate_h(image_list, self.bg_color, Image) _path = self.direntry.variable _path = MergerScripts.make_default_concatenation_path(_path) self.direntry.variable = _path result_image.save(f'{self.direntry.variable}', 'PNG')
def _find_widthheight(self): """ runs through selected image list, \\ returns width and height in pixels """ self.bs_sizes.variable = False self.bs_sizes.strv.variable = self.bs_sizes.falsestr file_names = self.lstbox.get(0, tk.END) if not file_names: self.width_entry.variable = 'none' self.height_entry.variable = 'none' return image_list = [Image.open(e) for e in file_names] if self.bs_maxmin.variable: self.width_entry.variable = MergerScripts.find_max_width( image_list) self.height_entry.variable = MergerScripts.find_max_height( image_list) else: self.width_entry.variable = MergerScripts.find_min_width( image_list) self.height_entry.variable = MergerScripts.find_min_height( image_list)
def _call_askdir(self): """ returns *.png string for a Concatenation tab """ _path = askdirectory() if _path: if self.current_active_tab == 0: _path = ['\\' if e == '/' else e for e in _path] _path.append('\\') _path = MergerScripts.make_default_concatenation_path( ''.join(_path)) self.direntry.variable = _path elif self.current_active_tab == 1: _path = ['\\' if e == '/' else e for e in _path] _path.append('\\') _path = ''.join(_path) self.direntry.variable = _path
def test_change_folder_strip_ext(self): tested_list = ['D:/images/resized1.png', 'D:/images/resized2.jpg'] expected = ['G:/my_images/resized1', 'G:/my_images/resized2'] actual = MergerScripts.change_folder_strip_ext(tested_list, 'G:/my_images/') assert expected == actual
class ImageMerger(): bg_color = '#ffffff' folder = MergerScripts.find_folderpath(sys.argv[0]) # in this list string value for "output path variable" is stored # if you click another tab, path changes output_entry_values = list() output_entry_values.append( MergerScripts.make_default_concatenation_path(folder)) output_entry_values.append(folder) current_active_tab = 0 DARK_BG = 'LightSteelBlue' LIGHT_BG = 'AliceBlue' def __init__(self, master): self.master = master self.configure_main_window() self.tabs_panel = self.add_notebook_panel(master) self.tabs_panel.bind("<ButtonRelease-1>", self._switch_tab_onclick) # region "define Concatenation tab" self.tabs_panel.concatenation_tab = self.append_tab( self.tabs_panel, 'Concatenate') self.tabs_panel.concatenation_tab.grid_ = self.add_grid( self.tabs_panel.concatenation_tab, 4, 6) self.lb_concat = self.add_label(0, 1, self.tabs_panel.concatenation_tab, 'Concatenate Vertically') self.bs_concat = self.add_biscale(0, 0, self.tabs_panel.concatenation_tab, self.lb_concat, 'Concatenate Vertically', 'Concatenate Horizontally') self.lb_resize = self.add_label(1, 1, self.tabs_panel.concatenation_tab, 'Resize to Max') self.bs_resize = self.add_biscale(1, 0, self.tabs_panel.concatenation_tab, self.lb_resize, 'Resize to Max', 'Do not Resize') self.colorbtn = self.add_color_btn( self.tabs_panel.concatenation_tab.grid_[2][0]) self.lb_colorb = self.add_label(2, 1, self.tabs_panel.concatenation_tab, 'Background Color') # endregion # region "define Resizing tab" self.tabs_panel.resizing_tab = self.append_tab(self.tabs_panel, 'Resize') self.tabs_panel.resizing_tab.grid_ = self.add_grid( self.tabs_panel.resizing_tab, 4, 6) self.lb_sizes = self.add_label(0, 1, self.tabs_panel.resizing_tab, 'Percent') self.bs_sizes = self.add_biscale(0, 0, self.tabs_panel.resizing_tab, self.lb_sizes, 'Percent', 'Pixels') self.lb_width = tk.Label(self.tabs_panel.resizing_tab.grid_[1][0], text='W:', bg=self.DARK_BG, font=('Consolas', '14', 'bold')) self.lb_width.pack(side=tk.RIGHT, padx=3) self.width_entry = self.add_size_entry( self.tabs_panel.resizing_tab.grid_[1][1]) self.lb_height = tk.Label(self.tabs_panel.resizing_tab.grid_[1][2], text='H:', bg=self.DARK_BG, font=('Consolas', '14', 'bold')) self.lb_height.pack(side=tk.RIGHT, padx=3) self.height_entry = self.add_size_entry( self.tabs_panel.resizing_tab.grid_[1][3]) self.lb_maxmin = self.add_label(2, 1, self.tabs_panel.resizing_tab, 'maximum sizes') self.bs_maxmin = self.add_biscale(2, 0, self.tabs_panel.resizing_tab, self.lb_maxmin, 'maximum sizes', 'minimum sizes') self.find_widthheight_btn = self.add_wdthgt_btn( 2, 4, self.tabs_panel.resizing_tab) self.lb_file_type = self.add_label(3, 1, self.tabs_panel.resizing_tab, 'save as PNG') self.bs_file_type = self.add_biscale(3, 0, self.tabs_panel.resizing_tab, self.lb_file_type, 'save as PNG', 'save as JPG') # endregion # region "define Files frame" self.files_frame = self.add_file_frame(master) self.files_frame.grid_ = self.add_grid(self.files_frame, 10, 6) self.lb_files = self.add_label(0, 0, self.files_frame, 'Files:') self.lb_files.configure(font=('Consolas', '12', 'bold'), padx=3) self.lstbox = self.add_listbox(1, 0, self.files_frame) self.clear_listboxbtn = self.add_clear_listbox_btn( self.files_frame.grid_[0][3]) self.inputbtn = self.add_inputbtn(self.files_frame.grid_[0][4]) self.excludebtn = self.add_excludebtn(self.files_frame.grid_[0][5]) self.lb_outputdir = self.add_label(6, 0, self.files_frame, 'Output Path:') self.lb_outputdir.configure(font=('Consolas', '12', 'bold'), padx=3) self.direntry = self.add_outputdir_entry(7, 0, self.files_frame, self.output_entry_values[0]) self.outputdir_btn = self.add_outputdir_btn( self.files_frame.grid_[7][5]) self.outputdir_btn = self.add_process_btn(9, 2, self.files_frame) # endregion def _switch_tab_onclick(self, event=None): """ clicking tabs event calls this function """ tab_index = self.tabs_panel.index('current') if tab_index != self.current_active_tab: self.output_entry_values[ self.current_active_tab] = self.direntry.variable self.current_active_tab = tab_index self.direntry.variable = self.output_entry_values[tab_index] self.lstbox.focus_force() else: return def configure_main_window(self): """ main window properties """ self.master.title('ImageFiles Merger') _icon_path = r"img/puzzle.ico" self.master.iconbitmap(_icon_path) self.master.geometry('320x480') self.master.configure(bg=self.DARK_BG) self.master.resizable(False, False) def add_notebook_panel(self, master): """ placed in top side of the app """ nb = ttk.Notebook(master, width=304, height=124) nb.style = ttk.Style() nb.style.theme_create("tabs", parent="alt", settings={ ".": { "configure": { "background": self.DARK_BG } }, "TNotebook": { "configure": { "tabmargins": [2, 5, 2, 0], "borderwidth": 0 } }, "TNotebook.Tab": { "configure": { "padding": [5, 1], "background": self.DARK_BG }, "map": { "background": [("selected", 'AliceBlue')], "expand": [("selected", [1, 1, 1, 0])] } } }) nb.style.theme_use("tabs") nb.pack(expand=1) return nb def append_tab(self, notebook, text): """ creates frame, appends it as a tab """ frame = tk.LabelFrame(notebook, width=300, height=120, bg=self.DARK_BG) notebook.add(frame, text=text) return frame def add_file_frame(self, master): """ file related widget placed here """ _frame = tk.LabelFrame(master, width=300, height=420, relief=tk.FLAT, border=1) _frame.propagate(False) _frame.pack(side=tk.BOTTOM, pady=8) return _frame def add_grid(self, master, sizex=8, sizey=8): """ creates a table structure on a given frame """ result = list() for i in range(sizex): result.append(list()) tk.Grid.rowconfigure(master, i, weight=0) for j in range(sizey): frame = tk.Frame(master, width=50, height=30, bg=self.DARK_BG) frame.grid(row=i, column=j, sticky=tk.NSEW) tk.Grid.columnconfigure(master, j, weight=0) result[i].append(frame) return result def _call_clrchooser(self): """ uses tkinter.dialogs colorchooser \\ sets self.bg_color """ _clr = colorchooser.askcolor() self.colorbtn.configure(background=_clr[1]) self.bg_color = _clr[1] def add_color_btn(self, master): """ if "do not resize" flag is set \\ selecting concatenated image bg color is often needed """ _btn = tk.Button(master, text='', background=self.bg_color, width=3, height=1, command=self._call_clrchooser) master.propagate(False) _btn.pack(pady=8) return _btn def add_biscale(self, row_index, column_index, master, lbl, truestr, falsestr): """ biscale(BinaryScale) is a customized Scale \\ it acts like simple "on/off" lever for an inner variable \\ and changes related Label text by onclick event """ _scale = BinaryScale(master=master, strvalue=lbl, truestr=truestr, falsestr=falsestr, orient='horizontal', from_=0, to=1) _scale.configure(background='Teal', length=25, width=8, highlightbackground='DarkSlateBlue', borderwidth=0, sliderrelief=tk.FLAT, troughcolor=self.LIGHT_BG, sliderlength=15, highlightthickness=2, showvalue=0) _scale.grid(row=row_index, column=column_index) return _scale def add_label(self, row_index, column_index, master, txt): """ just hides some simple code """ _label = BinaryLabel(master=master, initialstr=txt, background=self.DARK_BG, font=('Consolas', '14', 'bold')) _label.grid(row=row_index, column=column_index, columnspan=5, sticky=tk.W) return _label def add_size_entry(self, master, txt=''): """ for width and height entries """ master.propagate(False) _entry = MergerEntry(master=master, initialstr=txt, justify='right', bg=self.LIGHT_BG) _entry.pack(pady=3) _entry.variable = '100' return _entry def _find_widthheight(self): """ runs through selected image list, \\ returns width and height in pixels """ self.bs_sizes.variable = False self.bs_sizes.strv.variable = self.bs_sizes.falsestr file_names = self.lstbox.get(0, tk.END) if not file_names: self.width_entry.variable = 'none' self.height_entry.variable = 'none' return image_list = [Image.open(e) for e in file_names] if self.bs_maxmin.variable: self.width_entry.variable = MergerScripts.find_max_width( image_list) self.height_entry.variable = MergerScripts.find_max_height( image_list) else: self.width_entry.variable = MergerScripts.find_min_width( image_list) self.height_entry.variable = MergerScripts.find_min_height( image_list) def add_wdthgt_btn(self, rowindex, columnindex, master): """ "find" button """ _frame = tk.Frame(master, width=75, height=30, background=self.DARK_BG) _frame.propagate(False) _frame.grid(row=rowindex, column=columnindex, columnspan=2) _btn = tk.Button(_frame, text='find', activebackground=self.LIGHT_BG, font=('Consolas', '14', 'bold'), command=self._find_widthheight) _btn.pack(padx=5, pady=3) return _btn def add_listbox(self, rowindex, columnindex, master): """ main listbox """ lbox = tk.Listbox(master, bg=self.LIGHT_BG, height=4) lbox.grid(row=rowindex, column=columnindex, columnspan=6, rowspan=4, sticky=tk.NSEW) return lbox def _clear_listbox_files(self): self.lstbox.delete(0, tk.END) def add_clear_listbox_btn(self, master): """ "cl" clear button """ master.propagate(False) _btn = tk.Button(master, activebackground=self.LIGHT_BG, command=self._clear_listbox_files) _btn.img = tk.PhotoImage(file=r"img/clear.png") _btn.configure(image=_btn.img) _btn.pack(padx=5, pady=3) return _btn def _call_selectfiles(self): """ called by "+" button \\ calls tkinter.dialogs askopenfilenames \\ if listbox cursor is active file names should be placed after it \\ """ _curpos = self.lstbox.curselection() filelist = askopenfilenames(filetypes=(("image files", "*.png *.jpg *.jpeg *.gif"), )) if len(_curpos): _curpos = 1 + int(_curpos[0]) for i, name in enumerate(filelist): self.lstbox.insert(i + _curpos, name) else: for name in filelist: self.lstbox.insert(tk.END, name) def add_inputbtn(self, master): """ '+' sign button """ master.propagate(False) _btn = tk.Button(master, activebackground=self.LIGHT_BG, command=self._call_selectfiles) _btn.img = tk.PhotoImage(file=r"img/include.png") _btn.configure(image=_btn.img) _btn.pack(padx=5, pady=3) return _btn def _call_excludefile(self): """ called by "-" button """ _curpos = self.lstbox.curselection() if len(_curpos): self.lstbox.delete(_curpos) def add_excludebtn(self, master): """ '-' sign button """ master.propagate(False) _btn = tk.Button(master, activebackground=self.LIGHT_BG, command=self._call_excludefile) _btn.img = tk.PhotoImage(file=r"img/exclude.png") _btn.configure(image=_btn.img) _btn.pack(padx=5, pady=3) return _btn def add_outputdir_entry(self, rowindex, columnindex, master, txt): """ creates tk.Entry that shows output path """ _entry = MergerEntry(master=master, initialstr=txt, bg=self.LIGHT_BG) _entry.grid(row=rowindex, column=columnindex, columnspan=5, sticky=tk.NSEW, padx=3, pady=4) return _entry def _call_askdir(self): """ returns *.png string for a Concatenation tab """ _path = askdirectory() if _path: if self.current_active_tab == 0: _path = ['\\' if e == '/' else e for e in _path] _path.append('\\') _path = MergerScripts.make_default_concatenation_path( ''.join(_path)) self.direntry.variable = _path elif self.current_active_tab == 1: _path = ['\\' if e == '/' else e for e in _path] _path.append('\\') _path = ''.join(_path) self.direntry.variable = _path def add_outputdir_btn(self, master): """ """ master.propagate(False) _btn = tk.Button(master, text='<<', activebackground=self.LIGHT_BG, font=('Consolas', '14', 'bold'), command=self._call_askdir) _btn.pack(padx=5, pady=3) return _btn def _call_process_files(self): """ called by "process" button \\ checks 'current_active_tab' property \\ calls subservient functions """ if self.current_active_tab == 0: self._make_concatenation() elif self.current_active_tab == 1: self._make_resizing() def _make_concatenation(self): """ depending on activated flags different combination of scripts is called """ file_names = self.lstbox.get(0, tk.END) if len(file_names) < 2: return image_list = [Image.open(e) for e in file_names] if self.bs_resize.variable: if self.bs_concat.variable: max_width = MergerScripts.find_max_width(image_list) image_list = MergerScripts.resize_all_tomax(image_list, max_width, Image, is_vertical=True) else: max_height = MergerScripts.find_max_height(image_list) image_list = MergerScripts.resize_all_tomax(image_list, max_height, Image, is_vertical=False) if self.bs_concat.variable: result_image = MergerScripts.concatenate_v(image_list, self.bg_color, Image) else: result_image = MergerScripts.concatenate_h(image_list, self.bg_color, Image) _path = self.direntry.variable _path = MergerScripts.make_default_concatenation_path(_path) self.direntry.variable = _path result_image.save(f'{self.direntry.variable}', 'PNG') def _make_resizing(self): """ converts width, height values \\ calls subservient resizing functions """ file_names = self.lstbox.get(0, tk.END) if not file_names: return try: width = int(self.width_entry.variable) except ValueError: width = None try: height = int(self.height_entry.variable) except ValueError: height = None filetype = 'PNG' if self.bs_file_type.variable else 'JPEG' if self.bs_sizes.variable == True: MergerScripts.resize_percents(file_names, self.direntry.variable, width, height, filetype) else: MergerScripts.resize_pixels(file_names, self.direntry.variable, width, height, filetype) def add_process_btn(self, rowindex, columnindex, master): """ place "process" btn on bottom line """ _frame = tk.Frame(master, width=100, height=30, background=self.DARK_BG) _frame.grid(row=rowindex, column=columnindex, columnspan=2) _frame.propagate(False) _btn = tk.Button(_frame, text='Process', activebackground=self.LIGHT_BG, font=('Consolas', '14', 'bold'), command=self._call_process_files) _btn.pack(padx=5, pady=3) return _btn
def test_find_max_height(self): expected = 256 actual = MergerScripts.find_max_height(self.images) assert expected == actual
def test_resize_all_tomax(self): expected = 256 actual = MergerScripts.resize_all_tomax(self.images, 256, Image, True)[1].width assert expected == actual
def test_concatenate_v(self): expected = 270 actual = MergerScripts.concatenate_v(self.images, 'white', Image).height assert expected == actual
def test_concatenate_h(self): expected = 288 actual = MergerScripts.concatenate_h(self.images, 'white', Image).width assert expected == actual
def test_formfilepath(self): expected = 'merger.py.png' actual = MergerScripts.make_default_concatenation_path(r'merger.py') assert expected == actual
def test_findfilename(self): expected = 'merger.py' actual = MergerScripts._find_filename(r'E:\folder1\workdir\merger.py') assert expected == actual
def test_findfolderpath(self): expected = 'E:\\folder1\workdir\\' actual = MergerScripts.find_folderpath(r'E:\folder1\workdir\merger.py') assert expected == actual