From f053e2beccf35173db0c59947cc7ae26456d114e Mon Sep 17 00:00:00 2001 From: Likaon Date: Sat, 23 May 2026 21:33:25 +0300 Subject: [PATCH] =?UTF-8?q?=D0=92=D0=BD=D1=91=D1=81=20=D0=B8=D0=B7=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B2=20=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D1=84=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D0=B9=20=D0=B8?= =?UTF-8?q?=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9=D1=81=20=D1=82=D0=B5?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D1=8C=20=D0=B5=D1=81=D1=82=D1=8C=20=D0=B2?= =?UTF-8?q?=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20?= =?UTF-8?q?=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=20DCT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/dct.py | 65 ++++++----- gui/main_window.py | 277 ++++++++++++++++++++++++++++++++------------- 2 files changed, 238 insertions(+), 104 deletions(-) diff --git a/core/dct.py b/core/dct.py index edd73d8..1412165 100644 --- a/core/dct.py +++ b/core/dct.py @@ -1,6 +1,7 @@ """ Модуль DCT стеганографии с квантованием (JPEG-style) -Оптимизирован для длинных сообщений +Поддержка цветных изображений (RGB) +Оптимизирован для GUI """ import numpy as np @@ -10,16 +11,16 @@ from .utils import text_to_bits, bits_to_text BLOCK_SIZE = 8 -# Более мягкая таблица квантования (качество ~70-80%) +# Таблица квантования JPEG (качество 50%) QUANT_TABLE = np.array([ - [8, 6, 5, 8, 12, 20, 26, 31], - [6, 6, 7, 10, 13, 29, 30, 28], - [7, 7, 8, 12, 20, 29, 35, 28], - [7, 9, 11, 15, 26, 44, 40, 31], - [9, 11, 19, 28, 34, 55, 52, 39], - [12, 18, 28, 32, 41, 52, 57, 46], - [25, 32, 39, 44, 52, 61, 60, 51], - [36, 46, 48, 49, 56, 50, 52, 50] + [16, 11, 10, 16, 24, 40, 51, 61], + [12, 12, 14, 19, 26, 58, 60, 55], + [14, 13, 16, 24, 40, 57, 69, 56], + [14, 17, 22, 29, 51, 87, 80, 62], + [18, 22, 37, 56, 68, 109, 103, 77], + [24, 35, 55, 64, 81, 104, 113, 92], + [49, 64, 78, 87, 103, 121, 120, 101], + [72, 92, 95, 98, 112, 100, 103, 99] ]) @@ -41,8 +42,8 @@ def _get_zigzag_order() -> list: ZIGZAG_ORDER = _get_zigzag_order() -# Используем больше коэффициентов (1-50) -MID_FREQ_INDICES = ZIGZAG_ORDER[1:50] +# Используем среднечастотные коэффициенты +MID_FREQ_INDICES = ZIGZAG_ORDER[10:40] def _dct_quantize(block: np.ndarray) -> np.ndarray: @@ -98,7 +99,13 @@ def encode_dct(image_path: str, message: str, output_path: str) -> bool: img = Image.open(image_path).convert('RGB') pixels = np.array(img, dtype=np.float64) height, width, channels = pixels.shape - + + # Проверка размеров + if height % BLOCK_SIZE != 0 or width % BLOCK_SIZE != 0: + print(f"Ошибка: размеры {width}x{height} не кратны {BLOCK_SIZE}") + return False + + # Формат: [длина: 32 бита] + [сообщение] msg_bytes = message.encode('utf-8') msg_length = len(msg_bytes) length_bits = format(msg_length, '032b') @@ -106,19 +113,20 @@ def encode_dct(image_path: str, message: str, output_path: str) -> bool: all_bits = length_bits + message_bits bit_list = [int(b) for b in all_bits] total_bits = len(bit_list) - + + # Проверка вместимости blocks_per_row = width // BLOCK_SIZE blocks_per_col = height // BLOCK_SIZE max_bits = blocks_per_row * blocks_per_col * len(MID_FREQ_INDICES) * 3 - + if total_bits > max_bits: print(f"Ошибка: сообщение слишком большое.") print(f"Доступно: {max_bits}, требуется: {total_bits}") return False - + modified_pixels = pixels.copy() bit_index = 0 - + for i in range(0, height - BLOCK_SIZE + 1, BLOCK_SIZE): for j in range(0, width - BLOCK_SIZE + 1, BLOCK_SIZE): if bit_index >= total_bits: @@ -127,7 +135,7 @@ def encode_dct(image_path: str, message: str, output_path: str) -> bool: for c in range(3): if bit_index >= total_bits: break - + block = pixels[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE, c] quant_block = _dct_quantize(block) quant_block, bit_index = _embed_bits_in_block(quant_block, bit_list, bit_index) @@ -136,7 +144,7 @@ def encode_dct(image_path: str, message: str, output_path: str) -> bool: if bit_index >= total_bits: break - + result_img = Image.fromarray(modified_pixels.astype(np.uint8), mode='RGB') result_img.save(output_path) print(f"Успешно! Спрятано {total_bits} бит ({total_bits // 8} байт)") @@ -148,7 +156,11 @@ def decode_dct(image_path: str) -> str: img = Image.open(image_path).convert('RGB') pixels = np.array(img, dtype=np.float64) height, width, channels = pixels.shape - + + if height % BLOCK_SIZE != 0 or width % BLOCK_SIZE != 0: + return "" + + # Извлекаем все биты all_bits = [] for i in range(0, height - BLOCK_SIZE + 1, BLOCK_SIZE): @@ -158,7 +170,8 @@ def decode_dct(image_path: str) -> str: quant_block = _dct_quantize(channel_block) bits_from_block = _extract_bits_from_block(quant_block, len(MID_FREQ_INDICES)) all_bits.extend(bits_from_block) - + + # Читаем длину сообщения (первые 32 бита) if len(all_bits) < 32: return "" @@ -166,15 +179,13 @@ def decode_dct(image_path: str) -> str: length_str = ''.join(str(b) for b in length_bits) msg_length = int(length_str, 2) - if msg_length > (len(all_bits) - 32) // 8: + # Проверка + if msg_length <= 0 or msg_length > (len(all_bits) - 32) // 8: return "" + # Читаем сообщение message_bits = all_bits[32:32 + msg_length * 8] - remainder = len(message_bits) % 8 - if remainder != 0: - message_bits.extend([0] * (8 - remainder)) - if len(message_bits) == 0: return "" @@ -182,5 +193,5 @@ def decode_dct(image_path: str) -> str: try: return bits_to_text(bits_string) except Exception as e: - print(f"Ошибка при декодировании: {e}") + print(f"Ошибка декодирования: {e}") return "" \ No newline at end of file diff --git a/gui/main_window.py b/gui/main_window.py index 1c86e2f..164cc79 100644 --- a/gui/main_window.py +++ b/gui/main_window.py @@ -4,7 +4,6 @@ GUI интерфейс для стеганографического прило import tkinter as tk from tkinter import ttk, filedialog, messagebox -from PIL import Image, ImageTk import threading import os import sys @@ -22,15 +21,16 @@ class StegoGUI: def __init__(self, root): self.root = root - self.root.title("Stego App - Стеганография") - self.root.geometry("750x650") + self.root.title("Stego App - Стеганографическое приложение") + self.root.geometry("800x700") 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.encode_image_path = tk.StringVar() + self.encode_output_path = tk.StringVar() + self.decode_image_path = tk.StringVar() + self.method_encode = tk.StringVar(value="lsb") + self.method_decode = tk.StringVar(value="lsb") self.setup_ui() @@ -60,27 +60,45 @@ class StegoGUI: 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) + 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_encode, value="lsb").grid(row=row, column=1, padx=5, sticky='w') + ttk.Radiobutton(self.encode_frame, text="DCT (для гладких/одноцветных изображений)", variable=self.method_encode, value="dct").grid(row=row, column=2, padx=5, sticky='w') + row += 1 + + # Разделитель + ttk.Separator(self.encode_frame, orient='horizontal').grid(row=row, column=0, columnspan=4, sticky='ew', padx=5, pady=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) + self.encode_image_entry = ttk.Entry(self.encode_frame, textvariable=self.encode_image_path, width=55) + self.encode_image_entry.grid(row=row, column=1, columnspan=2, padx=5, sticky='ew') + ttk.Button(self.encode_frame, text="Обзор...", command=self.select_encode_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) + ttk.Label(self.encode_frame, text="Сохранить как (PNG):").grid(row=row, column=0, sticky='w', padx=5, pady=5) + self.encode_output_entry = ttk.Entry(self.encode_frame, textvariable=self.encode_output_path, width=55) + self.encode_output_entry.grid(row=row, column=1, columnspan=2, padx=5, sticky='ew') + ttk.Button(self.encode_frame, text="Обзор...", command=self.select_encode_output).grid(row=row, column=3, padx=5) + row += 1 + + # Разделитель + ttk.Separator(self.encode_frame, orient='horizontal').grid(row=row, column=0, columnspan=4, sticky='ew', padx=5, pady=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) + 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=8, width=65, wrap=tk.WORD) + self.message_text.grid(row=row, column=1, columnspan=3, padx=5, pady=5, sticky='ew') + row += 1 + + # Кнопка очистки сообщения + ttk.Button(self.encode_frame, text="Очистить сообщение", command=self.clear_message).grid(row=row, column=1, sticky='w', padx=5, pady=5) + row += 1 + + # Разделитель + ttk.Separator(self.encode_frame, orient='horizontal').grid(row=row, column=0, columnspan=4, sticky='ew', padx=5, pady=5) row += 1 # Вместимость @@ -88,20 +106,20 @@ class StegoGUI: self.capacity_label.grid(row=row, column=1, columnspan=2, sticky='w', padx=5) row += 1 + # Прогресс + self.encode_progress = ttk.Progressbar(self.encode_frame, mode='indeterminate', length=400) + # Кнопка 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) + self.encode_btn.grid(row=row, column=1, columnspan=2, pady=15) 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()) + # Привязка событий + self.encode_image_path.trace('w', lambda *args: self.update_capacity()) def setup_decode_tab(self): """Вкладка декодирования""" @@ -109,30 +127,47 @@ class StegoGUI: # Метод 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) + ttk.Radiobutton(self.decode_frame, text="LSB (рекомендуется)", variable=self.method_decode, value="lsb").grid(row=row, column=1, padx=5, sticky='w') + ttk.Radiobutton(self.decode_frame, text="DCT (для гладких/одноцветных изображений)", variable=self.method_decode, value="dct").grid(row=row, column=2, padx=5, sticky='w') + row += 1 + + # Разделитель + ttk.Separator(self.decode_frame, orient='horizontal').grid(row=row, column=0, columnspan=4, sticky='ew', padx=5, pady=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) + self.decode_image_entry = ttk.Entry(self.decode_frame, textvariable=self.decode_image_path, width=55) + self.decode_image_entry.grid(row=row, column=1, columnspan=2, padx=5, sticky='ew') + ttk.Button(self.decode_frame, text="Обзор...", command=self.select_decode_image).grid(row=row, column=3, padx=5) row += 1 + # Разделитель + ttk.Separator(self.decode_frame, orient='horizontal').grid(row=row, column=0, columnspan=4, sticky='ew', padx=5, pady=5) + row += 1 + + # Прогресс + self.decode_progress = ttk.Progressbar(self.decode_frame, mode='indeterminate', length=400) + # Кнопка 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) + self.decode_btn.grid(row=row, column=1, columnspan=2, pady=15) 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) + self.result_text = tk.Text(self.decode_frame, height=8, width=65, wrap=tk.WORD) + self.result_text.grid(row=row, column=1, columnspan=3, padx=5, pady=5, sticky='ew') 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) + # Кнопки + button_frame = ttk.Frame(self.decode_frame) + button_frame.grid(row=row, column=1, columnspan=2, pady=5) + + self.save_btn = ttk.Button(button_frame, text="Сохранить в файл", command=self.save_result, state='disabled') + self.save_btn.pack(side='left', padx=5) + + ttk.Button(button_frame, text="Очистить результат", command=self.clear_result).pack(side='left', padx=5) row += 1 # Статус @@ -143,55 +178,121 @@ class StegoGUI: 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() + # Основная информация + info_frame = ttk.Frame(frame) + info_frame.pack(pady=20) + + ttk.Label(info_frame, text="Stego App", font=("Arial", 16, "bold")).pack(pady=5) + ttk.Label(info_frame, text="Стеганографическое приложение", font=("Arial", 10)).pack(pady=2) + ttk.Label(info_frame, text="").pack(pady=5) + ttk.Label(info_frame, text="Петров Игорь ИУ10-24").pack() + ttk.Label(info_frame, text="").pack(pady=5) + + # Год + ttk.Label(info_frame, text="2026", font=("Arial", 10, "italic")).pack(pady=5) + + # Разделитель + ttk.Separator(frame, orient='horizontal').pack(fill='x', padx=20, pady=10) + + # Техническая информация + tech_frame = ttk.Frame(frame) + tech_frame.pack(pady=10) + + ttk.Label(tech_frame, text="Методы стеганографии:", font=("Arial", 10, "bold")).pack(anchor='w') + ttk.Label(tech_frame, text=" • LSB (Least Significant Bit) - сокрытие в последних битах пикселей").pack(anchor='w') + ttk.Label(tech_frame, text=" Работает с любыми изображениями, высокая вместимость").pack(anchor='w', padx=20) + ttk.Label(tech_frame, text="").pack() + ttk.Label(tech_frame, text=" • DCT (Discrete Cosine Transform) - сокрытие в частотной области").pack(anchor='w') + ttk.Label(tech_frame, text=" Рекомендуется для гладких/однотонных изображений").pack(anchor='w', padx=20) + ttk.Label(tech_frame, text="").pack() + + ttk.Label(tech_frame, text="Поддерживаемые форматы:", font=("Arial", 10, "bold")).pack(anchor='w') + ttk.Label(tech_frame, text=" • PNG, BMP, JPG, JPEG").pack(anchor='w') + ttk.Label(tech_frame, text="").pack() + + ttk.Label(tech_frame, text="Рекомендации:", font=("Arial", 10, "bold")).pack(anchor='w') + ttk.Label(tech_frame, text=" • Для сохранения результата используйте PNG").pack(anchor='w') + ttk.Label(tech_frame, text=" • LSB - универсальный метод, рекомендуется по умолчанию").pack(anchor='w') - def select_image(self): - """Выбор изображения""" + def select_encode_image(self): + """Выбор изображения для кодирования""" filename = filedialog.askopenfilename( - title="Выберите изображение", + title="Выберите исходное изображение", filetypes=[("Изображения", "*.png *.bmp *.jpg *.jpeg"), ("Все файлы", "*.*")] ) if filename: - self.image_path.set(filename) + self.encode_image_path.set(filename) - def select_output(self): + def select_encode_output(self): """Выбор выходного файла""" filename = filedialog.asksaveasfilename( - title="Сохранить как", + title="Сохранить результат как", defaultextension=".png", filetypes=[("PNG", "*.png"), ("BMP", "*.bmp"), ("Все файлы", "*.*")] ) if filename: - self.output_path.set(filename) + self.encode_output_path.set(filename) + + def select_decode_image(self): + """Выбор изображения для декодирования""" + filename = filedialog.askopenfilename( + title="Выберите изображение со скрытым сообщением", + filetypes=[("Изображения", "*.png *.bmp *.jpg *.jpeg"), ("Все файлы", "*.*")] + ) + if filename: + self.decode_image_path.set(filename) + + def clear_message(self): + """Очистка поля сообщения""" + self.message_text.delete("1.0", tk.END) + + def clear_result(self): + """Очистка поля результата""" + self.result_text.delete("1.0", tk.END) + self.decoded_message = "" + self.save_btn.config(state='disabled') def update_capacity(self): """Обновление информации о вместимости""" - path = self.image_path.get() + path = self.encode_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} КБ)") + kb = capacity / 1024 + self.capacity_label.config(text=f"📊 Максимальная вместимость: {capacity} байт (~{kb:.1f} КБ)") except Exception as e: - self.capacity_label.config(text=f"Ошибка: {e}") + self.capacity_label.config(text=f"⚠️ Ошибка: {e}") else: self.capacity_label.config(text="") + def check_image_for_dct(self, image_path): + """Проверка изображения для DCT метода""" + try: + from PIL import Image, ImageStat + + img = Image.open(image_path) + w, h = img.size + + # Проверка размеров + if w % 8 != 0 or h % 8 != 0: + return False, f"Размеры изображения ({w}x{h}) должны быть кратны 8 для DCT метода" + + # Проверка "гладкости" изображения + stat = ImageStat.Stat(img) + # Если стандартное отклонение цветов большое (>50) — изображение пёстрое + if max(stat.stddev) > 50: + return False, "Изображение содержит много деталей/цветов.\nDCT рекомендуется только для гладких/однотонных изображений.\nИспользуйте LSB метод." + + return True, "" + except Exception as e: + return False, str(e) + def do_encode(self): """Выполнение кодирования""" - image = self.image_path.get() - output = self.output_path.get() + image = self.encode_image_path.get() + output = self.encode_output_path.get() message = self.message_text.get("1.0", tk.END).strip() - method = self.method.get() + method = self.method_encode.get() if not image: messagebox.showerror("Ошибка", "Выберите исходное изображение") @@ -203,11 +304,18 @@ class StegoGUI: messagebox.showerror("Ошибка", "Введите сообщение для сокрытия") return + # Проверка для DCT + if method == 'dct': + is_ok, error_msg = self.check_image_for_dct(image) + if not is_ok: + messagebox.showerror("Ошибка", error_msg) + 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="Кодирование...") + self.encode_progress.grid(row=11, column=1, columnspan=2, sticky='ew', padx=5, pady=5) + self.encode_progress.start() + self.status_label.config(text="⏳ Кодирование... Пожалуйста, подождите", foreground="blue") thread = threading.Thread(target=self._do_encode, args=(image, output, message, method)) thread.daemon = True @@ -221,34 +329,38 @@ class StegoGUI: else: success = encode_dct(image, message, output) - self.root.after(0, self._encode_done, success) + self.root.after(0, self._encode_done, success, None) 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_progress.stop() + self.encode_progress.grid_forget() self.encode_btn.config(state='normal') if success: self.status_label.config(text="✅ Кодирование успешно завершено!", foreground="green") - messagebox.showinfo("Успех", "Сообщение успешно скрыто в изображении!") + messagebox.showinfo("Успех", f"✅ Сообщение успешно скрыто в изображении!\n\nСохранено: {self.encode_output_path.get()}") else: - self.status_label.config(text=f"❌ Ошибка: {error or 'Кодирование не удалось'}", foreground="red") - messagebox.showerror("Ошибка", error or "Не удалось скрыть сообщение") + error_msg = error or "Неизвестная ошибка" + self.status_label.config(text=f"❌ Ошибка кодирования", foreground="red") + messagebox.showerror("Ошибка", f"Не удалось скрыть сообщение:\n{error_msg}") def do_decode(self): """Выполнение декодирования""" - image = self.image_path.get() - method = self.method.get() + image = self.decode_image_path.get() + method = self.method_decode.get() if not image: messagebox.showerror("Ошибка", "Выберите изображение") return + # Запускаем в отдельном потоке self.decode_btn.config(state='disabled') - self.decode_status.config(text="Декодирование...") + self.decode_progress.grid(row=2, column=1, columnspan=2, sticky='ew', padx=5, pady=5) + self.decode_progress.start() + self.decode_status.config(text="⏳ Декодирование... Пожалуйста, подождите", foreground="blue") thread = threading.Thread(target=self._do_decode, args=(image, method)) thread.daemon = True @@ -262,35 +374,46 @@ class StegoGUI: else: message = decode_dct(image) - self.root.after(0, self._decode_done, message) + self.root.after(0, self._decode_done, message, None) except Exception as e: self.root.after(0, self._decode_done, None, str(e)) def _decode_done(self, message, error=None): """Завершение декодирования""" + self.decode_progress.stop() + self.decode_progress.grid_forget() self.decode_btn.config(state='normal') - if message and not error: + if message and not error and message != "": 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") + messagebox.showinfo("Успех", f"✅ Сообщение успешно извлечено!\n\nДлина: {len(message)} символов") else: - self.decode_status.config(text=f"❌ Ошибка: {error or 'Не удалось извлечь сообщение'}", foreground="red") + error_msg = error or "Сообщение не найдено или повреждено" + self.decode_status.config(text=f"❌ Ошибка декодирования", foreground="red") self.save_btn.config(state='disabled') + messagebox.showerror("Ошибка", f"Не удалось извлечь сообщение:\n{error_msg}") def save_result(self): """Сохранение извлеченного сообщения в файл""" + if not self.decoded_message: + return + 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}") + if filename: + try: + with open(filename, 'w', encoding='utf-8') as f: + f.write(self.decoded_message) + messagebox.showinfo("Успех", f"✅ Сообщение сохранено в:\n{filename}") + except Exception as e: + messagebox.showerror("Ошибка", f"Не удалось сохранить файл:\n{e}") def main():