графический интерфейс сделан

This commit is contained in:
Likaon
2026-05-10 22:38:58 +03:00
parent 30eecce68a
commit 1f04fb3c2c
2 changed files with 315 additions and 0 deletions

View File

@@ -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()

11
main.py
View File

@@ -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()