Project

General

Profile

Files » gui.py

Zhi Jie YEW, 11/06/2025 02:43 PM

 
1
# gui.py
2

    
3
import tkinter as tk
4
from tkinter import ttk, filedialog, messagebox
5
import threading
6
import json
7
import os
8

    
9
# Import the logic classes
10
from main_alpha_blender import MainAlphaBlender
11
from video_processor import VideoProcessor
12

    
13
class BlenderGUI:
14
    """A Tkinter GUI with tabs for image and video edge blending."""
15
    def __init__(self, master):
16
        self.master = master
17
        master.title("Image and Video Edge Blender")
18
        master.geometry("600x450") # Increased height for new buttons
19

    
20
        # --- Create a Tabbed Interface ---
21
        self.notebook = ttk.Notebook(master)
22
        self.notebook.pack(pady=10, padx=10, fill="both", expand=True)
23

    
24
        self.image_tab = ttk.Frame(self.notebook, padding="10")
25
        self.video_tab = ttk.Frame(self.notebook, padding="10")
26

    
27
        self.notebook.add(self.image_tab, text="Image Blender")
28
        self.notebook.add(self.video_tab, text="Video Processor")
29

    
30
        # --- Populate each tab ---
31
        self.create_image_widgets()
32
        self.create_video_widgets()
33

    
34
        # --- NEW: Add a frame at the bottom for config management ---
35
        self.config_frame = ttk.Frame(master, padding=(10, 0, 10, 10))
36
        self.config_frame.pack(fill=tk.X, side=tk.BOTTOM)
37
        self.create_config_widgets()
38

    
39
        # --- NEW: Load default config on startup ---
40
        # It will silently fail if config.json doesn't exist, using hardcoded defaults.
41
        self.load_config(filepath="config.json", silent=True)
42

    
43
    def create_image_widgets(self):
44
        """Creates all widgets for the Image Blender tab."""
45
        self.image_blender = MainAlphaBlender()
46
        
47
        ttk.Label(self.image_tab, text="Input Image Directory:").grid(row=0, column=0, sticky=tk.W, pady=2)
48
        self.img_input_path_var = tk.StringVar(value=self.image_blender.image_path)
49
        ttk.Entry(self.image_tab, textvariable=self.img_input_path_var, width=50).grid(row=0, column=1, sticky=tk.EW, padx=5)
50
        ttk.Button(self.image_tab, text="Browse...", command=self.select_img_input_dir).grid(row=0, column=2)
51

    
52
        ttk.Label(self.image_tab, text="Output Directory:").grid(row=1, column=0, sticky=tk.W, pady=2)
53
        self.img_output_path_var = tk.StringVar(value=self.image_blender.output_dir)
54
        ttk.Entry(self.image_tab, textvariable=self.img_output_path_var, width=50).grid(row=1, column=1, sticky=tk.EW, padx=5)
55
        ttk.Button(self.image_tab, text="Browse...", command=self.select_img_output_dir).grid(row=1, column=2)
56

    
57
        ttk.Label(self.image_tab, text="Blend Width (pixels):").grid(row=2, column=0, sticky=tk.W, pady=5)
58
        self.img_blend_width_var = tk.IntVar(value=self.image_blender.blend_width)
59
        ttk.Entry(self.image_tab, textvariable=self.img_blend_width_var, width=10).grid(row=2, column=1, sticky=tk.W, padx=5)
60

    
61
        ttk.Label(self.image_tab, text="Gamma Value:").grid(row=3, column=0, sticky=tk.W, pady=2)
62
        self.img_gamma_var = tk.DoubleVar(value=self.image_blender.gamma_value)
63
        ttk.Entry(self.image_tab, textvariable=self.img_gamma_var, width=10).grid(row=3, column=1, sticky=tk.W, padx=5)
64

    
65
        ttk.Label(self.image_tab, text="Blend Method:").grid(row=4, column=0, sticky=tk.W, pady=2)
66
        self.img_method_var = tk.StringVar(value=self.image_blender.method)
67
        methods = ['linear', 'cosine', 'quadratic', 'sqrt', 'log', 'sigmoid']
68
        ttk.Combobox(self.image_tab, textvariable=self.img_method_var, values=methods, state="readonly").grid(row=4, column=1, sticky=tk.W, padx=5)
69

    
70
        self.img_preview_var = tk.BooleanVar(value=self.image_blender.preview)
71
        ttk.Checkbutton(self.image_tab, text="Show Preview After Processing", variable=self.img_preview_var).grid(row=5, column=1, sticky=tk.W, pady=10, padx=5)
72

    
73
        ttk.Button(self.image_tab, text="Run Blending Process", command=self.run_image_blending).grid(row=6, column=1, pady=20, sticky=tk.W)
