import os
import numpy as np
from imageio.v2 import imread, imwrite
import tkinter as tk
from tkinter import filedialog
from PIL import Image, ImageTk
max_value = 255 # max uint value per pixel per channel
header_len = 4 * 8 # uint32 bit length
def read_image(img_path):
img = np.array(imread(img_path), dtype=np.uint8)
orig_shape = img.shape
return img.flatten(), orig_shape
def write_image(img_path, img_data, shape):
img_data = np.reshape(img_data, shape)
imwrite(img_path, img_data)
def bytes2array(byte_data):
byte_array = np.frombuffer(byte_data, dtype=np.uint8)
return np.unpackbits(byte_array)
def array2bytes(bit_array):
byte_array = np.packbits(bit_array)
return byte_array.tobytes()
def read_file(file_path):
file_bytes = open(file_path, "rb").read()
return bytes2array(file_bytes)
def write_file(file_path, file_bit_array):
bytes_data = array2bytes(file_bit_array)
with open(file_path, 'wb') as f:
f.write(bytes_data)
def encode_data(image, file_data):
or_mask = file_data
and_mask = np.zeros_like(or_mask)
and_mask = (and_mask + max_value - 1) + or_mask
res = np.bitwise_or(image, or_mask)
res = np.bitwise_and(res, and_mask)
return res
def decode_data(encoded_data):
out_mask = np.ones_like(encoded_data)
output = np.bitwise_and(encoded_data, out_mask)
return output
def update_zoom(event=None):
if 'original_image' not in globals():
print("No image loaded to zoom")
return
# Update the displayed image based on the zoom scale value
zoom_value = zoom_slider.get() / 100 # Scale the value down to a fraction
resized_image = original_image.resize((int(original_image.width * zoom_value), int(original_image.height * zoom_value)))
zoomed_photo = ImageTk.PhotoImage(resized_image)
lbl.config(image=zoomed_photo)
lbl.image = zoomed_photo
def hide_images():
global original_image # Ensure this is a global variable
img_path = original_entry_hide.get()
file_path = hide_file_entry.get()
output_path = save_file_entry_hide.get()
if not os.path.isfile(img_path):
print("Original image file does not exist")
return
if not os.path.isfile(file_path):
print("File to hide does not exist")
return
image, shape_orig = read_image(img_path)
file = read_file(file_path)
file_len = file.shape[0]
len_array = np.array([file_len], dtype=np.uint32).view(np.uint8)
len_array = np.unpackbits(len_array)
img_len = image.shape[0]
if file_len >= img_len - header_len:
print("File too big, error")
return
else:
tmp = file
file = np.random.randint(2, size=img_len, dtype=np.uint8)
file[header_len:header_len+file_len] = tmp
file[:header_len] = len_array
encoded_data = encode_data(image, file)
write_image(output_path, encoded_data, shape_orig)
print("Image encoded")
# Update the preview image
original_image = Image.fromarray(encoded_data.reshape(shape_orig))
photo = ImageTk.PhotoImage(original_image)
lbl.config(image=photo)
lbl.image = photo
zoom_slider.set(100) # Reset the zoom slider to default
def unhide_images():
global original_image # Ensure this is a global variable
original_file = original_entry_unhide.get()
save_file = save_file_entry_unhide.get()
if not os.path.isfile(original_file):
print("Image file does not exist")
return
encoded_data, shape_orig = read_image(original_file)
data = decode_data(encoded_data)
el_array = np.packbits(data[:header_len])
extracted_len = el_array.view(np.uint32)[0]
data = data[header_len:extracted_len+header_len]
write_file(save_file, data)
print("File extracted and saved")
# Load and display the original image
original_image = Image.open(original_file)
original_image.thumbnail((shape_orig[1], shape_orig[0])) # Resize if needed
original_photo = ImageTk.PhotoImage(original_image)
lbl.config(image=original_photo)
lbl.image = original_photo
zoom_slider.set(100) # Reset the zoom slider to default
# Create the root window
root = tk.Tk()
root.geometry("1000x700")
root.title("Image Steganography Tool")
root.configure(bg="#282c34")
# Styling for labels and buttons
label_font = ('Arial', 12, 'bold')
entry_font = ('Arial', 11)
button_font = ('Arial', 12, 'bold')
button_color = "#61afef"
button_fg = "white"
# Hiding Section
tk.Label(root, text="Hide a File Inside an Image", font=('Arial', 14, 'bold'), bg="#282c34", fg="white").grid(row=0, column=0, columnspan=2, pady=20)
# Input for Original Image to hide file in
tk.Label(root, text="Select Original Image:", font=label_font, bg="#282c34", fg="white").grid(row=1, column=0, sticky="e", padx=10)
original_entry_hide = tk.Entry(root, width=40, font=entry_font)
original_entry_hide.grid(row=1, column=1, padx=10, pady=5)
browse_button_hide_image = tk.Button(root, text="Browse", font=button_font, bg=button_color, fg=button_fg, command=lambda: original_entry_hide.insert(0, filedialog.askopenfilename()))
browse_button_hide_image.grid(row=1, column=2, padx=10, pady=5)
# Input for File to hide
tk.Label(root, text="Select File to Hide:", font=label_font, bg="#282c34", fg="white").grid(row=2, column=0, sticky="e", padx=10)
hide_file_entry = tk.Entry(root, width=40, font=entry_font)
hide_file_entry.grid(row=2, column=1, padx=10, pady=5)
browse_button_hide_file = tk.Button(root, text="Browse", font=button_font, bg=button_color, fg=button_fg, command=lambda: hide_file_entry.insert(0, filedialog.askopenfilename()))
browse_button_hide_file.grid(row=2, column=2, padx=10, pady=5)
# Input for Save Location of encoded image
tk.Label(root, text="Save Hidden Image As:", font=label_font, bg="#282c34", fg="white").grid(row=3, column=0, sticky="e", padx=10)
save_file_entry_hide = tk.Entry(root, width=40, font=entry_font)
save_file_entry_hide.grid(row=3, column=1, padx=10, pady=5)
browse_button_save_hide = tk.Button(root, text="Browse", font=button_font, bg=button_color, fg=button_fg, command=lambda: save_file_entry_hide.insert(0, filedialog.asksaveasfilename(defaultextension=".png")))
browse_button_save_hide.grid(row=3, column=2, padx=10, pady=5)
# Button to hide the file
hide_button = tk.Button(root, text="Hide and Save Image", font=button_font, bg="#98c379", fg="black", command=hide_images)
hide_button.grid(row=4, column=1, pady=20)
# Unhiding Section
tk.Label(root, text="Unhide File from Image", font=('Arial', 14, 'bold'), bg="#282c34", fg="white").grid(row=5, column=0, columnspan=2, pady=20)
# Input for Encoded Image to unhide file from
tk.Label(root, text="Select Encoded Image:", font=label_font, bg="#282c34", fg="white").grid(row=6, column=0, sticky="e", padx=10)
original_entry_unhide = tk.Entry(root, width=40, font=entry_font)
original_entry_unhide.grid(row=6, column=1, padx=10, pady=5)
browse_button_unhide_image = tk.Button(root, text="Browse", font=button_font, bg=button_color, fg=button_fg, command=lambda: original_entry_unhide.insert(0, filedialog.askopenfilename()))
browse_button_unhide_image.grid(row=6, column=2, padx=10, pady=5)
# Input for Save Location of extracted file
tk.Label(root, text="Save Extracted File As:", font=label_font, bg="#282c34", fg="white").grid(row=7, column=0, sticky="e", padx=10)
save_file_entry_unhide = tk.Entry(root, width=40, font=entry_font)
save_file_entry_unhide.grid(row=7, column=1, padx=10, pady=5)
browse_button_save_unhide = tk.Button(root, text="Browse", font=button_font, bg=button_color, fg=button_fg, command=lambda: save_file_entry_unhide.insert(0, filedialog.asksaveasfilename()))
browse_button_save_unhide.grid(row=7, column=2, padx=10, pady=5)
# Button to unhide the file
unhide_button = tk.Button(root, text="Extract and Save File", font=button_font, bg="#e06c75", fg="black", command=unhide_images)
unhide_button.grid(row=8, column=1, pady=20)
# Preview Section for Original and Encoded Images
preview_frame = tk.Frame(root, relief="groove", bg="white")
preview_frame.grid(row=0, column=3, rowspan=10, padx=20, pady=10)
lbl = tk.Label(preview_frame, text="Preview", bg="white", font=label_font)
lbl.grid(row=0, column=0)
# Image Zoom Slider
zoom_slider = tk.Scale(root, from_=10, to=200, orient='horizontal', label="Zoom Image (%)", font=label_font, bg="#282c34", fg="white", command=update_zoom)
zoom_slider.grid(row=9, column=3, padx=20, pady=10)
zoom_slider.set(100) # Default zoom is 100%
root.mainloop()