Using PIL to insert grayscale images into an RGB image by pasting grayscale values ​​into an RGB tuple

I am writing several functions, the first one inserts a grayscale bitmap image into another color bitmap. Now my goal is to take each digit of the grayscale image (e.g. 123) and replace the final digit of each RGB pixel (244, 244, 244), so that basically it will end (241, 242, 243). In essence, this is a watermark of a color image depicting shades of gray.

The following code is what I still have, I can return the values ​​of the tuple in the list, I just don’t know how to manipulate the size of a smaller image in grayscale on a large image.

def add_watermark(): image = Image.open() pixels = list(image.getdata()) image.putdata() image.save() for i in range(img.size[0]): for j in range(img.size[1]): pixels[i,j] = (i, j, 100) 

Can anyone consult?

+4
source share
2 answers

You are on the right track. This is how you manipulate pixels, although you can do it a little faster using pixel access objects , as shown below.

All this is quite simple, except for extracting and setting the correct numbers. In this example, I did this by dividing the values ​​by 10 and using the modulo operator, although there are other ways. Hope the comments explain well enough.

 from PIL import Image def add_watermark(watermark_path, image_in_path, image_out_path): # Load watermark and image and check sizes and modes watermark = Image.open(watermark_path) assert watermark.mode == 'L' image = Image.open(image_in_path) assert image.mode == 'RGB' assert watermark.size == image.size # Get pixel access objects watermark_pixels = watermark.load() image_pixels = image.load() # Watermark each pixel for x in range(image.size[0]): for y in xrange(image.size[1]): # Get the tuple of rgb values and convert to a list (mutable) rgb = list(image_pixels[x, y]) for i, p in enumerate(rgb): # Divide the watermark pixel by 100 (r), then 10 (g), then 1 (b) # Then take it modulo 10 to get the last digit watermark_digit = (watermark_pixels[x, y] / (10 ** (2 - i))) % 10 # Divide and multiply value by 10 to zero the last digit # Then add the watermark digit rgb[i] = (p / 10) * 10 + watermark_digit # Convert back to a tuple and store in the image image_pixels[x, y] = tuple(rgb) # Save the image image.save(image_out_path) 
0
source

If you are interested in watermarking images, you can take a look at steganography . As an example, Digital_Sight is a working demonstration of the concept and can be used as the basis for storing text used as a watermark. To learn how changing different pixel bits in an image can change its quality, you can play with Color_Disruptor before deciding which data to overwrite.