74

    
75
        self.img_status_var = tk.StringVar(value="Ready.")
76
        ttk.Label(self.image_tab, textvariable=self.img_status_var, font=("Helvetica", 10, "italic")).grid(row=7, column=0, columnspan=3, sticky=tk.W, pady=5)
77

    
78
        self.image_tab.columnconfigure(1, weight=1)
79

    
80
    def create_video_widgets(self):
81
        """Creates all widgets for the Video Processor tab."""
82
        self.video_processor = VideoProcessor()
83

    
84
        ttk.Label(self.video_tab, text="Input Video File:").grid(row=0, column=0, sticky=tk.W, pady=2)
85
        self.vid_input_path_var = tk.StringVar()
86
        ttk.Entry(self.video_tab, textvariable=self.vid_input_path_var, width=50).grid(row=0, column=1, sticky=tk.EW, padx=5)
87
        ttk.Button(self.video_tab, text="Browse...", command=self.select_vid_input_file).grid(row=0, column=2)
88

    
89
        ttk.Label(self.video_tab, text="Output Directory:").grid(row=1, column=0, sticky=tk.W, pady=2)
90
        self.vid_output_path_var = tk.StringVar(value=self.video_processor.output_dir)
91
        ttk.Entry(self.video_tab, textvariable=self.vid_output_path_var, width=50).grid(row=1, column=1, sticky=tk.EW, padx=5)
92
        ttk.Button(self.video_tab, text="Browse...", command=self.select_vid_output_dir).grid(row=1, column=2)
93

    
94
        ttk.Label(self.video_tab, text="Blend Width (pixels):").grid(row=2, column=0, sticky=tk.W, pady=5)
95
        self.vid_blend_width_var = tk.IntVar(value=self.video_processor.blend_width)
96
        ttk.Entry(self.video_tab, textvariable=self.vid_blend_width_var, width=10).grid(row=2, column=1, sticky=tk.W, padx=5)
97

    
98
        ttk.Label(self.video_tab, text="Blend Method:").grid(row=3, column=0, sticky=tk.W, pady=2)
99
        self.vid_method_var = tk.StringVar(value=self.video_processor.blend_method)
100
        methods = ['linear', 'cosine']
101
        ttk.Combobox(self.video_tab, textvariable=self.vid_method_var, values=methods, state="readonly").grid(row=3, column=1, sticky=tk.W, padx=5)
102

    
103
        self.run_video_button = ttk.Button(self.video_tab, text="Process Video", command=self.run_video_processing_thread)
104
        self.run_video_button.grid(row=4, column=1, pady=20, sticky=tk.W)
105

    
106
        self.vid_status_var = tk.StringVar(value="Ready.")
107
        ttk.Label(self.video_tab, textvariable=self.vid_status_var).grid(row=5, column=0, columnspan=3, sticky=tk.W, pady=5)
108
        
109
        self.video_tab.columnconfigure(1, weight=1)
110

    
111
    def create_config_widgets(self):
112
        """Creates the Load and Save configuration buttons."""
113
        ttk.Button(self.config_frame, text="Load Config", command=self.load_config).pack(side=tk.LEFT, padx=5)
114
        ttk.Button(self.config_frame, text="Save Config", command=self.save_config).pack(side=tk.LEFT, padx=5)
115

    
116
    def load_config(self, filepath=None, silent=False):
117
        """Loads settings from a JSON file and updates the GUI."""
118
        if filepath is None:
119
            filepath = filedialog.askopenfilename(
120
                title="Open Configuration File",
121
                filetypes=[("JSON files", "*.json"), ("All files", "*.*")]
122
            )
123
        
124
        if not filepath or not os.path.exists(filepath):
125
            if not silent:
126
                messagebox.showwarning("Load Config", "No configuration file selected or file not found.")
127
            return
128

    
129
        try:
130
            with open(filepath, 'r') as f:
131
                data = json.load(f)
132

    
133
            # Update Image Tab variables
134
            self.img_input_path_var.set(data.get("image_path", "OriginalImages"))
135
            self.img_output_path_var.set(data.get("output_dir", "Results"))
136
            self.img_blend_width_var.set(data.get("blend_width", 200))
137
            self.img_gamma_var.set(data.get("gamma_value", 1.4))
138
            self.img_method_var.set(data.get("blend_method", "cosine"))
139
            self.img_preview_var.set(data.get("preview", True))
140

    
141
            # Update Video Tab variables
142
            self.vid_input_path_var.set(data.get("video_input_path", ""))
143
            self.vid_output_path_var.set(data.get("video_output_dir", "VideoResults"))
144
            self.vid_blend_width_var.set(data.get("video_blend_width", 100))
145
            self.vid_method_var.set(data.get("video_blend_method", "linear"))
146
            
