def tagger(image,position): new_image = [] row_n = 0 for row in image: row_bits = bits.message_to_bits(str(row_n)) i = 0 new_row = [] # Nested 'for' loop iterates over each pixel in the current row-list # (r,g,b) matches r,g,b to the three intenstiy values in each pixel # respectively, so r = first intensity value (red), and so on. for (r,g,b) in row: # Use codebit function to set the LSB of the # new intensity values to match the bit being indexed new_r = bits.set_bit(r,bits.codebit(i,row_bits),position) new_g = bits.set_bit(g,bits.codebit(i + 1,row_bits),position) new_b = bits.set_bit(b,bits.codebit(i + 2,row_bits),position) # Amalgamate the three new intensity values into a new pixel new_pixel = (new_r,new_g,new_b) # Append new pixel to new row-list new_row.append(new_pixel) # increment counter by 3, as each pixel has 3 intensity values i += 3 # attach new row-list to new image-list new_image.append(new_row) row_n += 1 return new_image
def encode(image,message): """ conducts the encoding of the message, according to the method described in the header comments. Takes an image and a message and returns a new image which is encoded with the message. Arguments: image -- the image to be encoded. Image must be in the format generated by the SimpleImage module (i.e. a list of lists of tuples, where each tuple represents a pixel) message -- the string to be encoded in the message """ binarymsg = message_to_bits(message) output = [] index = 0 current_row = 0 for row in image: output.append([]) for pixel in row: new_pixel = [] for intensity in pixel: # check if there is any message left to be encoded if index < len(binarymsg): new_pixel.append(bits.set_bit(int(intensity),\ binarymsg[index],0)) index += 1 else: new_pixel.append(bits.set_bit(int(intensity),0,0)) output[current_row].append(tuple(new_pixel)) current_row+=1 return output
def decode(infile): """ Decode the data from a given file or file-like object. :param infile: File path or file-like object. :return: The encoded data as a byte array. """ encoded_image = Image.open(infile) # If this is a multi-channel image, the call to getdata() will return # an array of tuples for the pixel data. If this is a single-channel # image, we just get an array of pixel values. To normalize this, we # will try to flatten the array first. If this throws a TypeError, we # can just assume we have a single-channel image. try: encoded_data = encoded_image.getdata() encoded_data = itertools.chain.from_iterable(encoded_data) encoded_data = bytearray(encoded_data) except TypeError: encoded_data = encoded_image.getdata() encoded_data = bytearray(encoded_data) decoded_byte = 0 decoded_bytes = bytearray() current_byte = 0 # Find the size of the encoded data from the first N bytes of the image size_bytes = struct.calcsize('l') * 8 for i in xrange(size_bytes): if bits.is_bit_set(encoded_data[current_byte], 0): decoded_byte = bits.set_bit(decoded_byte, 7 - (i % 8)) else: decoded_byte = bits.unset_bit(decoded_byte, 7 - (i % 8)) if 7 - (i % 8) == 0: decoded_bytes.append(decoded_byte) current_byte += 1 decoded_size, = struct.unpack('l', str(decoded_bytes)) decoded_size *= 8 decoded_bytes = bytearray() decoded_byte = 0 # Decode the original datas for i in xrange(decoded_size): if bits.is_bit_set(encoded_data[current_byte], 0): decoded_byte = bits.set_bit(decoded_byte, 7 - (i % 8)) else: decoded_byte = bits.unset_bit(decoded_byte, 7 - (i % 8)) if 7 - (i % 8) == 0: decoded_bytes.append(decoded_byte) current_byte += 1 return decoded_bytes
def change_lsb(image,message_bit): new_image = [] #Defining the new image list. for row in image: new_row = [] for a in range(len(image[0])): pixel = list(row[a]) #The tuple consisiting of the intensity values of a pixel #is converted to a list. This is done to be able to mutate #the list since tuples are immutable. for b in range(len(image[0][0])): pixel[b] = bits.set_bit(pixel[b], message_bit[0], 0) #The set_bit function from the bits.py module is called #here and it sets the LSB of the intensity value equal to #the first character of message_bit. message_bit = message_bit[1:] #message_bit is sliced i.e the first character is removed #after it has been set equal to the LSB of an intensity value. new_row.append(tuple(pixel)) #A pixel is represented as a tuple in the image which itself #is a 2 dimensional list. Hence, the mutated list is converted #to a tuple and appended to the new_row list. new_image.append(new_row) #For every row in the original image, the new_row is appended to #the new_image list. return new_image
def change_lsb_ext(image, message_bit, num_bits): new_image = [] for row in image: new_row = [] for a in range(len(image[0])): pixel = list(row[a]) for b in range(len(image[0][0])): for count in range(num_bits): if message_bit == '': break else: pixel[b] = bits.set_bit(pixel[b], message_bit[0], count) message_bit = message_bit[1:] new_row.append(tuple(pixel)) new_image.append(new_row) return new_image
def insert(carrier_obj, message): message_numbers = [ord(c) for c in message ] # Get int representation of each char in message # Determine where colors are stored and byte_step if carrier_obj.color_type == 2 or carrier_obj.color_type == 6: # TrueColor OR TrueColor + Alpha (type 6) chunk_to_use = b'IDAT' else: # Indexed chunk_to_use = b'PLTE' modify_chunk_indexes, chunks_to_modify = carrier_obj.get_chunk_by_type( chunk_to_use, bool_return_index=True) # Since the get_chunk_by_type function can return singular objects, put singular return values into lists # for compatibility with the rest of the function if isinstance(chunks_to_modify, list) is False: modify_chunk_indexes = [modify_chunk_indexes] chunks_to_modify = [chunks_to_modify] total_avail_bytes = sum([chunk.int_size() for chunk in chunks_to_modify]) # FIXME: pretty sure this limit is broken, but it's currently not a priority if len(message_numbers ) * 4 > total_avail_bytes: # one character takes up 4 raise RuntimeError('Message too big given chunk sizes, sorry') else: # Determine spread -- assuming 1-Byte per character percent_const = int(total_avail_bytes // ((4 * len(message_numbers)) + 1)) # Given the actual chunk index we start at, determine the index of that chunk in these sublists counter = 1 for num_index, num in enumerate( message_numbers): # for each character in the secret message for char_bit_offset in range( 7, -1, -2): # starting from most sig bit in num to least # Index of which color value we're looking at chunk_list_index = determine_chunk_index(chunks_to_modify, percent_const * counter) byte_index = determine_byte_index(chunks_to_modify, percent_const * counter) current_chunk_index = modify_chunk_indexes[ chunk_list_index] # Index of chunk to modify in PNG.chunks current_chunk = chunks_to_modify[ chunk_list_index] # actual Chunk we want to modify # current RGB(A) value of interest current_color_value = current_chunk.data[byte_index] lsb_str = '' # first bit setup if test_bit(num, char_bit_offset) == 0: value_to_set = clear_bit(current_color_value, 1) lsb_str += '0' else: value_to_set = set_bit(current_color_value, 1) lsb_str += '1' # second bit setup if test_bit(num, char_bit_offset - 1) == 0: value_to_set = clear_bit(value_to_set, 0) lsb_str += '0' else: value_to_set = set_bit(value_to_set, 0) lsb_str += '1' if VERBOSE is True: print('Current LSB: {}'.format(lsb_str)) print("Modifying chunk {}, index {} from {} to {}".format( current_chunk_index, byte_index, get_bin(current_color_value), get_bin(value_to_set))) print('') # Change palette value, modifying the two least significant bits carrier_obj.set_value_at_index(current_chunk_index, byte_index, value_to_set) counter += 1 # FIXME: Storing message length in size of IEND chunk is stupid easy to detect # To keep track of the secret message's length, the message length is stored in the size of the IEND chunk message_byte_len = len(message_numbers).to_bytes(4, byteorder='big') iend_index, iend_chunk = carrier_obj.get_chunk_by_type( b'IEND', bool_return_index=True) carrier_obj.chunks[iend_index].size = message_byte_len return carrier_obj
def extract(carrier_obj): secret_size = carrier_obj.get_chunk_by_type(b'IEND').int_size() chars = [0] * secret_size # Determine where colors are stored and byte_step if carrier_obj.color_type == 2: # TrueColor chunk_to_use = b'IDAT' elif carrier_obj.color_type == 3: # Indexed chunk_to_use = b'PLTE' else: # TrueColor + Alpha (type 6) chunk_to_use = b'IDAT' use_chunk_index, use_chunk = carrier_obj.get_chunk_by_type( chunk_to_use, bool_return_index=True) if isinstance(use_chunk, list) is False: use_chunk_index = [use_chunk_index] use_chunk = [use_chunk] total_avail_bytes = sum([chunk.int_size() for chunk in use_chunk]) # Determine spread -- assuming 1-Byte per character percent_const = int(total_avail_bytes // ((4 * secret_size) + 1)) counter = 1 for char_index in range( 0, secret_size): # for each character in the secret message for char_bit_offset in range( 7, -1, -2): # starting from most sig bit in num to least # Index of which color value we're looking at chunk_list_index = determine_chunk_index(use_chunk, percent_const * counter) byte_index = determine_byte_index(use_chunk, percent_const * counter) current_chunk_index = use_chunk_index[chunk_list_index] current_chunk = use_chunk[chunk_list_index] if VERBOSE is True: print("Checking chunk {}, index {}".format( current_chunk_index, byte_index)) # current RGB value of interest current_color_value = current_chunk.data[byte_index] # first bit setup if test_bit(current_color_value, 1) == 0: value_to_set = clear_bit(chars[char_index], char_bit_offset) else: value_to_set = set_bit(chars[char_index], char_bit_offset) # second bit setup if test_bit(current_color_value, 0) == 0: value_to_set = clear_bit(value_to_set, char_bit_offset - 1) else: value_to_set = set_bit(value_to_set, char_bit_offset - 1) # Change palette value, modifying the two least significant bits chars[char_index] = value_to_set counter += 1 str_return = b''.join([str.encode(chr(n), ENCODING) for n in chars]) return str_return
def encode(image,message): """ This function takes an image as its first argument, a message as its second argument, and returns an encoded image as its result. The input image is a rectangular two-dimensional list of pixels, in zero-based row-major order. Each pixel is a 3-element tuple of integers in the range [0,255]. Input message is a string of ASCII characters. Encoding process: 1: Convert the ASCII message into a string of binary digits using message_to_bits function 2: Take the first intensity value (red intenstiy, of top-left pixel) convert it into binary, and generate a new BINARY intenstiy value exactly the same as the original, except change THE LEAST SIGNIFICANT BIT (LSB) of this number to match the FIRST bit of the message bitstream. 3: Repeat the above step for intenstiy value 2, ( green intensity of top-left pixel), for second bitstream bit, and same for third intenstiy value, so you now have three new 'encoded' intenstiy values. 4: Put these values into a NEW three elemnt tuple, i.e an encoded PIXEL, then put this new pixel into a new row-list. 5: Repeat steps 2-4 for the entire first row-list of the original image, to generate an encoded row-list of tuples almost identical to the original one, except for having (possibly) different LSBs for each intensity value. Add this row-list to a new image-list, i.e. the encoded image. 6: Repeat 5 for each row-list of the original image, to build up an entirely new, encoded image-list which the encode function will return as its result. General cases: - NOT ENOUGH INTENSTIY VALUES TO ENCODE ENTIRE MESSAGE Program will encode as much of the message as possible - EXTRA INTENSTIY VALUES LEFTOVER Program will change the LSB of all 'unencoded' intensity values to 0 Inputs: image: a two-dimensional list of pixels, in zero-based row-major order. Each pixel is a three element tuple of integers in the range [0,255]. message: a string of ASCII characters Result: new_image: a new image in the same format as the original, with the same dimensions, but with the message coded into the LSBs of the intenstiy values in each pixel. Examples: >>> test_image = [[(15,103,255),(0,3,19)],[(22,200,1),(8,8,8)], [(0,0,0),(5,123,19)]] >>> encode(test_image, '') [[(14, 102, 254), (0, 2, 18)], [(22, 200, 0), (8, 8, 8)], [(0, 0, 0), (4, 122, 18)]] >>> encode(test_image, 'hello') [[(14, 103, 255), (0, 3, 18)], [(22, 200, 0), (9, 9, 8)], [(0, 1, 0), (5, 122, 19)]] >>> encode([], 'hello') [] """ bitstream = bits.message_to_bits(message) new_image = [] i = 0 # Set counter i to keep track of index location in bitstream # The ith bit is encoded for row in image: # First 'for' loop iterates over each row-list # Empty list intialised for each new row-list new_row = [] # Nested 'for' loop iterates over each pixel in the current row-list # (r,g,b) matches r,g,b to the three intenstiy values in each pixel # respectively, so r = first intensity value (red), and so on. for (r,g,b) in row: # Use codebit function to set the LSB of the # new intensity values to match the bit being indexed new_r = bits.set_bit(r,bits.codebit(i,bitstream),0) new_g = bits.set_bit(g,bits.codebit(i + 1,bitstream),0) new_b = bits.set_bit(b,bits.codebit(i + 2,bitstream),0) # Amalgamate the three new intensity values into a new pixel new_pixel = (new_r,new_g,new_b) # Append new pixel to new row-list new_row.append(new_pixel) # increment counter by 3, as each pixel has 3 intensity values i += 3 # attach new row-list to new image-list new_image.append(new_row) return new_image
def encode_ext(image, message, num_bits): """ Essentially the same as the encode function, but: - in the original encode function, only the LSB was used to encode the message - in this function, an extra input, num_bits is introduced, which controls how many bits per 8-bit chunk are used to encode the message, up to using all 8 bits. Inputs: image: a two-dimensional list of pixels, in zero-based row-major order. Each pixel is a three element tuple of integers in the range [0,255]. message: a string of ASCII characters num_bits: integer value between 1 and 8 inclusive, which determines how many bits are used to encode the message, starting with the LSB. Result: new_image: a new image in the same format as the original, with the same dimensions, but with the message coded into the intenstiy values in each pixel. Number of bits used depends on value of num_bits. Examples: >>> encode_ext(test_image,'',4) [[(0, 96, 240), (0, 0, 16)], [(16, 192, 0), (0, 0, 0)], [(0, 0, 0), (0, 112, 16)]] >>> encode_ext(test_image,'',8) [[(0, 0, 0), (0, 0, 0)], [(0, 0, 0), (0, 0, 0)], [(0, 0, 0), (0, 0, 0)]] >>> encode_ext(test_image,'hello',4) [[(6, 97, 246), (10, 6, 19)], [(22, 195, 6), (15, 0, 0)], [(0, 0, 0), (0, 112, 16)]] >>> encode_ext(test_image,'hello',8) [[(22, 166, 54), (54, 246, 0)], [(0, 0, 0), (0, 0, 0)], [(0, 0, 0), (0, 0, 0)]] """ # Check num_bits is within accepted range if not(0 < num_bits <= 8): print ('Number of bits must be an integer between 1 and 8\ inclusive') return None bitstream = bits.message_to_bits(message) new_image = [] i = 0 for row in image: new_row = [] for pixel in row: pre_pixel = [] # Iterate over each intenstiy value in each pixel, # instead of iterating one pixel at a time for intensity in pixel: # need to set a seperate variable, as this may # be iterated over multiple times by next 'for' # loop new_i = intensity for pos in range(num_bits): # bit position increases after each loop, # until (num_bits -1) is reached new_i = bits.set_bit(new_i,bits.codebit(i,bitstream),pos) # only need to increment by one, as we are now only # setting one bit per loop i += 1 # append each intensity value to a list; # tuples cannot be used as they are immutable pre_pixel.append(new_i) # convert full pixel into three-element tuple new_pixel = (pre_pixel[0],pre_pixel[1],pre_pixel[2]) new_row.append(new_pixel) new_image.append(new_row) return new_image
from PIL import Image import numpy as np import sys, bits src_img = Image.open(sys.argv[1]) pixels = np.array(src_img) out_buffer = [] x = 0 y = 0 for i in range(int(sys.argv[2])): buffer = 0 for j in range(8): pixel = pixels[x][y] bit = bits.get_bit(pixel[0], bits.LSB) buffer = bits.set_bit(buffer, j, bit) x += 1 if x >= src_img.width: x = 0 y += 1 out_buffer.append(buffer) fh = open(sys.argv[3], "wb") fh.write(bytes(out_buffer))
import sys, bits def array_image_save(array, image_path): image = Image.fromarray(array) image.save(image_path) src_img = Image.open(sys.argv[1]) pixels = np.array(src_img) fh = open(sys.argv[2], "rb") binary_data = fh.read() x = 0 y = 0 for i in range(len(binary_data)): buffer = binary_data[i] for j in range(8): pixel = pixels[x][y] bit = bits.get_bit(buffer, j) pixel[0] = bits.set_bit(pixel[0], bits.LSB, bit) pixels[x][y] = pixel x += 1 if x >= src_img.width: x = 0 y += 1 array_image_save(pixels, sys.argv[3]) print("Encoded %s bytes" % len(binary_data))
def encode(data, infile, outfile): """ Encode the given data in a new copy of a given image file. :param data: Data to be encoded. This is typically a bytearray. :param infile: The original image to use. This can be either a file name or an open file object. :param outfile: The new image to create. This can be either a file name of an open file object. """ base_image = Image.open(infile) # If this is a multi-channel image, the call to getdata() will return # an array of tuples for the pixel data. If this is a single-channel # image, we just get an array of pixel values. To normalize this, we # will try to flatten the array first. If this throws a TypeError, we # can just assume we have a single-channel image. try: composite_data = base_image.getdata() composite_data = itertools.chain.from_iterable(composite_data) composite_data = bytearray(composite_data) except TypeError: composite_data = base_image.getdata() composite_data = bytearray(composite_data) # Build a simple header that will hold the size of the encoded data. # We use a structure here to make sure the header size itself remains # consistent. encoding_header = struct.pack('l', len(data)) # Prepend the header to the original data full_data = bytearray(encoding_header) full_data.extend(bytearray(data)) current_byte = 0 # Iterate over each byte in the input data. for data_byte in full_data: # Iterate over each bit in the input data byte in high-to-low order. for bit_shift in xrange(7, -1, -1): # If the current input bit is set, set the low bit in the current byte # of the base image. Similarly, if the bit is unset, then unset the low # bit of the current image byte. if bits.is_bit_set(data_byte, bit_shift): composite_data[current_byte] = bits.set_bit(composite_data[current_byte], 0) else: composite_data[current_byte] = bits.unset_bit(composite_data[current_byte], 0) # Go on to the next byte in the base image current_byte += 1 # Re-encode the data into a new image data_image_size = base_image.size composite_image = Image.frombytes(base_image.mode, data_image_size, str(composite_data)) # Save the image to the supplied output file composite_image.save(outfile, format=base_image.format)