Skip to content

Commit

Permalink
Add files via upload
Browse files Browse the repository at this point in the history
  • Loading branch information
chathaj committed Nov 15, 2024
1 parent 75117af commit 010e444
Show file tree
Hide file tree
Showing 5 changed files with 323 additions and 0 deletions.
Binary file added Encoding and Decoding an Image/Messi.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
82 changes: 82 additions & 0 deletions Encoding and Decoding an Image/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# **Image Text Encoder/Decoder**

This Python script provides a simple way to encode text messages into image files and decode them back.
It uses the OpenCV library to work with images and allows users to hide text within an image's pixel data.

## **How To Use:**

Run the script and choose the mode: encode or decode.
If encoding, provide the path to the image file and the text message you want to hide.
If decoding, provide the path to the encoded image.
The script will perform the selected operation and display the result.

## **Algorithm Justification:**

The programme encodes text messages with XOR and stores them in images using Least Significant Bit (LSB) steganography. XOR encryption was picked because it is simple and removable, which is appropriate for the helpful and lightweight nature of this tool. LSB steganography is used because it can conceal information inside the image's noise level, rendering changes unnoticeable to the human eye and keeping the image's original look while securely encoding the data.


## **How The Tool Works:**

### ImageLoader:

Loads the image and checks that it is a valid PNG file.

### Encryptor:

Encrypts or decrypts the given text using the XOR method. It's a static class that may be readily customised for various encryption methods.

### TextEncoder:

The encrypted text is encoded into the picture using LSB steganography. It examines the text length to ensure that it is within the image's capacity.

### TextDecoder:

Extracts and decrypts text from images by reversing the encoding process.

### ImageEncoder:

Controls the loading, encoding, and saving of the picture with embedded text.


## **Test Cases:**
The unittest framework is used to guarantee that the Image Text Encoder/Decoder tool is both robust and reliable. Below is an overview of the test cases used to validate each component of the system:

### TestImageLoader:
**Valid Image File**
Confirms that the ImageLoader correctly identifies valid PNG files. A temporary image is created and verified to be recognized as a valid image file.

### Load Image:
Ensures that an image is correctly loaded from the filesystem, checking both the presence of the loaded image and its dimensions to match the expected values.

### TestTextEncoder:
**Encode Text**
Verifies that text can be successfully encoded into an image. This test uses a blank image to encode a simple message, ensuring that the encoding process results in a modified image.

### TestImageEncoder:
**Save Encoded Image**
Tests the ability of the ImageEncoder to save an encoded image to the filesystem. It confirms that the saved file is correctly named to indicate its encoded status and then cleans up by removing the file.

### TestEncryptor:
**XOR Encrypt/Decrypt**
Validates the Encryptor's ability to encrypt a message with a key and then decrypt it back to its original form. It checks that the decrypted message matches the original and that the encrypted form is different.

### TestTextDecoder:
**Decode Text**
Assesses the TextDecoder's capability to accurately extract and decrypt text from an encoded image. The test ensures that the decoded text matches the original message encoded in the image.

### TestErrorHandling:
**Invalid Image File**
Tests the system's response to an attempt at loading a non-existent image file, expecting a ValueError to be raised.
### Text Too Long:
Verifies that encoding text which exceeds the capacity of the image results in a ValueError, preventing the operation.


## Notes
This tool displays basic steganography and encryption techniques. It is intended for educational uses and is not suitable for secure communications until modified to use higher-level encryption methods.

## Requirements:
1. Python 3.x
2. OpenCV (cv2) library

## Supported Files
The script currently supports PNG image files only due to their lossless nature, which is crucial for accurately encoding and decoding text within an image.
164 changes: 164 additions & 0 deletions Encoding and Decoding an Image/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import cv2
import os
# OpenCV is needed to work with images and os to work with files and directories
class ImageLoader:
"""Loads and checks image files."""
def __init__(self, image_filename):
"""
Initializes with an image filename.

:param image_filename: Name of the image file.
"""
self.image_filename = image_filename
self.image = None