147
            if not silent:
148
                messagebox.showinfo("Load Config", f"Configuration loaded successfully from {os.path.basename(filepath)}.")
149

    
150
        except Exception as e:
151
            if not silent:
152
                messagebox.showerror("Load Config Error", f"Failed to load or parse the configuration file.\n\nError: {e}")
153

    
154
    def save_config(self):
155
        """Saves the current GUI settings to a JSON file."""
156
        filepath = filedialog.asksaveasfilename(
157
            title="Save Configuration File",
158
            defaultextension=".json",
159
            initialfile="config.json",
160
            filetypes=[("JSON files", "*.json"), ("All files", "*.*")]
161
        )
162

    
163
        if not filepath:
164
            return
165

    
166
        try:
167
            config_data = {
168
                # Image Tab settings
169
                "image_path": self.img_input_path_var.get(),
170
                "output_dir": self.img_output_path_var.get(),
171
                "blend_width": self.img_blend_width_var.get(),
172
                "gamma_value": self.img_gamma_var.get(),
173
                "blend_method": self.img_method_var.get(),
174
                "preview": self.img_preview_var.get(),
175
                
176
                # Video Tab settings
177
                "video_input_path": self.vid_input_path_var.get(),
178
                "video_output_dir": self.vid_output_path_var.get(),
179
                "video_blend_width": self.vid_blend_width_var.get(),
180
                "video_blend_method": self.vid_method_var.get()
181
            }
182

    
183
            with open(filepath, 'w') as f:
184
                json.dump(config_data, f, indent=4)
185
            
186
            messagebox.showinfo("Save Config", f"Configuration saved successfully to {os.path.basename(filepath)}.")
187

    
188
        except Exception as e:
189
            messagebox.showerror("Save Config Error", f"Failed to save the configuration file.\n\nError: {e}")
190

    
191
    # --- Callbacks for Image Tab ---
192
    def select_img_input_dir(self):
193
        path = filedialog.askdirectory(title="Select Input Image Directory")
194
        if path: self.img_input_path_var.set(path)
195

    
196
    def select_img_output_dir(self):
197
        path = filedialog.askdirectory(title="Select Output Directory")
198
        if path: self.img_output_path_var.set(path)
199

    
200
    def run_image_blending(self):
201
        self.image_blender.image_path = self.img_input_path_var.get()
202
        self.image_blender.output_dir = self.img_output_path_var.get()
203
        self.image_blender.blend_width = self.img_blend_width_var.get()
204
        self.image_blender.gamma_value = self.img_gamma_var.get()
205
        self.image_blender.method = self.img_method_var.get()
206
        self.image_blender.preview = self.img_preview_var.get()
207
        self.image_blender.update_paths()
208
        
209
        success, message = self.image_blender.run()
210
        if success:
211
            self.img_status_var.set(f"Success! {message}")
212
            messagebox.showinfo("Success", message)
213
        else:
214
            self.img_status_var.set(f"Error: {message}")
215
            messagebox.showerror("Error", message)
216

    
217
    # --- Callbacks for Video Tab ---
218
    def select_vid_input_file(self):
219
        path = filedialog.askopenfilename(title="Select Input Video File", filetypes=[("MP4 files", "*.mp4"), ("All files", "*.*")])
220
        if path: self.vid_input_path_var.set(path)
221

    
222
    def select_vid_output_dir(self):
223
        path = filedialog.askdirectory(title="Select Output Directory")
224
        if path: self.vid_output_path_var.set(path)
225

    
226
    def update_video_status(self, message):
227
        """Thread-safe method to update the GUI status label."""
228
        self.vid_status_var.set(message)
229

    
230
    def run_video_processing_thread(self):
231
        """Starts the video processing in a new thread to avoid freezing the GUI."""
232
        self.run_video_button.config(state="disabled")
233
        thread = threading.Thread(target=self.run_video_processing)
234
        thread.daemon = True
235
        thread.start()
236

    
237
    def run_video_processing(self):
238
        """The actual processing logic, run in the background thread."""
239
        try:
240
            self.video_processor.input_video_path = self.vid_input_path_var.get()
241
            self.video_processor.output_dir = self.vid_output_path_var.get()
242
            self.video_processor.blend_width = self.vid_blend_width_var.get()
243
            self.video_processor.blend_method = self.vid_method_var.get()
244

    
245
            success, message = self.video_processor.run(status_callback=self.update_video_status)
246

    
247
            if success:
248
                messagebox.showinfo("Success", message)
249
            else:
250
                messagebox.showerror("Error", message)
251

    
252
        except Exception as e:
253
            messagebox.showerror("Critical Error", f"An unexpected error occurred: {e}")
254
        finally:
255
            self.run_video_button.config(state="normal")
(3-3/8)