""" GUI интерфейс для стеганографического приложения """ import tkinter as tk from tkinter import ttk, filedialog, messagebox from PIL import Image, ImageTk import threading import os import sys # Добавляем родительскую директорию для импорта модулей core sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from core.lsb import encode_lsb, decode_lsb from core.dct import encode_dct, decode_dct from core.utils import calculate_capacity class StegoGUI: """Главное окно приложения""" def __init__(self, root): self.root = root self.root.title("Stego App - Стеганография") self.root.geometry("750x650") self.root.resizable(True, True) # Переменные self.image_path = tk.StringVar() self.output_path = tk.StringVar() self.method = tk.StringVar(value="lsb") self.mode = tk.StringVar(value="encode") self.setup_ui() def setup_ui(self): """Создание интерфейса""" # Вкладки notebook = ttk.Notebook(self.root) notebook.pack(fill='both', expand=True, padx=10, pady=10) # Вкладка "Кодирование" self.encode_frame = ttk.Frame(notebook) notebook.add(self.encode_frame, text="Скрыть сообщение") self.setup_encode_tab() # Вкладка "Декодирование" self.decode_frame = ttk.Frame(notebook) notebook.add(self.decode_frame, text="Извлечь сообщение") self.setup_decode_tab() # Вкладка "О программе" about_frame = ttk.Frame(notebook) notebook.add(about_frame, text="О программе") self.setup_about_tab(about_frame) def setup_encode_tab(self): """Вкладка кодирования""" row = 0 # Метод ttk.Label(self.encode_frame, text="Метод:").grid(row=row, column=0, sticky='w', padx=5, pady=5) ttk.Radiobutton(self.encode_frame, text="LSB", variable=self.method, value="lsb").grid(row=row, column=1, padx=5) ttk.Radiobutton(self.encode_frame, text="DCT", variable=self.method, value="dct").grid(row=row, column=2, padx=5) row += 1 # Исходное изображение ttk.Label(self.encode_frame, text="Исходное изображение:").grid(row=row, column=0, sticky='w', padx=5, pady=5) ttk.Entry(self.encode_frame, textvariable=self.image_path, width=50).grid(row=row, column=1, columnspan=2, padx=5) ttk.Button(self.encode_frame, text="Обзор...", command=self.select_image).grid(row=row, column=3, padx=5) row += 1 # Выходное изображение ttk.Label(self.encode_frame, text="Сохранить как:").grid(row=row, column=0, sticky='w', padx=5, pady=5) ttk.Entry(self.encode_frame, textvariable=self.output_path, width=50).grid(row=row, column=1, columnspan=2, padx=5) ttk.Button(self.encode_frame, text="Обзор...", command=self.select_output).grid(row=row, column=3, padx=5) row += 1 # Сообщение ttk.Label(self.encode_frame, text="Сообщение:").grid(row=row, column=0, sticky='nw', padx=5, pady=5) self.message_text = tk.Text(self.encode_frame, height=10, width=60) self.message_text.grid(row=row, column=1, columnspan=3, padx=5, pady=5) row += 1 # Вместимость self.capacity_label = ttk.Label(self.encode_frame, text="", foreground="gray") self.capacity_label.grid(row=row, column=1, columnspan=2, sticky='w', padx=5) row += 1 # Кнопка self.encode_btn = ttk.Button(self.encode_frame, text="Скрыть сообщение", command=self.do_encode) self.encode_btn.grid(row=row, column=1, columnspan=2, pady=10) row += 1 # Статус self.status_label = ttk.Label(self.encode_frame, text="", foreground="blue") self.status_label.grid(row=row, column=0, columnspan=4, pady=5) # Прогресс self.progress = ttk.Progressbar(self.encode_frame, mode='indeterminate') # Привязка события для обновления вместимости self.image_path.trace('w', lambda *args: self.update_capacity()) def setup_decode_tab(self): """Вкладка декодирования""" row = 0 # Метод ttk.Label(self.decode_frame, text="Метод:").grid(row=row, column=0, sticky='w', padx=5, pady=5) ttk.Radiobutton(self.decode_frame, text="LSB", variable=self.method, value="lsb").grid(row=row, column=1, padx=5) ttk.Radiobutton(self.decode_frame, text="DCT", variable=self.method, value="dct").grid(row=row, column=2, padx=5) row += 1 # Изображение ttk.Label(self.decode_frame, text="Изображение со скрытым сообщением:").grid(row=row, column=0, sticky='w', padx=5, pady=5) ttk.Entry(self.decode_frame, textvariable=self.image_path, width=50).grid(row=row, column=1, columnspan=2, padx=5) ttk.Button(self.decode_frame, text="Обзор...", command=self.select_image).grid(row=row, column=3, padx=5) row += 1 # Кнопка self.decode_btn = ttk.Button(self.decode_frame, text="Извлечь сообщение", command=self.do_decode) self.decode_btn.grid(row=row, column=1, columnspan=2, pady=10) row += 1 # Результат ttk.Label(self.decode_frame, text="Извлеченное сообщение:").grid(row=row, column=0, sticky='nw', padx=5, pady=5) self.result_text = tk.Text(self.decode_frame, height=10, width=60) self.result_text.grid(row=row, column=1, columnspan=3, padx=5, pady=5) row += 1 # Кнопка сохранения self.save_btn = ttk.Button(self.decode_frame, text="Сохранить в файл", command=self.save_result, state='disabled') self.save_btn.grid(row=row, column=1, columnspan=2, pady=5) row += 1 # Статус self.decode_status = ttk.Label(self.decode_frame, text="", foreground="blue") self.decode_status.grid(row=row, column=0, columnspan=4, pady=5) self.decoded_message = "" def setup_about_tab(self, frame): """Вкладка 'О программе'""" ttk.Label(frame, text="Stego App", font=("Arial", 16)).pack(pady=10) ttk.Label(frame, text="Стеганографическое приложение").pack() ttk.Label(frame, text="").pack() ttk.Label(frame, text="Методы:").pack() ttk.Label(frame, text=" • LSB (Least Significant Bit) - сокрытие в последних битах пикселей").pack() ttk.Label(frame, text=" • DCT (Discrete Cosine Transform) - сокрытие в частотной области").pack() ttk.Label(frame, text="").pack() ttk.Label(frame, text="Поддерживаемые форматы: PNG, BMP, JPG").pack() ttk.Label(frame, text="").pack() ttk.Label(frame, text=" Курсовая работа по стеганографии").pack() ttk.Label(frame, text="Петров Игорь ИУ10-24").pack() def select_image(self): """Выбор изображения""" filename = filedialog.askopenfilename( title="Выберите изображение", filetypes=[("Изображения", "*.png *.bmp *.jpg *.jpeg"), ("Все файлы", "*.*")] ) if filename: self.image_path.set(filename) def select_output(self): """Выбор выходного файла""" filename = filedialog.asksaveasfilename( title="Сохранить как", defaultextension=".png", filetypes=[("PNG", "*.png"), ("BMP", "*.bmp"), ("Все файлы", "*.*")] ) if filename: self.output_path.set(filename) def update_capacity(self): """Обновление информации о вместимости""" path = self.image_path.get() if path and os.path.exists(path): try: capacity = calculate_capacity(path) self.capacity_label.config(text=f"Максимальная вместимость: {capacity} байт (~{capacity/1024:.1f} КБ)") except Exception as e: self.capacity_label.config(text=f"Ошибка: {e}") else: self.capacity_label.config(text="") def do_encode(self): """Выполнение кодирования""" image = self.image_path.get() output = self.output_path.get() message = self.message_text.get("1.0", tk.END).strip() method = self.method.get() if not image: messagebox.showerror("Ошибка", "Выберите исходное изображение") return if not output: messagebox.showerror("Ошибка", "Укажите путь для сохранения результата") return if not message: messagebox.showerror("Ошибка", "Введите сообщение для сокрытия") return # Запускаем в отдельном потоке self.encode_btn.config(state='disabled') self.progress.grid(row=5, column=1, columnspan=2, sticky='we', padx=5, pady=5) self.progress.start() self.status_label.config(text="Кодирование...") thread = threading.Thread(target=self._do_encode, args=(image, output, message, method)) thread.daemon = True thread.start() def _do_encode(self, image, output, message, method): """Фоновое кодирование""" try: if method == 'lsb': success = encode_lsb(image, message, output) else: success = encode_dct(image, message, output) self.root.after(0, self._encode_done, success) except Exception as e: self.root.after(0, self._encode_done, False, str(e)) def _encode_done(self, success, error=None): """Завершение кодирования""" self.progress.stop() self.progress.grid_forget() self.encode_btn.config(state='normal') if success: self.status_label.config(text="✅ Кодирование успешно завершено!", foreground="green") messagebox.showinfo("Успех", "Сообщение успешно скрыто в изображении!") else: self.status_label.config(text=f"❌ Ошибка: {error or 'Кодирование не удалось'}", foreground="red") messagebox.showerror("Ошибка", error or "Не удалось скрыть сообщение") def do_decode(self): """Выполнение декодирования""" image = self.image_path.get() method = self.method.get() if not image: messagebox.showerror("Ошибка", "Выберите изображение") return self.decode_btn.config(state='disabled') self.decode_status.config(text="Декодирование...") thread = threading.Thread(target=self._do_decode, args=(image, method)) thread.daemon = True thread.start() def _do_decode(self, image, method): """Фоновое декодирование""" try: if method == 'lsb': message = decode_lsb(image) else: message = decode_dct(image) self.root.after(0, self._decode_done, message) except Exception as e: self.root.after(0, self._decode_done, None, str(e)) def _decode_done(self, message, error=None): """Завершение декодирования""" self.decode_btn.config(state='normal') if message and not error: self.decoded_message = message self.result_text.delete("1.0", tk.END) self.result_text.insert("1.0", message) self.save_btn.config(state='normal') self.decode_status.config(text="✅ Декодирование успешно завершено!", foreground="green") else: self.decode_status.config(text=f"❌ Ошибка: {error or 'Не удалось извлечь сообщение'}", foreground="red") self.save_btn.config(state='disabled') def save_result(self): """Сохранение извлеченного сообщения в файл""" filename = filedialog.asksaveasfilename( title="Сохранить сообщение", defaultextension=".txt", filetypes=[("Текстовые файлы", "*.txt"), ("Все файлы", "*.*")] ) if filename and self.decoded_message: with open(filename, 'w', encoding='utf-8') as f: f.write(self.decoded_message) messagebox.showinfo("Успех", f"Сообщение сохранено в {filename}") def main(): """Запуск GUI""" root = tk.Tk() app = StegoGUI(root) root.mainloop() if __name__ == '__main__': main()