Digital_sight

 import cStringIO from PIL import Image import bz2 import math ################################################################################ PIXELS_PER_BLOCK = 4 BYTES_PER_BLOCK = 3 MAX_DATA_BYTES = 16777215 ################################################################################ class ByteWriter: "ByteWriter(image) -> ByteWriter instance" def __init__(self, image): "Initalize the ByteWriter internal variables." self.__width, self.__height = image.size self.__space = bytes_in_image(image) self.__pixels = image.load() def write(self, text): "Compress and write the text to the image pixels." data = bz2.compress(text) compressed_size = len(data) if compressed_size > self.__space: raise MemoryError('There is not enough space for the data!') size_data = self.__encode_size(compressed_size) tail = '\0' * ((3 - compressed_size) % 3) buffer = size_data + data + tail self.__write_buffer(buffer) @staticmethod def __encode_size(number): "Convert number into a 3-byte block for writing." data = '' for _ in range(3): number, lower = divmod(number, 256) data = chr(lower) + data return data def __write_buffer(self, buffer): "Write the buffer to the image in blocks." addr_iter = self.__make_addr_iter() data_iter = self.__make_data_iter(buffer) for trio in data_iter: self.__write_trio(trio, addr_iter.next()) def __make_addr_iter(self): "Iterate over addresses of pixels to write to." addr_group = [] for x in range(self.__width): for y in range(self.__height): addr_group.append((x, y)) if len(addr_group) == 4: yield tuple(addr_group) addr_group = [] @staticmethod def __make_data_iter(buffer): "Iterate over the buffer a block at a time." if len(buffer) % 3 != 0: raise ValueError('Buffer has a bad size!') data = '' for char in buffer: data += char if len(data) == 3: yield data data = '' def __write_trio(self, trio, addrs): "Write a 3-byte block to the pixels addresses given." duo_iter = self.__make_duo_iter(trio) tri_iter = self.__make_tri_iter(duo_iter) for (r_duo, g_duo, b_duo), addr in zip(tri_iter, addrs): r, g, b, a = self.__pixels[addr] r = self.__set_two_bits(r, r_duo) g = self.__set_two_bits(g, g_duo) b = self.__set_two_bits(b, b_duo) self.__pixels[addr] = r, g, b, a @staticmethod def __make_duo_iter(trio): "Iterate over 2-bits that need to be written." for char in trio: byte = ord(char) duos = [] for _ in range(4): byte, duo = divmod(byte, 4) duos.append(duo) for duo in reversed(duos): yield duo @staticmethod def __make_tri_iter(duo_iter): "Group bits into their pixel units for writing." group = [] for duo in duo_iter: group.append(duo) if len(group) == 3: yield tuple(group) group = [] @staticmethod def __set_two_bits(byte, duo): "Write a duo (2-bit) group to a pixel channel (RGB)." if duo > 3: raise ValueError('Duo bits has to high of a value!') byte &= 252 byte |= duo return byte ################################################################################ class ByteReader: "ByteReader(image) -> ByteReader instance" def __init__(self, image): "Initalize the ByteReader internal variables." self.__width, self.__height = image.size self.__pixels = image.load() def read(self): "Read data out of a picture, decompress the data, and return it." compressed_data = '' addr_iter = self.__make_addr_iter() size_block = self.__read_blocks(addr_iter) size_value = self.__block_to_number(size_block) blocks_to_read = math.ceil(size_value / 3.0) for _ in range(blocks_to_read): compressed_data += self.__read_blocks(addr_iter) if len(compressed_data) != blocks_to_read * 3: raise ValueError('Blocks were not read correctly!') if len(compressed_data) > size_value: compressed_data = compressed_data[:size_value] return bz2.decompress(compressed_data) def __make_addr_iter(self): "Iterate over the pixel addresses in the image." addr_group = [] for x in range(self.__width): for y in range(self.__height): addr_group.append((x, y)) if len(addr_group) == 4: yield tuple(addr_group) addr_group = [] def __read_blocks(self, addr_iter): "Read data a block at a time (4 pixels for 3 bytes)." pixels = [] for addr in addr_iter.next(): pixels.append(self.__pixels[addr]) duos = self.__get_pixel_duos(pixels) data = '' buffer = [] for duo in duos: buffer.append(duo) if len(buffer) == 4: value = 0 for duo in buffer: value <<= 2 value |= duo data += chr(value) buffer = [] if len(data) != 3: raise ValueError('Data was not decoded properly!') return data @classmethod def __get_pixel_duos(cls, pixels): "Extract bits from a given group of pixels." duos = [] for pixel in pixels: duos.extend(cls.__extract_duos(pixel)) return duos @staticmethod def __extract_duos(pixel): "Retrieve the bits stored in a pixel." r, g, b, a = pixel return r & 3, g & 3, b & 3 @staticmethod def __block_to_number(block): "Convert a block into a number (size of data buffer)." value = 0 for char in block: value <<= 8 value |= ord(char) return value ################################################################################ def main(picture, mode, text): "Dispatch the various operations that can be requested." image = Image.open(picture) if image.mode != 'RGBA': image = image.convert('RGBA') if mode == 'Evaluate': evaluate(image) elif mode == 'Simulate': simulate(image, text) elif mode == 'Encode': encode(image, text) elif mode == 'Decode': decode(image) else: raise ValueError('Mode %r was not recognized!' % mode) ################################################################################ def evaluate(image): "Display the number of bytes available in the image." print 'Usable bytes available =', bytes_in_image(image) def bytes_in_image(image): "Calculate the number of usable bytes in an image." blocks = blocks_in_image(image) usable_blocks = blocks - 1 usable_bytes = usable_blocks * BYTES_PER_BLOCK return min(usable_bytes, MAX_DATA_BYTES) def blocks_in_image(image): "Find out how many blocks are in an image." width, height = image.size pixels = width * height blocks = pixels / PIXELS_PER_BLOCK return blocks ################################################################################ def simulate(image, text): "Find out how much space the text takes in the image that was given." compressed_data = bz2.compress(text) compressed_size = len(compressed_data) usable_bytes = bytes_in_image(image) space_leftover = usable_bytes - compressed_size if space_leftover > 0: print 'You still have %s more bytes for storage.' % space_leftover elif space_leftover < 0: print 'You overfilled the image by %s bytes.' % -space_leftover else: print 'This is a perfect fit!' ################################################################################ def encode(image, text): "Encodes text in image and returns picture to the browser." mutator = ByteWriter(image) mutator.write(text) output = cStringIO.StringIO() image.save(output, 'PNG') output.seek(0) print 'Content-Type: image/PNG' print output.read() ################################################################################ def decode(image): "Extract the original message and deliver it to the client." accessor = ByteReader(image) buffer = accessor.read() print buffer ################################################################################ if __name__ == '__builtin__': try: main(cStringIO.StringIO(PICTURE), MODE, TEXT) except SystemExit: pass 

Color_disruptor

 from cStringIO import StringIO from PIL import Image from random import randrange def main(data, r_bits, g_bits, b_bits, a_bits): image = Image.open(data) if image.mode != 'RGBA': image = image.convert('RGBA') width, height = image.size array = image.load() data.close() for x in range(width): for y in range(height): r, g, b, a = array[x, y] r ^= randrange(r_bits) g ^= randrange(g_bits) b ^= randrange(b_bits) a ^= randrange(a_bits) array[x, y] = r, g, b, a data = StringIO() image.save(data, 'PNG') print 'Content-Type: image/PNG' print data.getvalue() if __name__ == '__builtin__': main(StringIO(DATA), *map(lambda bits: 1 << int(bits), (R, G, B, A))) 
0
source

Source: https://habr.com/ru/post/1480027/


All Articles