Skip to content

Commit eff6ee7

Browse files
committed
Added dynamic resolution to Photo-to-ASCII
1 parent d8a3794 commit eff6ee7

File tree

1 file changed

+145
-74
lines changed

1 file changed

+145
-74
lines changed

Photo To Ascii/photo_to_ascii.py

Lines changed: 145 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,49 @@
1+
'''
2+
Script that converts any Image into its ASCII representation.
3+
'''
4+
5+
from sys import exit as sysexit
16
from PIL import Image, ImageEnhance
27

3-
# ASCII characters used to build the output text
4-
char_ramp = []
58

6-
# Choose character sequence for mapping
7-
def set_char_ramp():
8-
global char_ramp
9+
def pixels_to_ascii(image, char_ramp):
10+
'''
11+
Function that takes in an image and a character sequence. And returns a string containing
12+
the ASCII representation of the image based on the character sequence provided.
13+
'''
14+
15+
pixels = image.convert("L").getdata()
16+
characters = " ".join([char_ramp[int((pixel/256)*len(char_ramp))] for pixel in pixels])
17+
pixel_count = len(characters)
18+
scanline_width = image.width * 2
19+
20+
return "\n".join([characters[index:(index+scanline_width)]
21+
for index in range(0, pixel_count, scanline_width)])
22+
23+
24+
def input_image():
25+
'''
26+
Function that asks user for a path to an image file and checks for validity.
27+
Then it loads and returns the image and the path as a tuple(image, path).
28+
'''
29+
30+
path = input("Enter a valid pathname to an image:\n")
31+
32+
try:
33+
image = Image.open(path)
34+
except IOError:
35+
print("Invalid Path!")
36+
sysexit()
37+
38+
return image, path
39+
40+
41+
def input_ramp():
42+
'''
43+
Function that asks the user to choose from a set of character sequences
44+
or to specify their own custom sequence and then returns that as a string.
45+
'''
46+
947
print("Choose a Character Sequence!")
1048
print("1 - Basic")
1149
print("2 - Standard (Short)")
@@ -17,90 +55,123 @@ def set_char_ramp():
1755

1856
try:
1957
choice = int(choice)
20-
except Exception:
58+
except ValueError:
2159
print("Invalid Input!")
22-
exit()
60+
sysexit()
2361

2462
if choice == 1:
25-
char_ramp = list("00011111...")
26-
elif choice == 2:
27-
char_ramp = list("@%#*+=-:. ")
28-
elif choice == 3:
29-
char_ramp = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1}{[]?-_+~<>i!lI;:,\"^`'. ")
30-
elif choice == 4:
31-
char_ramp = ["█", "▉", "▊", "▋", "▌", "▍", "▎", "▏"]
32-
elif choice == 5:
33-
char_ramp = ["█", "▓", "▒", "░", " "]
34-
elif choice == 6:
63+
return list("00011111...")
64+
if choice == 2:
65+
return list("@%#*+=-:. ")
66+
if choice == 3:
67+
return list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1}{[]?-_+~<>i!lI;:,\"^`'. ")
68+
if choice == 4:
69+
return ["█", "▉", "▊", "▋", "▌", "▍", "▎", "▏"]
70+
if choice == 5:
71+
return ["█", "▓", "▒", "░", " "]
72+
if choice == 6:
3573
custom_ramp = input("Enter your Character Sequence from High density to Low: ")
3674
if len(custom_ramp) == 0:
3775
print("Invalid Input!")
38-
exit()
39-
char_ramp = list(custom_ramp)
40-
else:
41-
print("Invalid Input!")
42-
exit()
76+
sysexit()
77+
return list(custom_ramp)
4378

79+
print("Invalid Input!")
80+
sysexit()
4481

45-
# Convert pixels to a string of ASCII characters
46-
def pixels_to_ascii(image, contrast_factor):
47-
image = image.convert("L")
48-
enhancer = ImageEnhance.Contrast(image)
49-
image = enhancer.enhance(contrast_factor)
50-
pixels = image.getdata()
51-
characters = " ".join([char_ramp[int((pixel/256)*len(char_ramp))] for pixel in pixels])
52-
return(characters)
5382

