Files
CourseWork/gui/main_window.py

304 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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