else/TextPixel/textpixel.py
Voussoir 2a0017371e else
textpixel
2015-05-17 23:48:27 -07:00

169 lines
4 KiB
Python

import sys
from PIL import Image as PILImage
import random
import string
ARGS_ENCODE = ['encode', 'save', 'write']
ARGS_DECODE = ['decode', 'read']
# Alternate operation names for commandline use.
def png_filename(filename):
if filename[-4:].lower() != '.png':
filename += '.png'
return filename
def encode_character(text, densitymin=0, densitymax=300):
'''
Given a character, take its ord() value and return an
RGB pixel representation.
The RGB channels will become the ord value, a number
lower than the ord value, and a number higher than the
ord value, though the order will be randomized.
`densitymin` and `densitymax` control how much lower and higher
the additional values will be relative to the ord value, in %.
Returns a tuple (R,G,B)
'''
densitymin = abs(densitymin)
densitymax = abs(densitymax)
asciivalue = ord(text)
percentmin = int((asciivalue * densitymin) / 100)
percentmax = int((asciivalue * densitymax) / 100)
lowerfill = random.randint(asciivalue - percentmax, asciivalue - percentmin)
upperfill = random.randint(asciivalue + percentmin, asciivalue + percentmax)
lowerfill = max(0, lowerfill)
upperfill = min(255, upperfill)
rgb = [lowerfill, asciivalue, upperfill]
random.shuffle(rgb)
return tuple(rgb)
def encode_string(text, densitymin=0, densitymax=300):
'''
Given a string `text`, map each character to an RGB
pixel, and return these pixels so they can be written to a file.
`densitymin` and `densitymax` are passed to encode_character
Returns a list where
[0] is (width,height),
[1] is a dict of pixels: {(x,y) : (R,G,B)}
'''
text = text.strip()
lines = text.split('\n')
encoded_height = len(lines)
encoded_width = max([len(line) for line in lines])
encoded_pixels = {}
for y in range(encoded_height):
line = lines[y]
for x in range(len(line)):
character = line[x]
pixel = encode_character(character, densitymin, densitymax)
encoded_pixels[(x,y)] = pixel
dim = (encoded_width, encoded_height)
out = [dim, encoded_pixels]
return out
def write_pixels(dimout, filename):
'''
Given pixel data of the form returned by `encode_string()`,
write it to a PNG file named `filename`.
'''
filename = png_filename(filename)
dim = dimout[0]
encoded_pixels = dimout[1]
image = PILImage.new('RGBA', dim)
for pixel in encoded_pixels:
image.putpixel(pixel, encoded_pixels[pixel])
image.save(filename)
##############################################################################
def decode_pixel(pixel):
'''
Given a tuple (R,G,B), return the decoded text character.
The character is the str() for the middle value of the tuple.
("middle" referring to numeric value, not index)
'''
pixel = sorted(list(pixel))
character = chr(pixel[1])
return character
def decode_image(image):
'''
Given an image, return the decoded string
`image` may be a string representing the filename
or a PIL Image object
'''
if isinstance(image, str):
if image[-4:].lower() != '.png':
image += '.png'
image = png_filename(image)
image = PILImage.open(image)
width = image.size[0]
height = image.size[1]
decoded_string = ''
for y in range(height):
for x in range(width):
pixel = image.getpixel((x,y))
if pixel[3] == 0:
break
decoded_string += decode_pixel(pixel)
decoded_string += '\n'
decoded_string = decoded_string.strip()
return decoded_string
def argsfail():
print('\ninvalid parameters.')
print('> textpixel.py encode text filename')
print('> textpixel.py decode filename')
quit()
if __name__ == '__main__':
if len(sys.argv) < 3:
argsfail()
op = sys.argv[1].lower()
if op in ARGS_ENCODE and len(sys.argv) == 4:
text = sys.argv[2]
if text[-4:].lower() == '.txt':
try:
tfile = open(text, 'r')
text = tfile.read()
tfile.close()
except FileNotFoundError:
pass
filename = sys.argv[3]
pixels = encode_string(text)
write_pixels(pixels, filename)
print('Done.')
elif op in ARGS_DECODE and len(sys.argv) == 3:
filename = sys.argv[2]
print(decode_image(filename))
else:
argsfail()