I am using a python script to stitch large images (10000 by 10000 pixels) together in a row. I can stitch the first six out of eight images together one at a time absolutely fine. However when I stitch more images beyond this point, I get 'Python.exe has stopped working'.
Code below:
from PIL import Image
import getopt, sys
args = sys.argv
ImageFileList = []
MasterWidth = 0
MasterHeight = 0
filename = ""
row = True
print """
Usage: python imageconnector.py [OPTIONS] [FILENAME] [FILE1] [FILE2] ...[FILE...]...
Combines [FILE1,2,...] into one file called [FILENAME]
OPTIONS:
-o <r/c> Stitch images into a row or a column. Default is row.
-c <colour> Change background fill colour. Default is black.
"""
def main(argv):
global args, MasterWidth, MasterHeight, ImageFileList, filename, deletename
try:
opts, args_files = getopt.getopt(argv, 'o:c:')
except getopt.GetoptError:
print "Illegal arguments!"
sys.exit(-1)
if '-o' in args:
index = args.index('-o')
cr = args[index + 1]
if cr == 'r':
row = True
elif cr == 'c':
row = False
else:
row = True
if '-c' in args:
index = args.index('-c')
colour = args[index + 1]
else:
colour = 'black'
filename = args_files.pop(0)
print('Combining the following images:')
if row:
for x in args_files:
try:
im = Image.open(x)
print(x)
MasterWidth += im.size[0]
if im.size[1] > MasterHeight:
MasterHeight = im.size[1]
else:
MasterHeight = MasterHeight
ImageFileList.append(x)
except:
raise
final_image = Image.new("RGB", (MasterWidth, MasterHeight), colour)
offset = 0
for x in ImageFileList:
temp_image = Image.open(x)
final_image.paste(temp_image, (offset, 0))
offset += temp_image.size[0]
final_image.save(filename)
else:
for x in args_files:
try:
im = Image.open(x)
print(x)
MasterHeight += im.size[1]
if im.size[0] > MasterWidth:
MasterWidth = im.size[0]
else:
MasterWidth = MasterWidth
ImageFileList.append(x)
except:
raise
final_image = Image.new("RGB", (MasterWidth, MasterHeight), colour)
offset = 0
for x in ImageFileList:
temp_image = Image.open(x)
final_image.paste(temp_image, (0, offset))
offset += temp_image.size[1]
final_image.save(filename)
if __name__ == "__main__":
try:
main(sys.argv[1:])
except IOError:
print 'One of more of the input image files is not valid.'
sys.exit(-1)
except SystemExit:
pass
except ValueError:
print 'Not a valid colour value.'
The answer was that Python Image Library (PIL) was 32 bit. I used the updated Pillow library (http://www.lfd.uci.edu/~gohlke/pythonlibs/thb9cnq7/Pillow-2.5.3.win-amd64-py2.7.exe) and it works perfectly. Uses a LOT of memory to stitch 60k pixel images though!
Related
I am trying to run this code to make images compatible with a polaroid z2300 camera:
import PIL
import PIL.Image
import pyexiv2
import os
import shutil
import re
import random
ok = re.compile(r'^PICT\d\d\d\d$')
def rename(f):
while True:
nf = "PICT%04d.JPG" % random.randint(1111,9999)
if not os.path.isfile(nf):
print (f,"->",nf)
return nf
# Fix image paths
for f in os.listdir('.'):
if f.upper().endswith('JPG'):
a, b = os.path.splitext(f)
if not ok.match(a):
shutil.move(f, rename(f))
for f in os.listdir('.'):
if f.upper().endswith('JPG'):
print ("Checking", f)
# Find the image size
im = PIL.Image.open(f)
im_size = im.size
if im_size != (3648,2736) and im_size != (2736,3648):
print ("Resizing image to POLAROID dimensions")
im = im.resize((im_size[0] * 4, im_size[1] * 4))
if im_size[0] > im_size[1]:
into = PIL.Image.new(im.mode, (3648,2736))
im.thumbnail((3648,2736), PIL.Image.ANTIALIAS)
else:
into = PIL.Image.new(im.mode, (2736,3648))
im.thumbnail((2736,3648), PIL.Image.ANTIALIAS)
into.paste(im, (int((into.size[0] - im.size[0]) / 2), int((into.size[1] - im.size[1]) / 2)))
into.save(f)
im = into
im_size = im.size
del im
source_image = pyexiv2.ImageMetadata[f]
source_image.read()
try:
exif_width = int(source_image["Exif.Photo.PixelXDimension"].raw_value)
exif_height = int(source_image["Exif.Photo.PixelYDimension"].raw_value)
except KeyError:
exif_width = None
exif_height = None
# for k,v in source_image.items():
# print k,v
if exif_width != im_size[0] or exif_height != im_size[1]:
print ("EXIF data was bad for %s" % f)
print (" width - ", exif_width, im_size[0])
print (" height - ", exif_height, im_size[1])
source_image["Exif.Photo.PixelXDimension"] = im_size[0]
source_image["Exif.Photo.PixelYDimension"] = im_size[1]
source_image.write()
At the beginning everything looks fine the code changes the image name and size but then the AttributeError appears and the conversion fails to finish:
source_image = pyexiv2.ImageMetadata(f)
AttributeError: module 'pyexiv2' has no attribute 'ImageMetadata'
Any idea how to fix this do I have to change something in the code or do I have to install something else to make it work?
I had images that were in tiles of the following format:
Each number represents a single tile. I used the following script (with the help of stackoverflow) and stitched the images together. The following is the script that I used to stitch the images together:
from PIL import Image
import os
path_to_file ='tiff-files'
def stich_tile(path_to_file, xx , yy):
images = []
for i in os.listdir(path_to_file):
images.append(i)
if len(images) >= xx*yy:
pass
else:
raise ValueError('not enough images in path_to_file !!!!!!!!!!!')
sq_x = xx
sq_y = yy
img_x = (Image.open(path_to_file+'/'+images[0]).size[0])
img_y = (Image.open(path_to_file+'/'+images[0]).size[1])
img_mode = (Image.open(path_to_file+'/'+images[0]).mode)
new_image = Image.new(img_mode, (img_x*sq_x, img_y*sq_y))
x = 0
y = 0
cnt = 0
for i in images:
with Image.open(path_to_file+'/'+i) as img:
new_image.paste(img, (x,y))
cnt += 1
x += img_x
if cnt == sq_x:
x = 0
y += img_y
cnt = 0
else:
pass
return new_image
stich_tile(path_to_file, 3, 5).save('filename.tiff')
The output saved image looks like the following:
I would like to remove the black image that was created. How do I do that?
here modified script that removes black border from bottom and right of the stitched images... as long as the problem was within the starting images:
import numpy as np
from PIL import Image
import os
# path_to_file ='tiff-files'
# path_to_file ='tiff-files2'
# path_to_file ='tiff-files3'
# path_to_file ='tiff-files5'
# path_to_file ='tiff-files5'
path_to_file ='tiff-files6'
def stich_tile(path_to_file, xx , yy):
images = []
for i in os.listdir(path_to_file):
images.append(i)
images.sort() # sort images alphabetically
# images.sort(key = lambda x: int(x.strip('.tiff').split('-')[1])) ## ---> per path_to_file ='tiff-files3'
images = images[:xx*yy] #-----> riduce lista immagini al numero richiesto
print(images)
print('lenght list of images', len(images), 'x and y requested', xx*yy)
if len(images) >= xx*yy:
pass
else:
# raise ValueError('not enough images in path_to_file !!!!!!!!!!!')
raise ValueError('EXCEPTION not enough images in path_to_file !!!!!!!!!!!', xx*yy,'images needed : ', len(images),'images present !!!')
sq_x = xx
sq_y = yy
img_x = (Image.open(path_to_file+'/'+images[0]).size[0])
img_y = (Image.open(path_to_file+'/'+images[0]).size[1])
img_mode = (Image.open(path_to_file+'/'+images[0]).mode)
print('images[0] size : ', img_x, img_y, img_x*sq_x, img_y*sq_y)
new_image = Image.new(img_mode, (img_x*sq_x, img_y*sq_y))
print('new_image : size :', new_image.size)
x = 0
y = 0
cnt = 0
cnt_cycle = 0
for i in images:
with Image.open(path_to_file+'/'+i) as img:
new_image.paste(img, (x,y))
cnt += 1
cnt_cycle += 1
x += img_x
if cnt == sq_x:
x = 0
y += img_y
cnt = 0
else:
pass
print('count of for i in images cycles', cnt_cycle)
new_image = np.array(new_image)
print(new_image.shape, np.min(new_image), np.max(new_image))
for ar_y in range(new_image.shape[0]-1,0,-1):
res = np.all(new_image[ar_y,:] == (0,0,0))
if res:
new_image = new_image[0:(ar_y),:]
# print('black', ar_y)
else:
print('break at :', ar_y ,' row')
break
print(new_image.shape, np.min(new_image), np.max(new_image))
print(new_image.shape, np.min(new_image), np.max(new_image))
for ar_x in range(new_image.shape[1]-1,0,-1):
res = np.all(new_image[:,ar_x] == (0,0,0))
if res:
new_image = new_image[:,0:(ar_x)]
# print('black', ar_x)
else:
print('break at :', ar_x ,' column')
break
print(new_image.shape, np.min(new_image), np.max(new_image))
new_image = Image.fromarray(new_image)
return new_image
try :
pippo = stich_tile(path_to_file, 3,3)
pippo.show()
# pippo.save('RGB_black_tiff_3X.tiff')
except ValueError as err:
print('stopped', err.args)
could use same approach to remove black border from top/left.
Could be that pillow library has an in built option/function/whatever its called to do the same....
its kind of late here, tested code with 3X3 RGB tiff images with black borders.. let me know if it works
I need to run a python script inside a folder.
the script count files ( image ) number create an image for
every 64 images.
example: if the folder containing 640 images, I will get 10
images as a combination of 64 images/outputimage
just, before adding "for" instruction, I can get a result, but manually and only with the same image ( a duplication of image ).
any idea?
so I proceed as follows:
import os
import os.path
from PIL import Image
list = os.listdir(".") # current directory
number_files = len(list)
print (number_files)
for x in range(0, number_files):
# files = ['x.png']
# opens an image: here I can't find how
im = Image.open("1.png") # here I tried to
# creates a new empty image, RGB mode, and size 800 by 800.
new_im = Image.new('RGB', (800, 800))
# Here I resize my opened image, so it is no bigger than 100,100
im.thumbnail((100, 100))
# Iterate through a 8 by 8 grid with 100 spacing, to place my image
for i in xrange(0, 800, 100):
for j in xrange(0, 800, 100):
# paste the image at location i,j:
new_im.paste(im, (i, j))
new_im.save(os.path.expanduser('outputimage.png'))
update :
import os
import os.path
from PIL import Image
def drange(start, stop, step):
while start < stop:
yield start
start += step
list = os.listdir(".") # directory path
number_files = len(list)
print (number_files)
new_im = Image.new('RGB', (800, 800))
for x in drange(0, number_files, 64):
im = Image.open(list[x])
im.thumbnail((100, 100))
for i in xrange(0, 800, 100):
for j in xrange(0, 800, 100):
new_im.paste(im, (i, j))
new_im.save(os.path.expanduser('out.png'))
other update based on Sven solution :
import os.path
from PIL import Image
fileList = [] where_to_look = "png/"
for f in os.listdir(where_to_look):
if os.path.isfile(os.path.join(where_to_look, f)):
fileList.append(f)
print (len(fileList))
target_img = None n_targets = 0 collage_saved = False
for n in range(len(fileList)):
img = Image.open(fileList[n])
img.thumbnail((100, 100))
if n % 64 == 0:
# create an empty image for a collage
target_img = Image.new("RGB", (800, 800))
n_targets += 1
collage_saved = False
# paste the image at the correct position
i = int(n / 8)
j = n % 8
target_img.paste(img, (100*i, 100*j))
if (n + 1) % 64 == 0 and target_img is not None:
# save a finished 8x8 collage
target_img.save("{0:04}.png".format(n_targets))
collage_saved = True
# save the last collage if not collage_saved:
target_img.save("{0:04}.png".format(n_targets))
Based on your update, I quickly sketched a solution. Please beware that I did not test it:
import os
from PIL import Image
image_dir = os.path.abspath("png")
# list all files in directory
files = os.listdir(image_dir)
# get all PNGs
png_files = filter(lambda x: x.endswith(".png"), files)
# make file paths absolute
image_files = map(lambda x: os.sep.join([image_dir, x]), png_files)
n_files = len(image_files)
target_img = None
n_targets = 0
collage_saved = False
for n in range(n_files):
img = Image.open(image_files[n])
img.thumbnail((100, 100))
if n % 64 == 0:
# create an empty image for a collage
target_img = Image.new("RGB", (800, 800))
n_targets += 1
collage_saved = False
# paste the image at the correct position
i = int(n / 8)
j = n % 8
target_img.paste(img, (100*i, 100*j))
if (n + 1) % 64 == 0 and target_img is not None:
# save a finished 8x8 collage
target_img.save("{0:04}.png".format(n_targets))
collage_saved = True
# save the last collage
if not collage_saved:
target_img.save("{0:04}.png".format(n_targets))
This will iterate over all your images and assemble them in a 8x8 grid (I call it collage in the script). Whenever the grid is filled, the collage is saved as a file with the naming pattern 0001.png, 0002.png and so on.
Note that there are some points to improve:
os.listdir(".") will give you all files in the directory, not only images. You would have to filter the output for image files.
This only works in the current working directory.
The output is also saved to the current working directory.
UPDATE: filter PNGs and use absolute paths
I'm uploading a file to the ftp server, the actual settings for the upload are correct but it isn't uploading the correct filename, it is uploading filename as the actual name of the file instead of capture......
#!/usr/bin/python
#
# Lightweight Motion Detection using python picamera libraries
# based on code from raspberry pi forum by user utpalc
# modified by Claude Pageau for this working example
# ------------------------------------------------------------
# original code on github https://github.com/pageauc/picamera-motion
# This is sample code that can be used for further development
verbose = True
if verbose:
print "Loading python libraries ....."
else:
print "verbose output has been disabled verbose=False"
import picamera
import picamera.array
import datetime
import time
import ftplib
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
from fractions import Fraction
#Constants
SECONDS2MICRO = 1000000 # Constant for converting Shutter Speed in Seconds to Microseconds
# User Customizable Settings
imageDir = "images"
imagePath = "/home/pi/pimotion/" + imageDir
imageNamePrefix = 'capture-' # Prefix for all image file names. Eg front-
imageWidth = 1980
imageHeight = 1080
imageVFlip = False # Flip image Vertically
imageHFlip = False # Flip image Horizontally
imagePreview = False
numberSequence = False
threshold = 10 # How Much pixel changes
sensitivity = 100 # How many pixels change
nightISO = 800
nightShutSpeed = 6 * SECONDS2MICRO # seconds times conversion to microseconds constant
# Advanced Settings not normally changed
testWidth = 100
testHeight = 75
def checkImagePath(imagedir):
# Find the path of this python script and set some global variables
mypath=os.path.abspath(__file__)
baseDir=mypath[0:mypath.rfind("/")+1]
baseFileName=mypath[mypath.rfind("/")+1:mypath.rfind(".")]
# Setup imagePath and create folder if it Does Not Exist.
imagePath = baseDir + imagedir # Where to save the images
# if imagePath does not exist create the folder
if not os.path.isdir(imagePath):
if verbose:
print "%s - Image Storage folder not found." % (progName)
print "%s - Creating image storage folder %s " % (progName, imagePath)
os.makedirs(imagePath)
return imagePath
def takeDayImage(imageWidth, imageHeight, filename):
if verbose:
print "takeDayImage - Working ....."
with picamera.PiCamera() as camera:
camera.resolution = (imageWidth, imageHeight)
# camera.rotation = cameraRotate #Note use imageVFlip and imageHFlip variables
if imagePreview:
camera.start_preview()
camera.vflip = imageVFlip
camera.hflip = imageHFlip
# Day Automatic Mode
camera.exposure_mode = 'auto'
camera.awb_mode = 'auto'
camera.capture(filename)
sftp = ftplib.FTP('ftpdomainname','myftpusername','myftppassword') # Connect
fp = open(filename) # file to send
sftp.storbinary('STOR filename', fp) # Send the file
fp.close() # Close file and FTP
sftp.quit()
if verbose:
print "takeDayImage - Captured %s" % (filename)
return filename
def takeNightImage(imageWidth, imageHeight, filename):
if verbose:
print "takeNightImage - Working ....."
with picamera.PiCamera() as camera:
camera.resolution = (imageWidth, imageHeight)
if imagePreview:
camera.start_preview()
camera.vflip = imageVFlip
camera.hflip = imageHFlip
# Night time low light settings have long exposure times
# Settings for Low Light Conditions
# Set a frame rate of 1/6 fps, then set shutter
# speed to 6s and ISO to approx 800 per nightISO variable
camera.framerate = Fraction(1, 6)
camera.shutter_speed = nightShutSpeed
camera.exposure_mode = 'off'
camera.iso = nightISO
# Give the camera a good long time to measure AWB
# (you may wish to use fixed AWB instead)
time.sleep(10)
camera.capture(filename)
if verbose:
print "checkNightMode - Captured %s" % (filename)
return filename
def takeMotionImage(width, height, daymode):
with picamera.PiCamera() as camera:
time.sleep(1)
camera.resolution = (width, height)
with picamera.array.PiRGBArray(camera) as stream:
if daymode:
camera.exposure_mode = 'auto'
camera.awb_mode = 'auto'
else:
# Take Low Light image
# Set a framerate of 1/6 fps, then set shutter
# speed to 6s and ISO to 800
camera.framerate = Fraction(1, 6)
camera.shutter_speed = nightShutSpeed
camera.exposure_mode = 'off'
camera.iso = nightISO
# Give the camera a good long time to measure AWB
# (you may wish to use fixed AWB instead)
time.sleep( 10 )
camera.capture(stream, format='rgb')
return stream.array
def scanIfDay(width, height, daymode):
data1 = takeMotionImage(width, height, daymode)
while not motionFound:
data2 = takeMotionImage(width, height, daymode)
pCnt = 0L;
diffCount = 0L;
for w in range(0, width):
for h in range(0, height):
# get the diff of the pixel. Conversion to int
# is required to avoid unsigned short overflow.
diff = abs(int(data1[h][w][1]) - int(data2[h][w][1]))
if diff > threshold:
diffCount += 1
if diffCount > sensitivity:
break; #break outer loop.
if diffCount > sensitivity:
motionFound = True
else:
# print "Sum of all pixels=", pxCnt
data2 = data1
return motionFound
def scanMotion(width, height, daymode):
motionFound = False
data1 = takeMotionImage(width, height, daymode)
while not motionFound:
data2 = takeMotionImage(width, height, daymode)
diffCount = 0L;
for w in range(0, width):
for h in range(0, height):
# get the diff of the pixel. Conversion to int
# is required to avoid unsigned short overflow.
diff = abs(int(data1[h][w][1]) - int(data2[h][w][1]))
if diff > threshold:
diffCount += 1
if diffCount > sensitivity:
break; #break outer loop.
if diffCount > sensitivity:
motionFound = True
else:
data2 = data1
return motionFound
def getFileName(imagePath, imageNamePrefix, currentCount):
rightNow = datetime.datetime.now()
if numberSequence :
filename = imagePath + "/" + imageNamePrefix + str(currentCount) + ".jpg"
else:
filename = "%s/%s%04d%02d%02d-%02d%02d%02d.jpg" % ( imagePath, imageNamePrefix ,rightNow.year, rightNow.month, rightNow.day, rightNow.hour, rightNow.minute, rightNow.second)
return filename
def motionDetection():
print "Scanning for Motion threshold=%i sensitivity=%i ......" % (threshold, sensitivity)
isDay = True
currentCount= 1000
while True:
if scanMotion(testWidth, testHeight, isDay):
filename = getFileName(imagePath, imageNamePrefix, currentCount)
if numberSequence:
currentCount += 1
if isDay:
takeDayImage( imageWidth, imageHeight, filename )
else:
takeNightImage( imageWidth, imageHeight, filename )
if __name__ == '__main__':
try:
motionDetection()
finally:
print ""
print "+++++++++++++++"
print "Exiting Program"
print "+++++++++++++++"
print ""
Instead of 'STOR filename', use the actual name of the file
sftp.storbinary('STOR ' + filename, fp)
I am writing several functions, the first one inserts a greyscale bitmap image into another colour bitmap image. Now my aim is basically to take each digit of the greyscale pixel image (eg. 123) and replace the end digit of every RGB pixel (244, 244, 244), so it would basically end up like this (241, 242, 243). Essentially this is watermarking the colour image with the greyscale image.
The following code is what I have so far, i'm able to return the tuple values in a list, i just do not know how to manipulate a space the size of a smaller greyscale image in a larger 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 offer some advice?
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
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)
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
################################################################################
class ByteWriter:
"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()
def write(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)
#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's 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)))