Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
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))
Ejemplo n.º 11
0
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))
Ejemplo n.º 12
0
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)