DucHaiten commited on
Commit
21648ed
1 Parent(s): 284f987

Update rotate_flip.py

Browse files
Files changed (1) hide show
  1. rotate_flip.py +316 -316
rotate_flip.py CHANGED
@@ -1,316 +1,316 @@
1
- import tkinter as tk
2
- from tkinter import filedialog, ttk, messagebox
3
- from wand.image import Image as WandImage
4
- import os
5
- import threading
6
- import queue
7
-
8
- # Biến toàn cục để điều khiển việc dừng và lưu lỗi
9
- stop_processing = False
10
- error_messages = []
11
- error_window = None
12
- selected_files = []
13
- save_directory = ""
14
-
15
- def open_image_rotate_flip():
16
- global stop_processing, error_messages, error_window, selected_files, save_dir_var, status_var, num_files_var, errors_var, thread_count_var, progress, q, rotate_left_angle_var, rotate_right_angle_var
17
-
18
- # Tạo cửa sổ Tkinter
19
- root = tk.Tk()
20
- root.title("Image Rotate & Flip")
21
-
22
- # Khởi tạo các biến Tkinter
23
- save_dir_var = tk.StringVar()
24
- status_var = tk.StringVar()
25
- num_files_var = tk.StringVar()
26
- errors_var = tk.StringVar(value="Errors: 0")
27
- thread_count_var = tk.StringVar(value="4")
28
- rotate_left_angle_var = tk.StringVar(value="90")
29
- rotate_right_angle_var = tk.StringVar(value="90")
30
- progress = tk.IntVar()
31
- q = queue.Queue()
32
-
33
- def center_window(window):
34
- window.update_idletasks()
35
- width = window.winfo_width() + 120
36
- height = window.winfo_height()
37
- x = (window.winfo_screenwidth() // 2) - (width // 2)
38
- y = (window.winfo_screenheight() // 2) - (height // 2)
39
- window.geometry(f'{width}x{height}+{x}+{y}')
40
-
41
- def select_files():
42
- global selected_files
43
- filetypes = [
44
- ("All Image files", "*.jpg;*.jpeg;*.png;*.gif;*.bmp;*.tiff;*.tif;*.svg;*.webp"),
45
- ("JPEG files", "*.jpg;*.jpeg"),
46
- ("PNG files", "*.png"),
47
- ("GIF files", "*.gif"),
48
- ("BMP files", "*.bmp"),
49
- ("TIFF files", "*.tiff;*.tif"),
50
- ("SVG files", "*.svg"),
51
- ("WEBP files", "*.webp")
52
- ]
53
- filepaths = filedialog.askopenfilenames(title="Select Image Files", filetypes=filetypes)
54
- if filepaths:
55
- selected_files.clear()
56
- selected_files.extend(filepaths)
57
- num_files_var.set(f"{len(selected_files)} files selected.")
58
-
59
- def choose_directory():
60
- global save_directory
61
- directory = filedialog.askdirectory()
62
- if directory:
63
- save_directory = directory
64
- save_dir_var.set(directory)
65
-
66
- def rotate_image(input_path, angle, output_path):
67
- """Xoay ảnh sử dụng ImageMagick thông qua Wand."""
68
- try:
69
- with WandImage(filename=input_path) as img:
70
- img.rotate(angle)
71
- img.save(filename=output_path)
72
- except Exception as e:
73
- raise RuntimeError(f"Error rotating image: {e}")
74
-
75
- def flip_image(input_path, direction, output_path):
76
- """Lật ảnh sử dụng ImageMagick thông qua Wand."""
77
- try:
78
- with WandImage(filename=input_path) as img:
79
- if direction == "horizontal":
80
- img.flip()
81
- elif direction == "vertical":
82
- img.flop()
83
- img.save(filename=output_path)
84
- except Exception as e:
85
- raise RuntimeError(f"Error flipping image: {e}")
86
-
87
- def process_image(input_path, save_directory, operation, angle, q):
88
- if stop_processing:
89
- return
90
-
91
- filename = os.path.basename(input_path)
92
- try:
93
- output_path = os.path.join(save_directory, filename)
94
- if operation == "rotate_left":
95
- rotate_image(input_path, -angle, output_path)
96
- elif operation == "rotate_right":
97
- rotate_image(input_path, angle, output_path)
98
- elif operation == "flip_horizontal":
99
- flip_image(input_path, "horizontal", output_path)
100
- elif operation == "flip_vertical":
101
- flip_image(input_path, "vertical", output_path)
102
-
103
- q.put(input_path)
104
- except Exception as e:
105
- error_message = f"Error processing {filename}: {str(e)}"
106
- q.put(error_message)
107
- error_messages.append(error_message)
108
-
109
- def worker(save_directory, operation, num_threads, angle):
110
- try:
111
- progress.set(0)
112
- for i, input_path in enumerate(selected_files, 1):
113
- if stop_processing:
114
- break
115
-
116
- thread = threading.Thread(target=process_image, args=(input_path, save_directory, operation, angle, q))
117
- thread.start()
118
- thread.join()
119
-
120
- q.put(None)
121
- except Exception as e:
122
- if not stop_processing:
123
- q.put(e)
124
-
125
- def update_progress():
126
- try:
127
- completed = 0
128
- while True:
129
- item = q.get()
130
- if item is None:
131
- break
132
- if isinstance(item, str):
133
- if "Error" in item:
134
- root.after(0, errors_var.set, f"Errors: {len(error_messages)}")
135
- continue
136
- completed += 1
137
- progress.set(int((completed / len(selected_files)) * 100))
138
- if not stop_processing:
139
- root.after(0, status_var.set, f"Processed {completed} files")
140
- root.after(0, root.update_idletasks)
141
- if not stop_processing:
142
- root.after(0, progress.set(100))
143
- show_completion_message(completed)
144
- except Exception as e:
145
- if not stop_processing:
146
- root.after(0, status_var.set, f"Error: {e}")
147
-
148
- def show_completion_message(completed):
149
- message = f"Processing complete. {completed} files processed."
150
- if error_messages:
151
- message += f" {len(error_messages)} errors occurred."
152
- messagebox.showinfo("Process Complete", message)
153
-
154
- def process_files(operation):
155
- global stop_processing, error_messages
156
- stop_processing = False
157
- error_messages.clear()
158
- errors_var.set("Errors: 0")
159
- if not selected_files or not save_directory:
160
- status_var.set("Please select images and save location.")
161
- return
162
-
163
- num_threads = int(thread_count_var.get() or 4)
164
- if operation in ["rotate_left", "rotate_right"]:
165
- angle = int(rotate_left_angle_var.get() or 0 if operation == "rotate_left" else rotate_right_angle_var.get() or 0)
166
- if angle < 0 or angle > 360:
167
- messagebox.showerror("Invalid Input", "Please enter a valid angle between 0 and 360.")
168
- return
169
- else:
170
- angle = 0
171
-
172
- threading.Thread(target=worker, args=(save_directory, operation, num_threads, angle)).start()
173
- threading.Thread(target=update_progress).start()
174
-
175
- def validate_number(P):
176
- return P.isdigit() or P == ""
177
-
178
- def validate_angle_input(var):
179
- value = var.get()
180
- if value != "":
181
- try:
182
- int_value = int(value)
183
- if int_value < 0 or int_value > 360:
184
- raise ValueError
185
- except ValueError:
186
- messagebox.showerror("Invalid Input", "Please enter a valid angle between 0 and 360.")
187
- var.set("")
188
-
189
- def validate_thread_count(var):
190
- value = var.get()
191
- if value != "":
192
- try:
193
- int_value = int(value)
194
- if int_value <= 0:
195
- raise ValueError
196
- except ValueError:
197
- messagebox.showerror("Invalid Input", "Please enter a valid number of threads.")
198
- var.set("")
199
-
200
- def stop_processing_func():
201
- global stop_processing
202
- stop_processing = True
203
- status_var.set("Processing stopped.")
204
-
205
- def return_to_menu():
206
- stop_processing_func()
207
- root.destroy()
208
- import main
209
- main.open_main_menu()
210
-
211
- def on_closing():
212
- return_to_menu()
213
-
214
- def show_errors():
215
- global error_window
216
- if error_window is not None:
217
- return
218
-
219
- error_window = tk.Toplevel(root)
220
- error_window.title("Error Details")
221
- error_window.geometry("500x400")
222
-
223
- error_text = tk.Text(error_window, wrap='word')
224
- error_text.pack(expand=True, fill='both')
225
-
226
- if error_messages:
227
- for error in error_messages:
228
- error_text.insert('end', error + '\n')
229
- else:
230
- error_text.insert('end', "No errors recorded.")
231
-
232
- error_text.config(state='disabled')
233
-
234
- def on_close_error_window():
235
- global error_window
236
- error_window.destroy()
237
- error_window = None
238
-
239
- error_window.protocol("WM_DELETE_WINDOW", on_close_error_window)
240
-
241
- # Tạo các thành phần giao diện
242
- validate_command = root.register(validate_number)
243
-
244
- back_button = tk.Button(root, text="<-", font=('Helvetica', 14), command=return_to_menu)
245
- back_button.pack(anchor='nw', padx=10, pady=10)
246
-
247
- title_label = tk.Label(root, text="Image Rotate & Flip", font=('Helvetica', 16))
248
- title_label.pack(pady=10)
249
-
250
- select_files_button = tk.Button(root, text="Select Files", command=select_files)
251
- select_files_button.pack(pady=5)
252
-
253
- num_files_label = tk.Label(root, textvariable=num_files_var)
254
- num_files_label.pack(pady=5)
255
-
256
- choose_dir_button = tk.Button(root, text="Choose Save Directory", command=choose_directory)
257
- choose_dir_button.pack(pady=10)
258
-
259
- save_dir_entry = tk.Entry(root, textvariable=save_dir_var, state='readonly', justify='center')
260
- save_dir_entry.pack(pady=5, fill=tk.X)
261
-
262
- rotate_left_label = tk.Label(root, text="Enter the rotation angle (°):")
263
- rotate_left_label.pack(pady=5)
264
- rotate_left_entry = tk.Entry(root, textvariable=rotate_left_angle_var, justify='center', width=5, validate="key", validatecommand=(validate_command, '%P'))
265
- rotate_left_entry.pack(pady=5)
266
-
267
- rotate_left_button = tk.Button(root, text="Rotate Left", command=lambda: process_files("rotate_left"))
268
- rotate_left_button.pack(pady=5)
269
-
270
- rotate_right_label = tk.Label(root, text="Enter the rotation angle (°):")
271
- rotate_right_label.pack(pady=5)
272
- rotate_right_entry = tk.Entry(root, textvariable=rotate_right_angle_var, justify='center', width=5, validate="key", validatecommand=(validate_command, '%P'))
273
- rotate_right_entry.pack(pady=5)
274
-
275
- rotate_right_button = tk.Button(root, text="Rotate Right", command=lambda: process_files("rotate_right"))
276
- rotate_right_button.pack(pady=5)
277
-
278
- # Đường kẻ phân cách
279
- separator = ttk.Separator(root, orient='horizontal')
280
- separator.pack(fill='x', pady=10)
281
-
282
- flip_horizontal_button = tk.Button(root, text="Flip Horizontal", command=lambda: process_files("flip_horizontal"))
283
- flip_horizontal_button.pack(pady=5)
284
-
285
- flip_vertical_button = tk.Button(root, text="Flip Vertical", command=lambda: process_files("flip_vertical"))
286
- flip_vertical_button.pack(pady=5)
287
-
288
- thread_count_label = tk.Label(root, text="Number of Threads:")
289
- thread_count_label.pack(pady=5)
290
-
291
- thread_count_entry = tk.Entry(root, textvariable=thread_count_var, width=5, justify='center', validate="key", validatecommand=(validate_command, '%P'))
292
- thread_count_entry.pack(pady=5)
293
-
294
- stop_button = tk.Button(root, text="Stop", command=stop_processing_func)
295
- stop_button.pack(pady=5)
296
-
297
- errors_button = tk.Button(root, textvariable=errors_var, command=show_errors)
298
- errors_button.pack(pady=5)
299
-
300
- progress_bar = ttk.Progressbar(root, variable=progress, maximum=100)
301
- progress_bar.pack(pady=5, fill=tk.X)
302
-
303
- status_label = tk.Label(root, textvariable=status_var, fg="green")
304
- status_label.pack(pady=5)
305
-
306
- # Ràng buộc xác thực đầu vào cho các ô nhập
307
- rotate_left_angle_var.trace_add('write', lambda *args: validate_angle_input(rotate_left_angle_var))
308
- rotate_right_angle_var.trace_add('write', lambda *args: validate_angle_input(rotate_right_angle_var))
309
- thread_count_var.trace_add('write', lambda *args: validate_thread_count(thread_count_var))
310
-
311
- center_window(root)
312
- root.protocol("WM_DELETE_WINDOW", on_closing)
313
- root.mainloop()
314
-
315
- if __name__ == "__main__":
316
- open_image_rotate_flip()
 
1
+ import tkinter as tk
2
+ from tkinter import filedialog, ttk, messagebox
3
+ from wand.image import Image as WandImage
4
+ import os
5
+ import threading
6
+ import queue
7
+
8
+ # Biến toàn cục để điều khiển việc dừng và lưu lỗi
9
+ stop_processing = False
10
+ error_messages = []
11
+ error_window = None
12
+ selected_files = []
13
+ save_directory = ""
14
+
15
+ def open_image_rotate_flip():
16
+ global stop_processing, error_messages, error_window, selected_files, save_dir_var, status_var, num_files_var, errors_var, thread_count_var, progress, q, rotate_left_angle_var, rotate_right_angle_var
17
+
18
+ # Tạo cửa sổ Tkinter
19
+ root = tk.Tk()
20
+ root.title("Image Rotate & Flip")
21
+
22
+ # Khởi tạo các biến Tkinter
23
+ save_dir_var = tk.StringVar()
24
+ status_var = tk.StringVar()
25
+ num_files_var = tk.StringVar()
26
+ errors_var = tk.StringVar(value="Errors: 0")
27
+ thread_count_var = tk.StringVar(value="1")
28
+ rotate_left_angle_var = tk.StringVar(value="90")
29
+ rotate_right_angle_var = tk.StringVar(value="90")
30
+ progress = tk.IntVar()
31
+ q = queue.Queue()
32
+
33
+ def center_window(window):
34
+ window.update_idletasks()
35
+ width = window.winfo_width() + 120
36
+ height = window.winfo_height()
37
+ x = (window.winfo_screenwidth() // 2) - (width // 2)
38
+ y = (window.winfo_screenheight() // 2) - (height // 2)
39
+ window.geometry(f'{width}x{height}+{x}+{y}')
40
+
41
+ def select_files():
42
+ global selected_files
43
+ filetypes = [
44
+ ("All Image files", "*.jpg;*.jpeg;*.png;*.gif;*.bmp;*.tiff;*.tif;*.svg;*.webp"),
45
+ ("JPEG files", "*.jpg;*.jpeg"),
46
+ ("PNG files", "*.png"),
47
+ ("GIF files", "*.gif"),
48
+ ("BMP files", "*.bmp"),
49
+ ("TIFF files", "*.tiff;*.tif"),
50
+ ("SVG files", "*.svg"),
51
+ ("WEBP files", "*.webp")
52
+ ]
53
+ filepaths = filedialog.askopenfilenames(title="Select Image Files", filetypes=filetypes)
54
+ if filepaths:
55
+ selected_files.clear()
56
+ selected_files.extend(filepaths)
57
+ num_files_var.set(f"{len(selected_files)} files selected.")
58
+
59
+ def choose_directory():
60
+ global save_directory
61
+ directory = filedialog.askdirectory()
62
+ if directory:
63
+ save_directory = directory
64
+ save_dir_var.set(directory)
65
+
66
+ def rotate_image(input_path, angle, output_path):
67
+ """Xoay ảnh sử dụng ImageMagick thông qua Wand."""
68
+ try:
69
+ with WandImage(filename=input_path) as img:
70
+ img.rotate(angle)
71
+ img.save(filename=output_path)
72
+ except Exception as e:
73
+ raise RuntimeError(f"Error rotating image: {e}")
74
+
75
+ def flip_image(input_path, direction, output_path):
76
+ """Lật ảnh sử dụng ImageMagick thông qua Wand."""
77
+ try:
78
+ with WandImage(filename=input_path) as img:
79
+ if direction == "horizontal":
80
+ img.flip()
81
+ elif direction == "vertical":
82
+ img.flop()
83
+ img.save(filename=output_path)
84
+ except Exception as e:
85
+ raise RuntimeError(f"Error flipping image: {e}")
86
+
87
+ def process_image(input_path, save_directory, operation, angle, q):
88
+ if stop_processing:
89
+ return
90
+
91
+ filename = os.path.basename(input_path)
92
+ try:
93
+ output_path = os.path.join(save_directory, filename)
94
+ if operation == "rotate_left":
95
+ rotate_image(input_path, -angle, output_path)
96
+ elif operation == "rotate_right":
97
+ rotate_image(input_path, angle, output_path)
98
+ elif operation == "flip_horizontal":
99
+ flip_image(input_path, "horizontal", output_path)
100
+ elif operation == "flip_vertical":
101
+ flip_image(input_path, "vertical", output_path)
102
+
103
+ q.put(input_path)
104
+ except Exception as e:
105
+ error_message = f"Error processing {filename}: {str(e)}"
106
+ q.put(error_message)
107
+ error_messages.append(error_message)
108
+
109
+ def worker(save_directory, operation, num_threads, angle):
110
+ try:
111
+ progress.set(0)
112
+ for i, input_path in enumerate(selected_files, 1):
113
+ if stop_processing:
114
+ break
115
+
116
+ thread = threading.Thread(target=process_image, args=(input_path, save_directory, operation, angle, q))
117
+ thread.start()
118
+ thread.join()
119
+
120
+ q.put(None)
121
+ except Exception as e:
122
+ if not stop_processing:
123
+ q.put(e)
124
+
125
+ def update_progress():
126
+ try:
127
+ completed = 0
128
+ while True:
129
+ item = q.get()
130
+ if item is None:
131
+ break
132
+ if isinstance(item, str):
133
+ if "Error" in item:
134
+ root.after(0, errors_var.set, f"Errors: {len(error_messages)}")
135
+ continue
136
+ completed += 1
137
+ progress.set(int((completed / len(selected_files)) * 100))
138
+ if not stop_processing:
139
+ root.after(0, status_var.set, f"Processed {completed} files")
140
+ root.after(0, root.update_idletasks)
141
+ if not stop_processing:
142
+ root.after(0, progress.set(100))
143
+ show_completion_message(completed)
144
+ except Exception as e:
145
+ if not stop_processing:
146
+ root.after(0, status_var.set, f"Error: {e}")
147
+
148
+ def show_completion_message(completed):
149
+ message = f"Processing complete. {completed} files processed."
150
+ if error_messages:
151
+ message += f" {len(error_messages)} errors occurred."
152
+ messagebox.showinfo("Process Complete", message)
153
+
154
+ def process_files(operation):
155
+ global stop_processing, error_messages
156
+ stop_processing = False
157
+ error_messages.clear()
158
+ errors_var.set("Errors: 0")
159
+ if not selected_files or not save_directory:
160
+ status_var.set("Please select images and save location.")
161
+ return
162
+
163
+ num_threads = int(thread_count_var.get() or 4)
164
+ if operation in ["rotate_left", "rotate_right"]:
165
+ angle = int(rotate_left_angle_var.get() or 0 if operation == "rotate_left" else rotate_right_angle_var.get() or 0)
166
+ if angle < 0 or angle > 360:
167
+ messagebox.showerror("Invalid Input", "Please enter a valid angle between 0 and 360.")
168
+ return
169
+ else:
170
+ angle = 0
171
+
172
+ threading.Thread(target=worker, args=(save_directory, operation, num_threads, angle)).start()
173
+ threading.Thread(target=update_progress).start()
174
+
175
+ def validate_number(P):
176
+ return P.isdigit() or P == ""
177
+
178
+ def validate_angle_input(var):
179
+ value = var.get()
180
+ if value != "":
181
+ try:
182
+ int_value = int(value)
183
+ if int_value < 0 or int_value > 360:
184
+ raise ValueError
185
+ except ValueError:
186
+ messagebox.showerror("Invalid Input", "Please enter a valid angle between 0 and 360.")
187
+ var.set("")
188
+
189
+ def validate_thread_count(var):
190
+ value = var.get()
191
+ if value != "":
192
+ try:
193
+ int_value = int(value)
194
+ if int_value <= 0:
195
+ raise ValueError
196
+ except ValueError:
197
+ messagebox.showerror("Invalid Input", "Please enter a valid number of threads.")
198
+ var.set("")
199
+
200
+ def stop_processing_func():
201
+ global stop_processing
202
+ stop_processing = True
203
+ status_var.set("Processing stopped.")
204
+
205
+ def return_to_menu():
206
+ stop_processing_func()
207
+ root.destroy()
208
+ import main
209
+ main.open_main_menu()
210
+
211
+ def on_closing():
212
+ return_to_menu()
213
+
214
+ def show_errors():
215
+ global error_window
216
+ if error_window is not None:
217
+ return
218
+
219
+ error_window = tk.Toplevel(root)
220
+ error_window.title("Error Details")
221
+ error_window.geometry("500x400")
222
+
223
+ error_text = tk.Text(error_window, wrap='word')
224
+ error_text.pack(expand=True, fill='both')
225
+
226
+ if error_messages:
227
+ for error in error_messages:
228
+ error_text.insert('end', error + '\n')
229
+ else:
230
+ error_text.insert('end', "No errors recorded.")
231
+
232
+ error_text.config(state='disabled')
233
+
234
+ def on_close_error_window():
235
+ global error_window
236
+ error_window.destroy()
237
+ error_window = None
238
+
239
+ error_window.protocol("WM_DELETE_WINDOW", on_close_error_window)
240
+
241
+ # Tạo các thành phần giao diện
242
+ validate_command = root.register(validate_number)
243
+
244
+ back_button = tk.Button(root, text="<-", font=('Helvetica', 14), command=return_to_menu)
245
+ back_button.pack(anchor='nw', padx=10, pady=10)
246
+
247
+ title_label = tk.Label(root, text="Image Rotate & Flip", font=('Helvetica', 16))
248
+ title_label.pack(pady=10)
249
+
250
+ select_files_button = tk.Button(root, text="Select Files", command=select_files)
251
+ select_files_button.pack(pady=5)
252
+
253
+ num_files_label = tk.Label(root, textvariable=num_files_var)
254
+ num_files_label.pack(pady=5)
255
+
256
+ choose_dir_button = tk.Button(root, text="Choose Save Directory", command=choose_directory)
257
+ choose_dir_button.pack(pady=10)
258
+
259
+ save_dir_entry = tk.Entry(root, textvariable=save_dir_var, state='readonly', justify='center')
260
+ save_dir_entry.pack(pady=5, fill=tk.X)
261
+
262
+ rotate_left_label = tk.Label(root, text="Enter the rotation angle (°):")
263
+ rotate_left_label.pack(pady=5)
264
+ rotate_left_entry = tk.Entry(root, textvariable=rotate_left_angle_var, justify='center', width=5, validate="key", validatecommand=(validate_command, '%P'))
265
+ rotate_left_entry.pack(pady=5)
266
+
267
+ rotate_left_button = tk.Button(root, text="Rotate Left", command=lambda: process_files("rotate_left"))
268
+ rotate_left_button.pack(pady=5)
269
+
270
+ rotate_right_label = tk.Label(root, text="Enter the rotation angle (°):")
271
+ rotate_right_label.pack(pady=5)
272
+ rotate_right_entry = tk.Entry(root, textvariable=rotate_right_angle_var, justify='center', width=5, validate="key", validatecommand=(validate_command, '%P'))
273
+ rotate_right_entry.pack(pady=5)
274
+
275
+ rotate_right_button = tk.Button(root, text="Rotate Right", command=lambda: process_files("rotate_right"))
276
+ rotate_right_button.pack(pady=5)
277
+
278
+ # Đường kẻ phân cách
279
+ separator = ttk.Separator(root, orient='horizontal')
280
+ separator.pack(fill='x', pady=10)
281
+
282
+ flip_horizontal_button = tk.Button(root, text="Flip Horizontal", command=lambda: process_files("flip_horizontal"))
283
+ flip_horizontal_button.pack(pady=5)
284
+
285
+ flip_vertical_button = tk.Button(root, text="Flip Vertical", command=lambda: process_files("flip_vertical"))
286
+ flip_vertical_button.pack(pady=5)
287
+
288
+ thread_count_label = tk.Label(root, text="Number of Threads:")
289
+ thread_count_label.pack(pady=5)
290
+
291
+ thread_count_entry = tk.Entry(root, textvariable=thread_count_var, width=5, justify='center', validate="key", validatecommand=(validate_command, '%P'))
292
+ thread_count_entry.pack(pady=5)
293
+
294
+ stop_button = tk.Button(root, text="Stop", command=stop_processing_func)
295
+ stop_button.pack(pady=5)
296
+
297
+ errors_button = tk.Button(root, textvariable=errors_var, command=show_errors)
298
+ errors_button.pack(pady=5)
299
+
300
+ progress_bar = ttk.Progressbar(root, variable=progress, maximum=100)
301
+ progress_bar.pack(pady=5, fill=tk.X)
302
+
303
+ status_label = tk.Label(root, textvariable=status_var, fg="green")
304
+ status_label.pack(pady=5)
305
+
306
+ # Ràng buộc xác thực đầu vào cho các ô nhập
307
+ rotate_left_angle_var.trace_add('write', lambda *args: validate_angle_input(rotate_left_angle_var))
308
+ rotate_right_angle_var.trace_add('write', lambda *args: validate_angle_input(rotate_right_angle_var))
309
+ thread_count_var.trace_add('write', lambda *args: validate_thread_count(thread_count_var))
310
+
311
+ center_window(root)
312
+ root.protocol("WM_DELETE_WINDOW", on_closing)
313
+ root.mainloop()
314
+
315
+ if __name__ == "__main__":
316
+ open_image_rotate_flip()