From 1f04fb3c2ca342ea9545eed4b997e5057ac37ca5 Mon Sep 17 00:00:00 2001 From: Likaon Date: Sun, 10 May 2026 22:38:58 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D1=87=D0=B5?= =?UTF-8?q?=D1=81=D0=BA=D0=B8=D0=B9=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84?= =?UTF-8?q?=D0=B5=D0=B9=D1=81=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gui/main_window.py | 304 +++++++++++++++++++++++++++++++++++++++++++++ main.py | 11 ++ 2 files changed, 315 insertions(+) diff --git a/gui/main_window.py b/gui/main_window.py index e69de29..1c86e2f 100644 --- a/gui/main_window.py +++ b/gui/main_window.py @@ -0,0 +1,304 @@ +""" +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() \ No newline at end of file diff --git a/main.py b/main.py index 3e83d12..ad57efe 100644 --- a/main.py +++ b/main.py @@ -204,6 +204,17 @@ def main() -> None: parser.print_help() sys.exit(1) +def gui_main(): + """Запуск GUI""" + from gui.main_window import main as gui_start + gui_start() + + +if __name__ == '__main__': + if len(sys.argv) > 1 and sys.argv[1] == '--gui': + gui_main() + else: + main() if __name__ == '__main__': main() \ No newline at end of file