from PIL import Image
import numpy as np

# Преобразование текста в двоичный код
def text_to_bin(text):
    return ''.join(format(ord(i), '08b') for i in text)

# Преобразование двоичного кода обратно в текст
def bin_to_text(bin_data):
    chars = [bin_data[i:i+8] for i in range(0, len(bin_data), 8)]
    return ''.join(chr(int(i, 2)) for i in chars)

# Считывание текста из файла
def read_text_from_file(file_path):
    with open(file_path, 'r') as file:
        return file.read()

# Вычисление бита четности для блока пикселей
def calculate_parity(block):
    flat_block = block.flatten()
    return sum(flat_block) % 2

# Внедрение текста в изображение методом Куттера-Джордана-Боссена
def embed_text_in_image_kjb(image_path, text_file_path, output_path):
    text = read_text_from_file(text_file_path)  # Считываем текст из файла
    bin_text = text_to_bin(text) + '1111111111111110'  # Добавляем завершение сообщения
    
    img = Image.open(image_path)
    img_data = np.array(img)
    
    data_index = 0
    block_size = 2  # Размер блока пикселей (2x2)
    for row in range(0, img_data.shape[0], block_size):
        for col in range(0, img_data.shape[1], block_size):
            if data_index < len(bin_text):
                # Определяем блок из 2x2 пикселей
                block = img_data[row:row+block_size, col:col+block_size, :3]

                # Вычисляем текущий бит четности блока
                parity_bit = calculate_parity(block)
                
                # Скрываем текущий бит сообщения
                secret_bit = int(bin_text[data_index])

                if parity_bit != secret_bit:
                    # Если бит четности не совпадает с битом сообщения, инвертируем один пиксель
                    # Инвертируем LSB у первого пикселя блока
                    block[0, 0] = invert_lsb(block[0, 0])  # инвертируем LSB
                    parity_bit = calculate_parity(block)  # пересчитываем бит четности
                    if parity_bit != secret_bit:
                        # Если бит четности по-прежнему не совпадает, инвертируем случайный LSB другого пикселя
                        block[1, 0] = invert_lsb(block[1, 0])  # пример инвертирования

                # Обновляем пиксели в изображении
                img_data[row:row+block_size, col:col+block_size, :3] = block
                data_index += 1

    # Сохраняем изменённое изображение
    new_img = Image.fromarray(img_data)
    new_img.save(output_path)
    return len(bin_text)

# Извлечение текста из изображения методом Куттера-Джордана-Боссена
def extract_text_from_image_kjb(image_path):
    img = Image.open(image_path)
    img_data = np.array(img)
    
    bin_data = ''
    block_size = 2  # Размер блока пикселей (2x2)
    
    for row in range(0, img_data.shape[0], block_size):
        for col in range(0, img_data.shape[1], block_size):
            # Определяем блок из 2x2 пикселей
            block = img_data[row:row+block_size, col:col+block_size, :3]
            
            # Вычисляем текущий бит четности блока
            parity_bit = calculate_parity(block)
            bin_data += str(parity_bit)

            if bin_data.endswith("1111111111111110"):  # Проверка окончания сообщения
                break
        if bin_data.endswith("1111111111111110"):
            break

    return bin_to_text(bin_data[:-16])  # Убираем маркер окончания

# Функция инвертирования LSB
def invert_lsb(value):
    return value ^ 1  # Инвертируем младший бит

def calculate_psnr(original_image_path, modified_image_path):
    original_img = np.array(Image.open(original_image_path))
    modified_img = np.array(Image.open(modified_image_path))
    
    mse = np.mean((original_img - modified_img) ** 2)
    if mse == 0:
        return 100  # Идеальный случай
    
    psnr = 10 * np.log10(255**2 / mse)
    return psnr

def main():
    print("Выберите действие:")
    print("1 - Кодирование текста в изображение методом Куттера-Джордана-Боссена")
    print("2 - Декодирование текста из изображения методом Куттера-Джордана-Боссена")
    print("3 - Расчет PSNR между изображениями")
    
    choice = input("Введите номер действия: ")
    
    if choice == '1':
        image_path = input("Введите путь к изображению для кодирования: ")
        text_file_path = input("Введите путь к текстовому файлу для внедрения: ")
        output_path = input("Введите путь для сохранения изображения с внедренным текстом: ")
        
        bits_inserted = embed_text_in_image_kjb(image_path, text_file_path, output_path)
        print(f"Количество вставленных бит: {bits_inserted}")
    
    elif choice == '2':
        image_path = input("Введите путь к изображению для декодирования: ")
        
        extracted_text = extract_text_from_image_kjb(image_path)
        print(f"Извлеченный текст: {extracted_text}")
    
    elif choice == '3':
        original_image_path = input("Введите путь к оригинальному изображению: ")
        modified_image_path = input("Введите путь к измененному изображению: ")
        
        psnr_value = calculate_psnr(original_image_path, modified_image_path)
        print(f"PSNR: {psnr_value} dB")
    
    else:
        print("Неверный выбор. Пожалуйста, выберите правильный номер действия.")

if __name__ == "__main__":
    main()

объясни подробно код