Using Pil To Insert Greyscale Image Into Rgb Image By Inserting Greyscale Values In Rgb Tuple
Solution 1:
You're on the right track. That's how you manipulate pixels, though you can do it a little faster using pixel access objects like I've shown below.
It's all pretty straightforward except for extracting and setting the correct digits. In this example, I've done that by dividing by powers of 10 and by using the modulo operator, though there are other ways. Hopefully the comments explain it well enough.
from PIL import Image
defadd_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 pixelfor x inrange(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 inenumerate(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)
Solution 2:
If you are interested in watermarking images, you might want to take a look at steganography. As an example, Digital_Sight is a working demonstration of the concept and could be used as a basis for storing text used as a watermark. To study how modifying various pixel-bits in an image can alter its quality, you might want to play around with Color_Disruptor before deciding what 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################################################################################classByteWriter:
"ByteWriter(image) -> ByteWriter instance"def__init__(self, image):
"Initalize the ByteWriter's internal variables."
self.__width, self.__height = image.size
self.__space = bytes_in_image(image)
self.__pixels = image.load()
defwrite(self, text):
"Compress and write the text to the image's 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)
@staticmethoddef__encode_size(number):
"Convert number into a 3-byte block for writing."
data = ''for _ inrange(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 inrange(self.__width):
for y inrange(self.__height):
addr_group.append((x, y))
iflen(addr_group) == 4:
yieldtuple(addr_group)
addr_group = []
@staticmethoddef__make_data_iter(buffer):
"Iterate over the buffer a block at a time."iflen(buffer) % 3 != 0:
raise ValueError('Buffer has a bad size!')
data = ''for char in buffer:
data += char
iflen(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 inzip(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
@staticmethoddef__make_duo_iter(trio):
"Iterate over 2-bits that need to be written."for char in trio:
byte = ord(char)
duos = []
for _ inrange(4):
byte, duo = divmod(byte, 4)
duos.append(duo)
for duo inreversed(duos):
yield duo
@staticmethoddef__make_tri_iter(duo_iter):
"Group bits into their pixel units for writing."
group = []
for duo in duo_iter:
group.append(duo)
iflen(group) == 3:
yieldtuple(group)
group = []
@staticmethoddef__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
################################################################################classByteReader:
"ByteReader(image) -> ByteReader instance"def__init__(self, image):
"Initalize the ByteReader's internal variables."
self.__width, self.__height = image.size
self.__pixels = image.load()
defread(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 _ inrange(blocks_to_read):
compressed_data += self.__read_blocks(addr_iter)
iflen(compressed_data) != blocks_to_read * 3:
raise ValueError('Blocks were not read correctly!')
iflen(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 inrange(self.__width):
for y inrange(self.__height):
addr_group.append((x, y))
iflen(addr_group) == 4:
yieldtuple(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)
iflen(buffer) == 4:
value = 0for duo in buffer:
value <<= 2
value |= duo
data += chr(value)
buffer = []
iflen(data) != 3:
raise ValueError('Data was not decoded properly!')
return data
@classmethoddef__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
@staticmethoddef__extract_duos(pixel):
"Retrieve the bits stored in a pixel."
r, g, b, a = pixel
return r & 3, g & 3, b & 3 @staticmethoddef__block_to_number(block):
"Convert a block into a number (size of data buffer)."
value = 0for char in block:
value <<= 8
value |= ord(char)
return value
################################################################################defmain(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)
################################################################################defevaluate(image):
"Display the number of bytes available in the image."print'Usable bytes available =', bytes_in_image(image)
defbytes_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
returnmin(usable_bytes, MAX_DATA_BYTES)
defblocks_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
################################################################################defsimulate(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!'################################################################################defencode(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()
################################################################################defdecode(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)))
Post a Comment for "Using Pil To Insert Greyscale Image Into Rgb Image By Inserting Greyscale Values In Rgb Tuple"