54-
# Driver function
55-
def photoascii():
56-
# Attempt to open image file from user-input
57-
path = input("Enter a valid pathname to an image:\n")
58-
try:
59-
image = Image.open(path)
60-
except Exception:
61-
print("Invalid Path!")
62-
exit()
63-
64-
contrast_factor = input("Enter contrast factor (1 = Original Contrast) [Note: Enter negative value to invert output] : ")
83+
def input_contrast():
84+
'''
85+
Function that asks user for the contrast factor that is to be applied
86+
on the image before conversion. And returns it.
87+
'''
88+
89+
while True:
90+
contrast_factor = input("Enter contrast factor [Default - 1 | '?' for info] : ")
91+
if contrast_factor == '?':
92+
print("Contrast factor is a value that is used to controle the contrast of the output.",
93+
"Default value of the contrast factor is 1.",
94+
"Entering a negative value will invert the output.")
95+
else:
96+
break
97+
6598
try:
6699
contrast_factor = float(contrast_factor)
67-
except Exception:
100+
except ValueError:
68101
print("Invalid Input!")
69-
exit()
70-
71-
set_char_ramp()
72-
73-
if contrast_factor < 0:
74-
global char_ramp
75-
char_ramp.reverse()
76-
contrast_factor = -contrast_factor
77-
78-
# Fetch the name of the image file
102+
sysexit()
103+
104+
return contrast_factor
105+
106+
107+
def resize_image(image):
108+
'''
109+
Function that takes in an image and asks the user for a sample size.
110+
Then returns a resized image such that each pixel represents the sample grid.
111+
'''
112+
113+
while True:
114+
sample_size = input("Enter the sample size [Default - 4 | '?' for info] : ")
115+
if sample_size == '?':
116+
print("Sample size refers to the number of pixels",
117+
"that will be sampled for one character.",
118+
"Default value of sample size is 4.",
119+
"Its value must be greater than or equal to 1.")
120+
else:
121+
break
122+
123+
try:
124+
sample_size = int(sample_size)
125+
except ValueError:
126+
print("Invalid Input!")
127+
sysexit()
128+
129+
if sample_size <= 0:
130+
print("Invalid Input!")
131+
sysexit()
132+
133+
width, height = image.size
134+
new_width = width // sample_size
135+
new_height = (new_width * height) // width
136+
137+
return image.resize((new_width, new_height))
138+
139+
140+
def get_output_path(path):
141+
'''
142+
Function that takes in the path of the input image file and returns the
143+
path of a text file that the ouput will be saved to.
144+
'''
145+
79146
dot_index = path.rfind('.')
80147
slash_index = path.rfind('\\')
148+
81149
if slash_index == -1:
82150
slash_index = path.rfind('/')
151+
83152
image_name = path[slash_index+1:dot_index] + "_" + path[dot_index+1:]
84-
85-
# Resize image
86-
new_width = 100
87-
width, height = image.size
88-
ratio = height/width
89-
new_height = int(new_width * ratio)
90-
resized_image = image.resize((new_width, new_height))
91-
92-
# Convert image to ASCII
93-
new_image_data = pixels_to_ascii(resized_image, contrast_factor)
94-
pixel_count = len(new_image_data)
95-
scanline_width = new_width * 2;
96-
ascii_image = "\n".join([new_image_data[index:(index+scanline_width)]
97-
for index in range(0, pixel_count, scanline_width)])
98-
99-
# Save result to text file
100-
with open(path[:slash_index] + "/{}_ASCII.txt".format(image_name), "w", encoding='utf8') as f:
101-
f.write(ascii_image)
102-
103-
104-
# Run Program
153+
154+
return path[:slash_index] + f"/{image_name}_ASCII.txt"
155+
156+
157+
def main():
158+
'''
159+
The main function.
160+
'''
161+
162+
image, path = input_image()
163+
char_ramp = input_ramp()
164+
165+
contrast_factor = input_contrast()
166+
if contrast_factor < 0:
167+
char_ramp.reverse()
168+
contrast_factor = -contrast_factor
169+
170+
image = resize_image(ImageEnhance.Contrast(image).enhance(contrast_factor))
171+
ascii_image = pixels_to_ascii(image, char_ramp)
172+
173+
with open(get_output_path(path), "w", encoding='utf8') as file:
174+
file.write(ascii_image)
175+
105176
if __name__ == '__main__':
106-
photoascii()
177+
main()

0 commit comments

Comments
 (0)