def is_image_file_valid(self):
"""
Checks if the image file is a valid PNG.

:return: True if valid, False otherwise.
"""
# This method checks if the file name provided is actually an image file that the user can work with.
valid_extensions = ['.png']
return os.path.isfile(self.image_filename) and os.path.splitext(self.image_filename)[1] in valid_extensions

def load_image_file(self):
"""
Loads the image from the file.

:return: Loaded image.
:raises ValueError: If file is not a valid image.
"""
if self.is_image_file_valid():
self.image = cv2.imread(self.image_filename)
else:
raise ValueError("Invalid image file.")
return self.image

class Encryptor:
"""Handles text encryption and decryption."""
@staticmethod
def xor_encrypt_decrypt(input_text, key=123): # (OpenAI, 2022)
"""
Encrypts/decrypts text using XOR.

:param input_text: Text to encrypt/decrypt.
:param key: Encryption key.
:return: Encrypted/decrypted text.
"""
# This method uses XOR encryption/decryption on the input text.
output = ''.join(
# It changes each letter into a number, does the XOR with the key and then changes it back to a letter.
chr(ord(char) ^ key) for char in input_text
)
return output # Returns the encrypted or decrypted text.
# (OpenAI, 2022)

class TextIntoImageEncoder:
"""Encodes text into an image."""
@staticmethod
def encode_text_into_image(image, text):
"""
Encodes text into an image.

:param image: Image to encode text into.
:param text: Text to encode.
:return: Image with encoded text.
:raises ValueError: If image not loaded or text too long.
"""
# This method takes some text and hides it inside the image.
if image is None:
raise ValueError("Image not loaded.")

# (OpenAI, 2022)
encrypted_text = Encryptor.xor_encrypt_decrypt(text)

binary_text = ''.join([format(ord(char), '08b') for char in encrypted_text]) + '00000000'


if len(binary_text) > image.shape[0] * image.shape[1] * 3:
raise ValueError("Text too long to encode in the image.")

data_index = 0
for i in range(image.shape[0]):
for j in range(image.shape[1]):
pixel = list(image[i, j])
for k in range(3):
if data_index < len(binary_text):
pixel[k] = (pixel[k] & 254) | int(binary_text[data_index])
data_index += 1
image[i, j] = tuple(pixel)
return image
# (OpenAI, 2022)

class TextFromImageDecoder:
"""Decodes text from an image."""
@staticmethod
def decode_text_from_image(image):
"""
Extracts text from an image.

:param image: Image to extract text from.
:return: Decoded text.
:raises ValueError: If image not loaded.
"""
# (OpenAI, 2022)
if image is None:
raise ValueError("Image not loaded.")
binary_text = ''
for i in range(image.shape[0]):
for j in range(image.shape[1]):
pixel = image[i, j]
for k in range(3):
binary_text += str(pixel[k] & 1)
all_bytes = [binary_text[i: i+8] for i in range(0, len(binary_text), 8)]
encrypted_text = ''
for byte in all_bytes:
if byte == '00000000':
break
encrypted_text += chr(int(byte, 2))

decrypted_text = Encryptor.xor_encrypt_decrypt(encrypted_text) # Uses the Encryptor class to decrypt the text.
return decrypted_text # Return the decrypted text so it can be displayed.
# (OpenAI, 2022)

class ImageTextEncoder:
"""Manages encoding of text into images."""
def __init__(self, image_filename):
"""
Initializes with the image filename.

:param image_filename: Image file to process.
"""
self.loader = ImageLoader(image_filename)
def save_encoded_image(self, image):
"""
Saves the encoded image to a file.

:param image: Image with encoded text.
:return: Filename of the saved image.
"""
encoded_image_filename = os.path.splitext(self.loader.image_filename)[0] + "_encoded" + os.path.splitext(self.loader.image_filename)[1]
cv2.imwrite(encoded_image_filename, image)
return encoded_image_filename

