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')
예제 #4
0
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')