def embed_image(message: IO[str], image: pygame.Surface, num_bits_modify: int) -> None: """ Mutates the given image to embed the given message within it Arguments: message -- the file object containing the wanted message image -- the pygame Surface with pixel data num_bits_modify -- the number of bits to modify in each pixel value """ if (num_bits_modify > 8): raise Error('No more than 8 bits per pixel\'s channel') elif (num_bits_modify <= 0): raise Error('Must modify at least 1 bit per pixel channel') image.lock() image_buffer = image.get_view('1') image_bytes = image_buffer.raw image_bytes_length = len(image_bytes) current_byte_index = 0 stride_size = 8 * num_bits_modify bit_mask = construct_mask(num_bits_modify) inverted_mask = construct_inverted_mask(num_bits_modify) current_message_stream = message.read(stride_size) while len(current_message_stream) > 0: if len(current_message_stream) < stride_size: current_message_stream += b'\xFF' # Grab the value from the message bits = int.from_bytes(current_message_stream[::-1], byteorder='big') i = 0 while (i * num_bits_modify) < (len(current_message_stream) * 8) + ( (len(current_message_stream) * 8) % num_bits_modify): value_to_embed = (bits & (bit_mask << (num_bits_modify * i))) >> (num_bits_modify * i) current_image_byte = image_bytes[current_byte_index] current_image_byte = (current_image_byte & inverted_mask) | value_to_embed image_buffer.write(bytes([current_image_byte]), current_byte_index) i += 1 current_byte_index += 1 if current_byte_index >= len(image_bytes): raise Exception('Message too big for image') current_message_stream = message.read(stride_size) image.unlock()
def decrypt_message(image: pygame.Surface, output_file: IO[str], num_bits: int) -> None: """ Function that looks into the image and retrieves the hidden message Arguments: image -- the pygame.Surface object holding the data output_file -- the writeable stream that we'll write the data to """ write_buffer = bytes() image_buffer = image.get_view('1') image_bytes = image_buffer.raw current_byte_index = 0 current_bits = 0 bit_mask = construct_mask(num_bits) done = False while not done and current_byte_index < len(image_bytes): for i in range(8): # because each value will only be a single byte, it doesn't matter what the byte order is retrieved_value = image_bytes[current_byte_index] & bit_mask current_bits = current_bits | (retrieved_value << (i * num_bits)) current_byte_index += 1 for i in range(num_bits): current_value = (current_bits & (0xFF << (i * 8))) >> (i * 8) current_byte = bytes([current_value]) # Check for EOT character if (current_byte == b'\xFF'): done = True break write_buffer += current_byte current_bits = 0 if output_file.write(write_buffer) != len(write_buffer): raise IOError('Unable to write the message to file') else: output_file.flush() write_buffer = bytes() output_file.flush() output_file.close()
def alpha_blending(surface1_: pygame.Surface, surface2_: pygame.Surface) -> pygame.Surface: """ Alpha blending algorithm :param surface1_: First layer texture (foreground) :param surface2_: Second layer texture (background) :return: Return a pygame surface (blend between surface1 & surface2) """ """ WIKIPEDIA Assuming that the pixel color is expressed using straight (non-premultiplied) RGBA tuples, a pixel value of (0, 0.7, 0, 0.5) implies a pixel that has 70% of the maximum green intensity and 50% opacity. If the color were fully green, its RGBA would be (0, 1, 0, 0.5). However, if this pixel uses premultiplied alpha, all of the RGB values (0, 0.7, 0) are multiplied by 0.5 and then the alpha is appended to the end to yield (0, 0.35, 0, 0.5). In this case, the 0.35 value for the G channel actually indicates 70% green intensity (with 50% opacity). Fully green would be encoded as (0, 0.5, 0, 0.5). For this reason, knowing whether a file uses straight or premultiplied alpha is essential to correctly process or composite it. Formula to apply to each pixels: OutA = SrcA + DstA x (1 - SrcA) outRGB = (SrcRGB x SrcA + DstRGB x DstA x (1 - SrcA) / ( SrcA + DstA(1 - SrcA)) if pre-multiplied alpha is used, the above equations are simplified to: outA = SrcA + DstA x (1 - SrcA) outRGB = SrcRGB + DstRGB x (1 - SrcA) If the destination background is opaque, then DSTA = 1 , and if you enter it to the upper equation: outA = 1 outRGB = SrcRGB x SrcA + DstRGB x (1 - SrcA) Surface1 is png format with alpha transparency channel (image created with alpha channel) Compatible with 32 bit only """ assert isinstance(surface1_, pygame.Surface), \ 'Expecting Surface for argument surface got %s ' % type(surface1_) assert isinstance(surface2_, pygame.Surface), \ 'Expecting Surface for argument surface2_ got %s ' % type(surface2_) # sizes w, h = surface1_.get_size() # Create a BufferProxy for surface1_ and 2 # '3' returns a (surface-width, surface-height, 3) array of RGB color components. # Each of the red, green, and blue components are unsigned bytes. # Only 24-bit and 32-bit surfaces are supported. # The color components must be in either RGB or BGR order within the pixel. buffer1 = surface1_.get_view('3') buffer2 = surface2_.get_view('3') # Extract RGB values (source -> rgb1, destination -> rgb2) rgb1 = (numpy.array(buffer1, dtype=numpy.uint8).transpose(1, 0, 2) / 255) rgb2 = (numpy.array(buffer2, dtype=numpy.uint8).transpose(1, 0, 2) / 255) # create the output array RGBA new = numpy.zeros((w, h, 4)) # ---------------- background opaque --------------------------- # Extract the alpha channels from surface1 & surface2 # alpha1 = (numpy.array(surface1_.get_view('a'), dtype=numpy.uint8).transpose(1, 0)).reshape(w, h, 1) / 255 # alpha2 = (numpy.array(surface2_.get_view('a'), dtype=numpy.uint8).transpose(1, 0)).reshape(w, h, 1) / 255 # new[:, :, 3] = 1 # outA # new[:, :, :3] = (rgb1 * alpha1 + rgb2 * alpha2 * (1 - alpha1)) # outRGB # ---------------- background partially transparent ------------ # Extract the alpha channels from surface1 & surface2 alpha1 = (numpy.array(surface1_.get_view('a'), dtype=numpy.uint8).transpose(1, 0)) / 255 alpha2 = (numpy.array(surface2_.get_view('a'), dtype=numpy.uint8).transpose(1, 0)) / 255 new[:, :, 3] = alpha1 + alpha2 * (1 - alpha1) alpha1 = alpha1.reshape(w, h, 1) alpha2 = alpha2.reshape(w, h, 1) new[:, :, :3] = (rgb1 * alpha1 + rgb2 * alpha2 * (1 - alpha1)) / (alpha1 + alpha2 * (1 - alpha1)) # De-normalization new = numpy.multiply(new, 255) # Capping all the values over 255 # numpy.putmask(new, new > 255, 255) return pygame.image.frombuffer( new.copy('C').astype(numpy.uint8), (w, h), 'RGBA')
def blend_texture_add(surface1_: pygame.Surface, surface2_: pygame.Surface, set_alpha1_: (float, numpy.ndarray), set_alpha2_: (float, numpy.ndarray), mask_: bool = False) -> pygame.Surface: """ :param surface1_: First layer texture :param surface2_: Second layer texture :param set_alpha1_: Alpha values for surface1 (can be a float or a numpy array) :param set_alpha2_: Alpha values for surface2 (can be a flaot or a numpy array) :param mask_: True | False, create a mask from surface1 (only black pixels) :return: Return a pygame surface (blend between surface1 & surface2) """ assert isinstance(surface1_, pygame.Surface), \ 'Expecting Surface for argument surface got %s ' % type(surface1_) assert isinstance(surface2_, pygame.Surface), \ 'Expecting Surface for argument surface2_ got %s ' % type(surface2_) assert isinstance(set_alpha1_, (float, numpy.ndarray)), \ 'Expecting float or numpy.ndarray for argument set_alpha1_ got %s ' % type(set_alpha1_) assert isinstance(set_alpha2_, (float, numpy.ndarray)), \ 'Expecting float for argument set_alpha2_ got %s ' % type(set_alpha2_) # sizes w, h = surface1_.get_width(), surface1_.get_height() # Create a BufferProxy for surface1_ and 2 # '3' returns a (surface-width, surface-height, 3) array of RGB color components. # Each of the red, green, and blue components are unsigned bytes. # Only 24-bit and 32-bit surfaces are supported. # The color components must be in either RGB or BGR order within the pixel. buffer1 = surface1_.get_view('3') buffer2 = surface2_.get_view('3') # Extract the alpha channel from surface1 and create # a mask (array with black pixels flagged) alpha1_ <= 0 if isinstance(mask_, bool): # Extract the surface1_ alpha channel and create a mask_ for (black pixel) alpha1_ = numpy.array(surface1_.get_view('a'), dtype=numpy.uint8).transpose(1, 0) / 255 mask_alpha1 = alpha1_ <= 0 if isinstance(set_alpha1_, float): # Create alpha channels alpha1 and alpha2 alpha1 = numpy.full((w, h, 1), set_alpha1_).transpose(1, 0, 2) elif isinstance(set_alpha1_, numpy.ndarray): alpha1 = set_alpha1_ if isinstance(set_alpha2_, float): # Create alpha channels alpha1 and alpha2 alpha2 = numpy.full((w, h, 1), set_alpha2_).transpose(1, 0, 2) elif isinstance(set_alpha2_, numpy.ndarray): alpha2 = set_alpha2_ # ------------------- pre-multiplied ------------------- # 1) create arrays representing surface1_ and surface2_, swap row and column and normalize. # 2 ) pre - multiplied alphas rgb1 = (numpy.array(buffer1, dtype=numpy.uint8).transpose(1, 0, 2) / 255) * alpha1 rgb2 = (numpy.array(buffer2, dtype=numpy.uint8).transpose(1, 0, 2) / 255) * alpha2 # create the output array RGBA new = numpy.zeros((w, h, 4)) # Calculations for RGB values -> outRGB = SrcRGB + DstRGB(1 - SrcA) new[:, :, :3] = numpy.add(rgb1, rgb2 * (1 - alpha1)) # Calculation for alpha channel -> outA = SrcA + DstA(1 - SrcA) new[:, :, 3] = numpy.add(alpha1, alpha2 * (1 - alpha1)).reshape(w, h) # ------------------- pre-multiplied ------------------- """ # ------------------- non pre-multiplied ------------------- # Formula to apply to each pixels: # outRGB = (SrcRGB x SrcA + DstRGB x DstA x (1 - SrcA) / ( SrcA + DstA(1 - SrcA)) # OutA = SrcA + DstA(1 - SrcA) rgb1 = (numpy.array(buffer1, dtype=numpy.uint8).transpose(1, 0, 2) / 255) * alpha1 rgb2 = (numpy.array(buffer2, dtype=numpy.uint8).transpose(1, 0, 2) / 255) * alpha2 new[:, :, 3] = alpha1[0] + alpha2[0] * (1 - alpha1[0]) new[:, :, :3] = rgb1 * alpha1 + rgb2 * alpha2 * (1 - alpha1) / (alpha1 + alpha2 * (1 - alpha1)) # ------------------- non pre-multiplied ------------------- """ # De-normalization new = numpy.multiply(new, 255) # Capping all the values over 255 numpy.putmask(new, new > 255, 255) # Apply the mask_ to the new surface if mask_: new[mask_alpha1] = 0 return pygame.image.frombuffer( new.copy('C').astype(numpy.uint8), (w, h), 'RGBA')
def alpha_blending_1(surface1_: pygame.Surface, surface2_: pygame.Surface) -> pygame.Surface: """ Same method than alpha_blending but much slower (looping over all pixels). :param surface1_: First layer texture (foreground) :param surface2_: Second layer texture (background) :return: Return a pygame surface (blend between surface1 & surface2) """ assert isinstance(surface1_, pygame.Surface), \ 'Expecting Surface for argument surface got %s ' % type(surface1_) assert isinstance(surface2_, pygame.Surface), \ 'Expecting Surface for argument surface2_ got %s ' % type(surface2_) # sizes w, h = surface1_.get_size() # Create a BufferProxy for surface1_ and 2 # '3' returns a (surface-width, surface-height, 3) array of RGB color components. # Each of the red, green, and blue components are unsigned bytes. # Only 24-bit and 32-bit surfaces are supported. # The color components must be in either RGB or BGR order within the pixel. buffer1 = surface1_.get_view('3') buffer2 = surface2_.get_view('3') # Extract RGB values (source -> rgb1, destination -> rgb2) rgb1 = (numpy.array(buffer1, dtype=numpy.uint8).transpose(1, 0, 2) / 255) rgb2 = (numpy.array(buffer2, dtype=numpy.uint8).transpose(1, 0, 2) / 255) # create the output array RGBA new = numpy.zeros((w, h, 4)) # Extract the alpha channels from surface1 & surface2 alpha1 = (numpy.array( surface1_.get_view('a'), dtype=numpy.uint8).transpose(1, 0)).reshape( w, h, 1) / 255 alpha2 = (numpy.array( surface2_.get_view('a'), dtype=numpy.uint8).transpose(1, 0)).reshape( w, h, 1) / 255 """ # -------------- background opaque-------------- for i in range(w): for j in range(h): rgb = (rgb1[i][j] * alpha1[i][j] + rgb2[i][j] * (1 - alpha1[i][j])) new[i][j] = (*rgb, 1) # (outRGB, outA=1) # --------------- background not opaque -------- """ for i in range(w): for j in range(h): alpha = alpha1[i][j] + alpha2[i][j] * (1 - alpha1[i][j]) assert alpha > 0, 'Incorrect alpha value, Division by zero.' rgb = (rgb1[i][j] * alpha1[i][j] + rgb2[i][j] * alpha2[i][j] * (1 - alpha1[i][j])) / alpha new[i][j] = (*rgb, alpha) # De-normalization new = numpy.multiply(new, 255) return pygame.image.frombuffer( new.copy('C').astype(numpy.uint8), (w, h), 'RGBA')