# This is the menu the user is presented with when first running the Tool.
if __name__ == "__main__":
mode = input("Choose mode (encode/decode): ")
image_filename = input("Image Filename (with extension): ")
encoder = ImageTextEncoder(image_filename)

try:
image = encoder.loader.load_image_file()
if mode.lower() == "encode":
text = input("Encoded Text: ")
encoded_image = TextIntoImageEncoder.encode_text_into_image(image, text)
encoded_image_filename = encoder.save_encoded_image(encoded_image)
print(f"Text successfully encoded. Encoded image saved as: {encoded_image_filename}")
elif mode.lower() == "decode":
extracted_text = TextFromImageDecoder.decode_text_from_image(image)
print(f"Extracted Text: {extracted_text}")
else:
print("Invalid mode. Please choose 'encode' or 'decode'.")
except Exception as e:
print(f"Error: {e}")
3 changes: 3 additions & 0 deletions Encoding and Decoding an Image/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Requirements:
opencv-python~=4.9.0.80
numpy~=1.24.2
74 changes: 74 additions & 0 deletions Encoding and Decoding an Image/testcase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import unittest
import os
import cv2
import numpy as np
from main import ImageLoader, TextIntoImageEncoder, TextFromImageDecoder, ImageTextEncoder, Encryptor

class TestImageLoader(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.test_image_path = "test_image.png"
cls.test_image = np.random.randint(0, 256, (100, 100, 3), dtype=np.uint8)
cv2.imwrite(cls.test_image_path, cls.test_image)

@classmethod
def tearDownClass(cls):
os.remove(cls.test_image_path)

def test_is_valid_image_file(self):
loader = ImageLoader(self.test_image_path)
self.assertTrue(loader.is_image_file_valid())

def test_load_image_file(self):
loader = ImageLoader(self.test_image_path)
image = loader.load_image_file()
self.assertIsNotNone(image)
self.assertEqual(image.shape, (100, 100, 3))

class TestTextIntoImageEncoder(unittest.TestCase):
def test_encode_text_into_image(self):
image = np.zeros((100, 100, 3), dtype=np.uint8)
encoded_image = TextIntoImageEncoder.encode_text_into_image(image, "Test")
self.assertIsNotNone(encoded_image)

class TestImageTextEncoder(unittest.TestCase):
def test_save_encoded_image(self):
encoder = ImageTextEncoder("test_image.png")
image = np.zeros((100, 100, 3), dtype=np.uint8)
encoded_image_filename = encoder.save_encoded_image(image)
self.assertTrue(encoded_image_filename.endswith("_encoded.png"))
os.remove(encoded_image_filename)

class TestEncryptor(unittest.TestCase):
def test_xor_encrypt_decrypt(self):
original_text = "Hello"
key = 123
encrypted = Encryptor.xor_encrypt_decrypt(original_text, key)
decrypted = Encryptor.xor_encrypt_decrypt(encrypted, key)
self.assertEqual(original_text, decrypted)
self.assertNotEqual(original_text, encrypted)

class TestTextFromImageDecoder(unittest.TestCase):
def setUp(self):
self.image = np.zeros((100, 100, 3), dtype=np.uint8)
self.test_text = "Hello"
self.encoded_image = TextIntoImageEncoder.encode_text_into_image(self.image, self.test_text)

def test_decode_text_from_image(self):
decoded_text = TextFromImageDecoder.decode_text_from_image(self.encoded_image)
self.assertEqual(decoded_text, self.test_text)

class TestErrorHandling(unittest.TestCase):
def test_invalid_image_file(self):
loader = ImageLoader("nonexistent_file.png")
with self.assertRaises(ValueError):
loader.load_image_file()

def test_text_too_long(self):
image = np.zeros((10, 10, 3), dtype=np.uint8)
long_text = "a" * 1000
with self.assertRaises(ValueError):
TextIntoImageEncoder.encode_text_into_image(image, long_text)

if __name__ == "__main__":
unittest.main()

0 comments on commit 010e444

Please sign in to comment.