-
-
Save aliarain/881cae967ebe3a09b2707b01808c6d05 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
import click | |
from PIL import Image | |
class Steganography(object): | |
@staticmethod | |
def __int_to_bin(rgb): | |
"""Convert an integer tuple to a binary (string) tuple. | |
:param rgb: An integer tuple (e.g. (220, 110, 96)) | |
:return: A string tuple (e.g. ("00101010", "11101011", "00010110")) | |
""" | |
r, g, b = rgb | |
return ('{0:08b}'.format(r), | |
'{0:08b}'.format(g), | |
'{0:08b}'.format(b)) | |
@staticmethod | |
def __bin_to_int(rgb): | |
"""Convert a binary (string) tuple to an integer tuple. | |
:param rgb: A string tuple (e.g. ("00101010", "11101011", "00010110")) | |
:return: Return an int tuple (e.g. (220, 110, 96)) | |
""" | |
r, g, b = rgb | |
return (int(r, 2), | |
int(g, 2), | |
int(b, 2)) | |
@staticmethod | |
def __merge_rgb(rgb1, rgb2): | |
"""Merge two RGB tuples. | |
:param rgb1: A string tuple (e.g. ("00101010", "11101011", "00010110")) | |
:param rgb2: Another string tuple | |
(e.g. ("00101010", "11101011", "00010110")) | |
:return: An integer tuple with the two RGB values merged. | |
""" | |
r1, g1, b1 = rgb1 | |
r2, g2, b2 = rgb2 | |
rgb = (r1[:4] + r2[:4], | |
g1[:4] + g2[:4], | |
b1[:4] + b2[:4]) | |
return rgb | |
@staticmethod | |
def merge(img1, img2): | |
"""Merge two images. The second one will be merged into the first one. | |
:param img1: First image | |
:param img2: Second image | |
:return: A new merged image. | |
""" | |
# Check the images dimensions | |
if img2.size[0] > img1.size[0] or img2.size[1] > img1.size[1]: | |
raise ValueError('Image 2 should not be larger than Image 1!') | |
# Get the pixel map of the two images | |
pixel_map1 = img1.load() | |
pixel_map2 = img2.load() | |
# Create a new image that will be outputted | |
new_image = Image.new(img1.mode, img1.size) | |
pixels_new = new_image.load() | |
for i in range(img1.size[0]): | |
for j in range(img1.size[1]): | |
rgb1 = Steganography.__int_to_bin(pixel_map1[i, j]) | |
# Use a black pixel as default | |
rgb2 = Steganography.__int_to_bin((0, 0, 0)) | |
# Check if the pixel map position is valid for the second image | |
if i < img2.size[0] and j < img2.size[1]: | |
rgb2 = Steganography.__int_to_bin(pixel_map2[i, j]) | |
# Merge the two pixels and convert it to a integer tuple | |
rgb = Steganography.__merge_rgb(rgb1, rgb2) | |
pixels_new[i, j] = Steganography.__bin_to_int(rgb) | |
return new_image | |
@staticmethod | |
def unmerge(img): | |
"""Unmerge an image. | |
:param img: The input image. | |
:return: The unmerged/extracted image. | |
""" | |
# Load the pixel map | |
pixel_map = img.load() | |
# Create the new image and load the pixel map | |
new_image = Image.new(img.mode, img.size) | |
pixels_new = new_image.load() | |
# Tuple used to store the image original size | |
original_size = img.size | |
for i in range(img.size[0]): | |
for j in range(img.size[1]): | |
# Get the RGB (as a string tuple) from the current pixel | |
r, g, b = Steganography.__int_to_bin(pixel_map[i, j]) | |
# Extract the last 4 bits (corresponding to the hidden image) | |
# Concatenate 4 zero bits because we are working with 8 bit | |
rgb = (r[4:] + '0000', | |
g[4:] + '0000', | |
b[4:] + '0000') | |
# Convert it to an integer tuple | |
pixels_new[i, j] = Steganography.__bin_to_int(rgb) | |
# If this is a 'valid' position, store it | |
# as the last valid position | |
if pixels_new[i, j] != (0, 0, 0): | |
original_size = (i + 1, j + 1) | |
# Crop the image based on the 'valid' pixels | |
new_image = new_image.crop((0, 0, original_size[0], original_size[1])) | |
return new_image | |
@click.group() | |
def cli(): | |
pass | |
@cli.command() | |
@click.option('--img1', required=True, type=str, help='Image that will hide another image') | |
@click.option('--img2', required=True, type=str, help='Image that will be hidden') | |
@click.option('--output', required=True, type=str, help='Output image') | |
def merge(img1, img2, output): | |
merged_image = Steganography.merge(Image.open(img1), Image.open(img2)) | |
merged_image.save(output) | |
@cli.command() | |
@click.option('--img', required=True, type=str, help='Image that will be hidden') | |
@click.option('--output', required=True, type=str, help='Output image') | |
def unmerge(img, output): | |
unmerged_image = Steganography.unmerge(Image.open(img)) | |
unmerged_image.save(output) | |
if __name__ == '__main__': | |
cli() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment