else
textpixel
This commit is contained in:
parent
98146327d7
commit
2a0017371e
6 changed files with 205 additions and 2 deletions
24
TextPixel/README.md
Normal file
24
TextPixel/README.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
TextPixel
|
||||
==============
|
||||
|
||||
Encode / Decode between strings and PNG images. Can be imported or used on the commandline. Since it uses one channel to store each character, this program is only compatible with characters between 0 and 255 in unicode.
|
||||
|
||||
One channel is used to store the character, and the other two are randomized, so the output looks different every time.
|
||||
|
||||
python example:
|
||||
|
||||
encoded_string = textpixel.encode_string('Wow, look!')
|
||||
textpixel.write_pixels(encoded_string, 'wowlook.png')
|
||||
|
||||
|
||||
commandline example:
|
||||
|
||||
> textpixel encode bears.txt bears
|
||||
Done.
|
||||
|
||||
> textpixel decode bears
|
||||
Once upon a time there
|
||||
was a book.
|
||||
|
||||
It was about bears.
|
||||
So many bears.
|
BIN
TextPixel/bears.png
Normal file
BIN
TextPixel/bears.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 267 B |
5
TextPixel/bears.txt
Normal file
5
TextPixel/bears.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
Once upon a time there
|
||||
was a book.
|
||||
|
||||
It was about bears.
|
||||
So many bears.
|
BIN
TextPixel/example.png
Normal file
BIN
TextPixel/example.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 95 B |
169
TextPixel/textpixel.py
Normal file
169
TextPixel/textpixel.py
Normal file
|
@ -0,0 +1,169 @@
|
|||
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()
|
|
@ -252,12 +252,14 @@ def handle_twitter(url, customname=None):
|
|||
try:
|
||||
link = pagedata.split('data-url="')[1]
|
||||
link = link.split('"')[0]
|
||||
if link != url:
|
||||
handle_master(link, customname=customname)
|
||||
return
|
||||
except IndexError:
|
||||
try:
|
||||
link = pagedata.split('data-expanded-url="')[1]
|
||||
link = link.split('"')[0]
|
||||
if link != url:
|
||||
handle_master(link, customname=customname)
|
||||
return
|
||||
except IndexError:
|
||||
|
@ -366,6 +368,9 @@ def test_twitter():
|
|||
# Twitter plain text
|
||||
handle_master('https://twitter.com/cp_orange_x3/status/599700702382817280')
|
||||
|
||||
# Twitter plain text
|
||||
handle_master('https://twitter.com/SyriacMFS/status/556513635913437184')
|
||||
|
||||
def test_generic():
|
||||
# Some link that might work
|
||||
handle_master('https://raw.githubusercontent.com/voussoir/reddit/master/SubredditBirthdays/show/statistics.txt')
|
||||
|
|
Loading…
Reference in a new issue