Копирование другого моего ответа на другой вопрос:
Вот старый не слишком необычный модуль, который я однажды сделал для друга (код Python 2.x):
код
from __future__ import division
import math, os, array, random
import itertools as it
import Image as I
import sys
def encode(txtfn, imgfn):
with open(txtfn, "rb") as ifp:
txtdata= ifp.read()
txtdata= txtdata.encode('zip')
img= I.open(imgfn).convert("RGB")
pixelcount= img.size[0]*img.size[1]
## sys.stderr.write("image %dx%d\n" % img.size)
factor= len(txtdata) / pixelcount
width= int(math.ceil(img.size[0]*factor**.5))
height= int(math.ceil(img.size[1]*factor**.5))
pixelcount= width * height
if pixelcount < len(txtdata): # just a sanity check
sys.stderr.write("phase 2, %d bytes in %d pixels?\n" % (len(txtdata), pixelcount))
sys.exit(1)
## sys.stderr.write("%d bytes in %d pixels (%dx%d)\n" % (len(txtdata), pixelcount, width, height))
img= img.resize( (width, height), I.ANTIALIAS)
txtarr= array.array('B')
txtarr.fromstring(txtdata)
txtarr.extend(random.randrange(256) for x in xrange(len(txtdata) - pixelcount))
newimg= img.copy()
newimg.putdata([
(
r & 0xf8 |(c & 0xe0)>>5,
g & 0xfc |(c & 0x18)>>3,
b & 0xf8 |(c & 0x07),
)
for (r, g, b), c in it.izip(img.getdata(), txtarr)])
newimg.save(os.path.splitext(imgfn)[0]+'.png', optimize=1, compression=9)
def decode(imgfn, txtfn):
img= I.open(imgfn)
with open(txtfn, 'wb') as ofp:
arrdata= array.array('B',
((r & 0x7) << 5 | (g & 0x3) << 3 | (b & 0x7)
for r, g, b in img.getdata())).tostring()
findata= arrdata.decode('zip')
ofp.write(findata)
if __name__ == "__main__":
if sys.argv[1] == 'e':
encode(sys.argv[2], sys.argv[3])
elif sys.argv[1] == 'd':
decode(sys.argv[2], sys.argv[3])
алгоритм
Он хранит байт данных на пиксель изображения, используя: 3 младших бита синей полосы, 2 младших бита зеленой и 3 младших бита красной.
функция кодирования: входной текстовый файл сжимается zlib, а размер входного изображения изменяется (с сохранением пропорций), чтобы обеспечить как минимум столько же пикселей, сколько сжатых байтов. * PNG изображение с тем же именем, что и входное изображение (поэтому не используйте имя файла ".png" в качестве ввода, если вы оставляете код как есть :), сохраняется со стеганографическими данными.
функция декодирования: ранее сохраненные zlib-сжатые данные извлекаются из входного изображения и сохраняются без сжатия под предоставленным именем файла.
Я проверил, что старый код все еще выполняется, поэтому вот пример изображения, содержащего стеганографические данные:
Вы заметите, что добавленный шум едва заметен.