Last active
March 22, 2022 00:53
-
-
Save Ge0rg3/af58b802b9f8c3147a726a558f8898e3 to your computer and use it in GitHub Desktop.
Python Pixel Value Differencing test implementation. Can iterate via ROW/COL, and optionally in zigzag.
This file contains 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
import numpy as np | |
import math | |
from PIL import Image | |
img = "pvd_test.png" | |
RANGES = [8, 8, 16, 32, 64, 128] | |
SECRET_STRING = "HELLO WORLD "*50 | |
# Generate range table | |
RANGE_TABLE = [] | |
start = 0 | |
for _range in RANGES: | |
RANGE_TABLE.append((start, start + _range - 1)) | |
start += _range | |
# Load pixels | |
image = Image.open(img) | |
width, height = image.size | |
pixels_array = list(image.getdata()) | |
def get_bits(difference): | |
""" | |
Determine the number of embeddable/extractable bits from a difference | |
between 2 pixels, based off the range table values. | |
""" | |
for min_range, max_range in RANGE_TABLE: | |
if min_range <= difference and max_range >= difference: | |
embeddable_bit_count = int(math.log2(max_range+1 - min_range)) | |
return (min_range, embeddable_bit_count) | |
def string_to_binary(input_string): | |
return ''.join(format(ord(x), 'b').zfill(8) for x in input_string) | |
first_in_pair = True | |
previous_index = 0 | |
secret_position = 0 | |
secret_binary = string_to_binary(SECRET_STRING) | |
# Iteration process begins | |
order = "ROW" | |
zigzag = True | |
zigzag_start = "LEFT" | |
start_a = 0 | |
start_b = 0 | |
step_a = 1 | |
step_b = 1 | |
if order == "ROW": | |
end_a = height | |
end_b = width | |
else: | |
end_a = width | |
end_b = height | |
for a in range(start_a, end_a, step_a): | |
if zigzag: | |
if zigzag_start == "RIGHT": | |
zigzag_flip = 0 | |
elif zigzag_start == "LEFT": | |
zigzag_flip = 1 | |
if a % 2 == zigzag_flip: | |
start_b = end_b - 1 | |
end_b = -1 | |
step_b = -1 | |
else: | |
start_b = 0 | |
step_b = 1 | |
if order == "ROW": | |
end_b = width | |
else: | |
end_b = height | |
for b in range(start_b, end_b, step_b): | |
if order == "ROW": | |
index = (a * width) + b | |
else: | |
index = (b * width) + a | |
# Iteration process over! | |
if first_in_pair: | |
previous_index = index | |
first_in_pair = False | |
else: | |
# Get difference between two pixel values | |
pixel_difference = pixels_array[previous_index] - pixels_array[index] | |
# Find the minimum range and number of embeddable bits from range table | |
min_range, bit_count = get_bits(abs(pixel_difference)) | |
# Calculate which int to add to min range for given embeddable bit count | |
bits_to_embed = secret_binary[secret_position:secret_position+bit_count] | |
int_to_embed = int(bits_to_embed, 2) | |
# Find the new difference that will be used between the new pixels | |
new_pixel_difference = min_range + int_to_embed | |
# Change pixel values to fit the new difference (algorithm sourced directly from Wu-Tsai paper) | |
if pixel_difference < 0: | |
m = pixel_difference - (new_pixel_difference * -1) | |
else: | |
m = pixel_difference - new_pixel_difference | |
m /= 2 | |
if pixel_difference % 2 == 0: | |
pixels_array[previous_index] -= math.floor(m) | |
pixels_array[index] += math.ceil(m) | |
else: | |
pixels_array[previous_index] -= math.ceil(m) | |
pixels_array[index] += math.floor(m) | |
secret_position += bit_count | |
first_in_pair = True | |
if secret_position >= len(secret_binary): | |
break | |
if secret_position >= len(secret_binary): | |
break | |
pixels_array = np.array(pixels_array) | |
mat = np.reshape(pixels_array, (height, width)) | |
img = Image.fromarray(np.uint8(mat), 'L') | |
img.save("embedded.png") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment