Skip to content

Commit 5afcdcc

Browse files
feat: img_to_ascii(_2) - Create ascii arts based on images
1 parent 938577b commit 5afcdcc

File tree

8 files changed

+231
-0
lines changed

8 files changed

+231
-0
lines changed

ebook/chapters/third_party_chapters/commandline.tex

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,19 @@ \subsection{Cooked Input}
133133
What is your name?: FlOriAn dAhliTz
134134
Florian Dahlitz
135135
\end{lstlisting}
136+
137+
138+
\subsection{Image To ASCII Art}
139+
140+
Once in a while you may want to enhance the look and feel of your command-line application by adding some ascii art to it.
141+
Luckily, the folowing to recipes allow you to create ascii arts based on images.
142+
As both recipes are quite long, they are not display here, but you can find them in the corresponding repository.
143+
144+
The first one saves the ascii art to a file called \lstinline{out.txt} in your current working directory.
145+
The second one prints the resulting ascii art directly to stdout.
146+
Make sure to checkout the code of the first recipe as it shows you, which arguments you can pass to the file.
147+
148+
\begin{lstlisting}[caption=Usage of both recipes]
149+
$ python img_to_ascii.py --file src/python.png --cols 120
150+
$ python img_to_ascii_2.py src/python.png
151+
\end{lstlisting}

ebook/python-snippets.epub

395 Bytes
Binary file not shown.

ebook/python-snippets.mobi

438 Bytes
Binary file not shown.

ebook/python-snippets.pdf

1.68 KB
Binary file not shown.

third_party/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ A collection of useful snippets using third party packages.
1919
| folium_snippet | Folium is used to create interactive maps |
2020
| formatted_json | Dump json-objects in a formatted way |
2121
| f-strings_vs_str | Compare the time of f-strings and str |
22+
| img_to_ascii(_2) | Create ascii arts based on images |
2223
| inspect_docker | Control docker from within Python |
2324
| interactive_cli | Example of PyInquirer to create interactive CLIs |
2425
| is_holiday | Checks whether a given date is a holiday in a certain country |

third_party/img_to_ascii.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
2+
# Python code to convert an image to ASCII image.
3+
import sys, random, argparse
4+
import numpy as np
5+
import math
6+
7+
from PIL import Image
8+
9+
# gray scale level values from:
10+
# http://paulbourke.net/dataformats/asciiart/
11+
12+
# 70 levels of gray
13+
gscale1 = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. "
14+
15+
# 10 levels of gray
16+
gscale2 = '@%#*+=-:. '
17+
18+
def getAverageL(image):
19+
20+
"""
21+
Given PIL Image, return average value of grayscale value
22+
"""
23+
# get image as numpy array
24+
im = np.array(image)
25+
26+
# get shape
27+
w,h = im.shape
28+
29+
# get average
30+
return np.average(im.reshape(w*h))
31+
32+
def covertImageToAscii(fileName, cols, scale, moreLevels):
33+
"""
34+
Given Image and dims (rows, cols) returns an m*n list of Images
35+
"""
36+
# declare globals
37+
global gscale1, gscale2
38+
39+
# open image and convert to grayscale
40+
image = Image.open(fileName).convert('L')
41+
42+
# store dimensions
43+
W, H = image.size[0], image.size[1]
44+
print("input image dims: %d x %d" % (W, H))
45+
46+
# compute width of tile
47+
w = W/cols
48+
49+
# compute tile height based on aspect ratio and scale
50+
h = w/scale
51+
52+
# compute number of rows
53+
rows = int(H/h)
54+
55+
print("cols: %d, rows: %d" % (cols, rows))
56+
print("tile dims: %d x %d" % (w, h))
57+
58+
# check if image size is too small
59+
if cols > W or rows > H:
60+
print("Image too small for specified cols!")
61+
exit(0)
62+
63+
# ascii image is a list of character strings
64+
aimg = []
65+
# generate list of dimensions
66+
for j in range(rows):
67+
y1 = int(j*h)
68+
y2 = int((j+1)*h)
69+
70+
# correct last tile
71+
if j == rows-1:
72+
y2 = H
73+
74+
# append an empty string
75+
aimg.append("")
76+
77+
for i in range(cols):
78+
79+
# crop image to tile
80+
x1 = int(i*w)
81+
x2 = int((i+1)*w)
82+
83+
# correct last tile
84+
if i == cols-1:
85+
x2 = W
86+
87+
# crop image to extract tile
88+
img = image.crop((x1, y1, x2, y2))
89+
90+
# get average luminance
91+
avg = int(getAverageL(img))
92+
93+
# look up ascii char
94+
if moreLevels:
95+
gsval = gscale1[int((avg*69)/255)]
96+
else:
97+
gsval = gscale2[int((avg*9)/255)]
98+
99+
# append ascii char to string
100+
aimg[j] += gsval
101+
102+
# return txt image
103+
return aimg
104+
105+
# main() function
106+
def main():
107+
# create parser
108+
descStr = "This program converts an image into ASCII art."
109+
parser = argparse.ArgumentParser(description=descStr)
110+
# add expected arguments
111+
parser.add_argument('--file', dest='imgFile', required=True)
112+
parser.add_argument('--scale', dest='scale', required=False)
113+
parser.add_argument('--out', dest='outFile', required=False)
114+
parser.add_argument('--cols', dest='cols', required=False)
115+
parser.add_argument('--morelevels',dest='moreLevels',action='store_true')
116+
117+
# parse args
118+
args = parser.parse_args()
119+
120+
imgFile = args.imgFile
121+
122+
# set output file
123+
outFile = 'out.txt'
124+
if args.outFile:
125+
outFile = args.outFile
126+
127+
# set scale default as 0.43 which suits
128+
# a Courier font
129+
scale = 0.43
130+
if args.scale:
131+
scale = float(args.scale)
132+
133+
# set cols
134+
cols = 80
135+
if args.cols:
136+
cols = int(args.cols)
137+
138+
print('generating ASCII art...')
139+
# convert image to ascii txt
140+
aimg = covertImageToAscii(imgFile, cols, scale, args.moreLevels)
141+
142+
# open file
143+
f = open(outFile, 'w')
144+
145+
# write to file
146+
for row in aimg:
147+
f.write(row + '\n')
148+
149+
# cleanup
150+
f.close()
151+
print("ASCII art written to %s" % outFile)
152+
153+
# call main
154+
if __name__ == '__main__':
155+
main()

third_party/img_to_ascii_2.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from PIL import Image
2+
3+
ASCII_CHARS = [ '#', '?', '%', '.', 'S', '+', '.', '*', ':', ',', '@']
4+
5+
def scale_image(image, new_width=100):
6+
"""Resizes an image preserving the aspect ratio.
7+
"""
8+
(original_width, original_height) = image.size
9+
aspect_ratio = original_height/float(original_width)
10+
new_height = int(aspect_ratio * new_width)
11+
12+
new_image = image.resize((new_width, new_height))
13+
return new_image
14+
15+
def convert_to_grayscale(image):
16+
return image.convert('L')
17+
18+
def map_pixels_to_ascii_chars(image, range_width=25):
19+
"""Maps each pixel to an ascii char based on the range
20+
in which it lies.
21+
22+
0-255 is divided into 11 ranges of 25 pixels each.
23+
"""
24+
25+
pixels_in_image = list(image.getdata())
26+
pixels_to_chars = [ASCII_CHARS[pixel_value//range_width] for pixel_value in
27+
pixels_in_image]
28+
29+
return "".join(pixels_to_chars)
30+
31+
def convert_image_to_ascii(image, new_width=100):
32+
image = scale_image(image)
33+
image = convert_to_grayscale(image)
34+
35+
pixels_to_chars = map_pixels_to_ascii_chars(image)
36+
len_pixels_to_chars = len(pixels_to_chars)
37+
38+
image_ascii = [pixels_to_chars[index: index + new_width] for index in
39+
range(0, len_pixels_to_chars, new_width)]
40+
41+
return "\n".join(image_ascii)
42+
43+
def handle_image_conversion(image_filepath):
44+
image = None
45+
try:
46+
image = Image.open(image_filepath)
47+
except Exception as e:
48+
print("Unable to open image file {image_filepath}.".format(image_filepath=image_filepath))
49+
print(e)
50+
return
51+
52+
image_ascii = convert_image_to_ascii(image)
53+
print(image_ascii)
54+
55+
if __name__=='__main__':
56+
import sys
57+
58+
image_file_path = sys.argv[1]
59+
handle_image_conversion(image_file_path)

third_party/src/python.png

50.9 KB
Loading

0 commit comments

Comments
 (0)