Here's one way to embed data into the least significant bit of each colour channel of the pixels in a 8 bit per channel RGB image file, using PIL to do the image handling.
The code below illustrates bit stream handling in Python. It's reasonably efficient (as far as such operations can be efficient in Python), but it sacrifices efficiency for readability & simplicity of use when necessary. :)
#! /usr/bin/env python
''' Steganography with PIL (really Pillow)
Encodes / decodes bits of a binary data file into the LSB of each color
value of each pixel of a non-palette-mapped image.
Written by PM 2Ring 2015.02.03
'''
import sys
import getopt
import struct
from PIL import Image
def readbits(bytes):
''' Generate single bits from bytearray '''
r = range(7, -1, -1)
for n in bytes:
for m in r:
yield (n>>m) & 1
def encode(image_bytes, mode, size, dname, oname):
print 'Encoding...'
with open(dname, 'rb') as dfile:
payload = bytearray(dfile.read())
#Prepend encoded data length to payload
datalen = len(payload)
print 'Data length:', datalen
#datalen = bytearray.fromhex(u'%06x' % datalen)
datalen = bytearray(struct.pack('>L', datalen)[1:])
payload = datalen + payload
databits = readbits(payload)
for i, b in enumerate(databits):
image_bytes[i] = (image_bytes[i] & 0xfe) | b
img = Image.frombytes(mode, size, str(image_bytes))
img.save(oname)
def bin8(i):
return bin(i)[2:].zfill(8)
bit_dict = dict((tuple(int(c) for c in bin8(i)), i) for i in xrange(256))
def decode_bytes(data):
return [bit_dict[t] for t in zip(*[iter(c&1 for c in data)] * 8)]
def decode(image_bytes, dname):
print 'Decoding...'
t = decode_bytes(image_bytes[:24])
datalen = (t[0] << 16) | (t[1] << 8) | t[2]
print 'Data length:', datalen
t = decode_bytes(image_bytes[24:24 + 8*datalen])
with open(dname, 'wb') as dfile:
dfile.write(str(bytearray(t)))
def process(iname, dname, oname):
with Image.open(iname) as img:
mode = img.mode
if mode == 'P':
raise ValueError, '%s is a palette-mapped image' % fname
size = img.size
image_bytes = bytearray(img.tobytes())
#del img
print 'Data capacity:', len(image_bytes) // 8 - 24
if oname:
encode(image_bytes, mode, size, dname, oname)
elif dname:
decode(image_bytes, dname)
def main():
#input image filename
iname = None
#data filename
dname = None
#output image filename
oname = None
def usage(msg=None):
s = msg + '
' if msg else ''
s += '''Embed data into or extract data from the low-order bits of an image file.
Usage:
%s [-h] -i input_image [-d data_file] [-o output_image]
To encode, you must specify all 3 file names.
To decode, just specify the input image and the data file names.
If only the the input image is given, its capacity will be printed,
i.e., the maximum size (in bytes) of data that it can hold.
Uses PIL (Pillow) to read and write the image data.
Do NOT use lossy image formats for output, eg JPEG, or the data WILL get scrambled.
The program will abort if the input image is palette-mapped, as such images
are not suitable.
'''
print >>sys.stderr, s % sys.argv[0]
raise SystemExit, msg!=None
try:
opts, args = getopt.getopt(sys.argv[1:], "hi:d:o:")
except getopt.GetoptError, e:
usage(e.msg)
for o, a in opts:
if o == '-h': usage(None)
elif o == '-i': iname = a
elif o == '-d': dname = a
elif o == '-o': oname = a
if iname:
print 'Input image:', iname
else:
usage('No input image specified!')
if dname:
print 'Data file:', dname
if oname:
print 'Output image:', oname
process(iname, dname, oname)
if __name__ == '__main__':
main()