forked from ProfOak/ascii_py
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathascii.py
183 lines (132 loc) · 5.55 KB
/
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import colorama
import shutil
import os
from PIL import Image
from PIL import ImageDraw
class Ascii():
def __init__(self, in_file):
""" Python ascii image maker """
self.letter_densities = '" .`-_\':,;^=+/"|)\\<>)iv%xclrs{*}I?!][1taeo7zjLunT#JCwfy325Fp6mqSghVd4EgXPGZbYkOA&8U$@KHDBWNMR0Q'
self.from_pic = Image.open(in_file)
self.to_pic = Image.new("RGB", self.from_pic.size, "black")
self.draw = ImageDraw.Draw(self.to_pic)
self.MAX_W, self.MAX_H = self.from_pic.size
def word_artify(self, words="#", step=3):
""" create ascii image from word string """
words = words.upper()
h = w = i = 0
# skip pixels by `step` amount and place characters around image
while h < self.MAX_H:
while w < self.MAX_W:
# use a string as the art characters
c = words[i]
# loop around
i = (i+1) % len(words)
# get the color from the pixel
p = self.from_pic.getpixel((w, h))
# insert text based on pixel color
self.draw.text((w, h), c, p)
w += step
h += step
w = 0
self.from_pic.close()
def density_artify(self, step=7):
"""
Generates the acsii image where the 'words' are selected based on
the brightness of a pixel.
A brighter pixel will have a character with lower visual density and
a less bright pixel will have a character with high visual density.
"""
if step < 7:
step = 7
h = 0
w = 0
# used for brightness (density) levels
grayscale_img = self.from_pic.convert("L")
while h < self.MAX_H:
while w < self.MAX_W:
# get brightness value
brightness = 255 - grayscale_img.getpixel((w, h))
clr = self.from_pic.getpixel((w, h))
# select required character from the letter_densities list
char_pos = (brightness/255.0) * (len(self.letter_densities) - 1)
c = self.letter_densities[int(round(char_pos, 0))]
self.draw.text((w, h), c, clr)
w += step
h += step
w = 0
self.from_pic.close()
grayscale_img.close()
def terminal_artify(self):
""" covert image to ascii, display in terminal """
# all colors are beautiful
acab = [
[( 0, 0, 0), colorama.Fore.LIGHTBLACK_EX],
[( 0, 0, 255), colorama.Fore.BLUE],
[( 0, 255, 0), colorama.Fore.GREEN],
[(255, 0, 0), colorama.Fore.RED],
[(255, 255, 255), colorama.Fore.WHITE],
[(255, 0, 255), colorama.Fore.MAGENTA],
[( 0, 255, 255), colorama.Fore.CYAN],
[(255, 255, 0), colorama.Fore.YELLOW]
]
# convert to floats, linearize
# in case more colors are added
acab = [ [[(v/255.0)**2.2 for v in x[0]], x[1]] for x in acab ]
# needed for Windows operating systems
colorama.init()
canvas = self.from_pic
current_h, current_w = float(self.MAX_H), float(self.MAX_W)
# resize to fit current dimensions of terminal
# shutil added get_terminal_size starting in python 3.3
t_width, t_height = shutil.get_terminal_size()
if current_h > t_height or current_w > t_width:
# floating point division
scalar = max(current_h/t_height, (current_w*2)/t_width)
current_w = int((current_w*2)/scalar)
current_h = int((current_h)/scalar)
dimensions = current_w, current_h
canvas = self.from_pic.resize(dimensions)
# used for brightness (density) levels
grayscale_img = canvas.convert("L")
image = ""
for h in range(current_h):
for w in range(current_w):
# get brightness value
brightness = grayscale_img.getpixel((w, h))/255.0
srgb = [ (v/255.0)**2.2 for v in canvas.getpixel((w,h)) ]
# select required character from the letter_densities list
char_pos = brightness * (len(self.letter_densities) - 1)
color = self._convert_color(srgb, brightness, acab)
image += color + self.letter_densities[int(round(char_pos, 0))]
image += "\n"
# prints the converted image to terminal
# (remove the last newline)
print(image[:-1] + colorama.Fore.RESET)
self.from_pic.close()
grayscale_img.close()
input("Press enter to continue...")
#######################################
# Helper Methods #
#######################################
def _L2_min(self, v1, v2):
"""
euclidian norm in a 2 dimensional space
used for calculating shortest distance
"""
return (v1[0]-v2[0])**2 + (v1[1]-v2[1])**2 + (v1[2]-v2[2])**2
def _convert_color(self, rgb, brightness, acab):
""" convert color using acab data """
min_distance = 2
index = 0
for i in range(len(acab)):
tmp = [ v*brightness for v in acab[i][0] ]
distance = self._L2_min(tmp, rgb)
if distance < min_distance:
index = i
min_distance = distance
return acab[index][1]
def save(self, out_file="out.jpg"):
""" save image to file """
self.to_pic.save(out_file)
self.to_pic.close()