-
Notifications
You must be signed in to change notification settings - Fork 0
/
code-and-picture-to-ascii.py
executable file
·140 lines (91 loc) · 4.06 KB
/
code-and-picture-to-ascii.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#!/bin/python3
# Readable version
# search for space in front to put the unpacker first, then the back.
#
# TODO change to allow user to input pattern
import re
import lzma
import base64
import argparse
from PIL import Image,ImageOps,ImageStat
parser = argparse.ArgumentParser(description='Converts given code into the shape of input picture')
parser.add_argument('--image', type=str, help='input image', default="sample_media/boykisser.jpg")
parser.add_argument('--code', type=str, help='python file of code', default="out/run-gif.py")
parser.add_argument('--output', type=str, help='output', default="out/formatted-code.py")
parser.add_argument('--width', type=int, help='Number of characters in a line', default=400)
args = parser.parse_args()
code = open(args.code).read()
og_img = Image.open(args.image)
def asciify(img, new_width = args.width):
width, height = img.size
aspect_ratio = height/width
new_height = aspect_ratio * new_width * 0.55
img = img.resize((new_width, int(new_height)))
img_grayscale = img.convert('L')
if ImageStat.Stat(img_grayscale).mean[0] > 128:
img_grayscale = ImageOps.invert(img).convert('L')
chars = ["#", "?", ":", " "]
pixels = img_grayscale.getdata()
new_pixels = []
for pixel in pixels:
new_pixels.append(chars[pixel // 64])
new_pixels = ''.join(new_pixels)
new_pixels_count = len(new_pixels)
# One liner to return list with each line representing a row of ascii characters
return [new_pixels[index:index + new_width] for index in range(0, new_pixels_count, new_width)]
def count_usable(to_count):
result = re.sub(r"[^#?]+", "", to_count)
return len(result)
text = asciify(og_img)
usable_chars = ["#", "?"]
unpacker_head = r'import base64,lzma;exec(lzma.decompress(base64.b64decode("""'
unpacker_end = '""")))'
unpacker_end_length = len(unpacker_end)
unpacker_head_length = len(unpacker_head)
# shebang is for linux, the correct execution is python3 file.py
# but it's there if it's called directly
file_magic = "#!/bin/python3\n\n"
if len(unpacker_head) > count_usable(text[0]):
raise Exception(f"unpacker head is longer than the amount of free continuous space in the first line, minimum width is {len(unpacker_head)}")
target_inject = text[-1][len(text[0]) - unpacker_end_length:]
if len(target_inject) > count_usable(target_inject):
raise Exception("unpacker end is longer than the amount of free continuous space in the last line")
text="\n".join(text)
# print(text)
max_payload_size = count_usable(text[unpacker_head_length:len(text) - unpacker_end_length])
payload = base64.b64encode(lzma.compress(code.strip().encode(), preset = 9 | lzma.PRESET_EXTREME)).decode()
payload_len = len(payload)
print(payload_len)
#exit()
if payload_len > max_payload_size:
print("Payload is too big, calculating resize...")
new_width = args.width + 1
while True:
print(f"Trying {new_width}")
text = "".join(asciify(og_img, new_width))
new_payload_size = count_usable(text[unpacker_head_length:len(text) - unpacker_end_length])
print(f"New payload size, {new_payload_size}")
if new_payload_size >= payload_len:
break
new_width += 1
print(f"Found nearest width {new_width+1}, try using it as width")
raise Exception(f"payload is longer than the amount of free space we have ({payload_len} > {max_payload_size})")
print("Packed payload")
payload_pointer = 0
text_pointer = 0
output=""
for i in text[unpacker_head_length:len(text) - unpacker_end_length]:
text_pointer += 1
if payload_pointer > len(payload) - 1:
output+=text[unpacker_head_length + text_pointer - 1:len(text) - unpacker_end_length]
break
if i in usable_chars:
output += payload[payload_pointer]
payload_pointer += 1
continue
output += i
if payload_pointer != len(payload):
raise Exception(f"Unused payload left over {payload_pointer} != {len(payload)}")
output = file_magic + unpacker_head + output + unpacker_end
open(args.output, "w").write(output)
print(f"Generated file at {args.output}")