class SteganographyImg(BaseCodec):
"""SteganographyImg
This module implements simple LSB steganography in images.
* To encode: Convert the RGB values for each color channel to binary. Again for each
channel, take the 4 most significant bits from the cover image and the 4 most
significant bits from the hidden image. Shift the 4 bits from the hidden image to
the positions of the least significant bits and combine them with the 4 bits from
the cover image Combine the RGB channels together to create a new pixel, dominated
by the 4 significant bits from the cover image but with the 4 most significant
bits from the hidden image encoded within. The color will change slightly due to
the information hidden in the least significant bits.
* To decode: To decode the hidden image, take the 4 least significant bits from the
new pixel and shift them back to the positions of the most significant bits. Fill
in the vacated least significant bits with 0's (this is information we've forever
lost due to the encoding).The hidden image will now appear, slightly different
than the original due to the lost data in the least significant bits.
Originally implemented in
[raffg/steganography](https://github.com/raffg/steganography)
"""
def __init__(self) -> None:
super().__init__()
def encode(self, carrier: Image.Image, payload: Image.Image) -> Image.Image:
img1 = np.array(carrier)
img2 = np.array(payload)
h1, w1, s1 = img1.shape
h2, w2, s2 = img2.shape
# Ensure image 1 is larger than image 2
if h2 > h1 or w2 > w1:
raise ValueError("Image 1 size is lower than image 2 size!")
for i in range(h1):
for j in range(w1):
rgb1 = integer_to_binary(img1[i, j])
# Get the pixel map of the two images
rgb2 = integer_to_binary((0, 0, 0))
if i < h2 and j < w2:
rgb2 = integer_to_binary(img2[i, j])
# Merge the two pixels and convert it to a integer tuple
rgb = merge_rgb(rgb1, rgb2)
img1[i, j] = binary_to_integer(rgb)
new_img = Image.fromarray(img1)
return new_img
def decode(self, carrier: Image.Image) -> Image.Image:
img1 = np.array(carrier)
h1, w1, s1 = img1.shape
h2, w2 = h1, w1
for i in range(h1):
for j in range(w1):
# Get the RGB (as a string tuple) from the current pixel
r, g, b = integer_to_binary(img1[i, j])
# Extract the last 4 bits (corresponding to the hidden image)
# Concatenate 4 zero bits because we are working with 8 bit values
rgb = (r[4:] + "0000", g[4:] + "0000", b[4:] + "0000")
# Convert it to an integer tuple
img1[i, j] = binary_to_integer(rgb)
# If this is a 'valid' position, store it
# as the last valid position
if img1[i, j, 0] != 0 and img1[i, j, 1] != 0 and img1[i, j, 2] != 0:
h2 = i + 1
w2 = j + 1
img2 = np.zeros((h2, w2, 3))
for i in range(h2):
for j in range(w2):
for k in range(3):
img2[i, j, k] = img1[i, j, k]
new_img = Image.fromarray(np.uint8(img2))
return new_img
def _check_empty_payload(self, payload: str) -> None:
if not payload:
raise Exception("Payload must not be empty.")