1
+ """
2
+ Script that converts any Image into its ASCII representation.
3
+ """
4
+
5
+ from sys import exit as sysexit
1
6
from PIL import Image , ImageEnhance
2
7
3
- # ASCII characters used to build the output text
4
- char_ramp = []
5
8
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.
12
+ And returns a string containing the ASCII representation of
13
+ the image based on the character sequence provided.
14
+ """
15
+
16
+ pixels = image .convert ("L" ).getdata ()
17
+ characters = " " .join (
18
+ [char_ramp [int ((pixel / 256 ) * len (char_ramp ))] for pixel in pixels ]
19
+ )
20
+ pixel_count = len (characters )
21
+ scanline_width = image .width * 2
22
+
23
+ return "\n " .join (
24
+ [
25
+ characters [index : (index + scanline_width )]
26
+ for index in range (0 , pixel_count , scanline_width )
27
+ ]
28
+ )
29
+
30
+
31
+ def input_image ():
32
+ """
33
+ Function that asks user for a path to an image file
34
+ and checks for validity. Then it loads and returns the
35
+ image and the path as a tuple(image, path).
36
+ """
37
+
38
+ path = input ("Enter a valid pathname to an image:\n " )
39
+
40
+ try :
41
+ image = Image .open (path )
42
+ except IOError :
43
+ print ("Invalid Path!" )
44
+ sysexit ()
45
+
46
+ return image , path
47
+
48
+
49
+ def input_ramp ():
50
+ """
51
+ Function that asks the user to choose from a set
52
+ of character sequences or to specify their own
53
+ custom sequence and then returns that as a string.
54
+ """
55
+
9
56
print ("Choose a Character Sequence!" )
10
57
print ("1 - Basic" )
11
58
print ("2 - Standard (Short)" )
@@ -17,90 +64,140 @@ def set_char_ramp():
17
64
18
65
try :
19
66
choice = int (choice )
20
- except Exception :
67
+ except ValueError :
21
68
print ("Invalid Input!" )
22
- exit ()
69
+ sysexit ()
23
70
24
71
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 :
35
- custom_ramp = input ("Enter your Character Sequence from High density to Low: " )
72
+ return list ("00011111..." )
73
+ if choice == 2 :
74
+ return list ("@%#*+=-:. " )
75
+ if choice == 3 :
76
+ return list (
77
+ "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft"
78
+ + "/\\ |()1}{[]?-_+~<>i!lI;:,\" ^`'. "
79
+ )
80
+ if choice == 4 :
81
+ return ["█" , "▉" , "▊" , "▋" , "▌" , "▍" , "▎" , "▏" ]
82
+ if choice == 5 :
83
+ return ["█" , "▓" , "▒" , "░" , " " ]
84
+ if choice == 6 :
85
+ while True :
86
+ custom_ramp = input ("Enter character sequence ['?' for info]: " )
87
+ if custom_ramp == "?" :
88
+ print (
89
+ "The character sequence must start with characters" ,
90
+ "that represent high pixel density and end with" ,
91
+ "characters that represent low pixel density." ,
92
+ )
93
+ else :
94
+ break
36
95
if len (custom_ramp ) == 0 :
37
96
print ("Invalid Input!" )
38
- exit ()
39
- char_ramp = list (custom_ramp )
40
- else :
41
- print ("Invalid Input!" )
42
- exit ()
97
+ sysexit ()
98
+ return list (custom_ramp )
43
99
100
+ print ("Invalid Input!" )
101
+ sysexit ()
44
102
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 )
53
103
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] : " )
104
+ def input_contrast ():
105
+ """
106
+ Function that asks user for the contrast factor that is to be applied
107
+ on the image before conversion. And returns it.
108
+ """
109
+
110
+ while True :
111
+ contrast_factor = input ("Enter contrast factor ['?' for info] : " )
112
+ if contrast_factor == "?" :
113
+ print (
114
+ "Contrast factor is a value that is used to controle" ,
115
+ "the contrast of the output. Default value of the contrast" ,
116
+ "factor is 1. Negative value will invert the output." ,
117
+ )
118
+ else :
119
+ break
120
+
65
121
try :
66
122
contrast_factor = float (contrast_factor )
67
- except Exception :
123
+ except ValueError :
68
124
print ("Invalid Input!" )
69
- exit ()
70
-
71
- set_char_ramp ()
72
-
125
+ sysexit ()
126
+
127
+ return contrast_factor
128
+
129
+
130
+ def resize_image (image ):
131
+ """
132
+ Function that takes in an image and asks the user for a sample size.
133
+ Then returns a resized image with each pixel representing the sample grid.
134
+ """
135
+
136
+ while True :
137
+ sample_size = input ("Enter the sample size ['?' for info] : " )
138
+ if sample_size == "?" :
139
+ print (
140
+ "Sample size refers to the number of pixels" ,
141
+ "that will be sampled for one character." ,
142
+ "Default value of sample size is 4." ,
143
+ "Its value must be greater than or equal to 1." ,
144
+ )
145
+ else :
146
+ break
147
+
148
+ try :
149
+ sample_size = int (sample_size )
150
+ except ValueError :
151
+ print ("Invalid Input!" )
152
+ sysexit ()
153
+
154
+ if sample_size <= 0 :
155
+ print ("Invalid Input!" )
156
+ sysexit ()
157
+
158
+ width , height = image .size
159
+ new_width = width // sample_size
160
+ new_height = (new_width * height ) // width
161
+
162
+ return image .resize ((new_width , new_height ))
163
+
164
+
165
+ def get_output_path (path ):
166
+ """
167
+ Function that takes in the path of the input image file and returns the
168
+ path of a text file that the ouput will be saved to.
169
+ """
170
+
171
+ dot_index = path .rfind ("." )
172
+ slash_index = path .rfind ("\\ " )
173
+
174
+ if slash_index == - 1 :
175
+ slash_index = path .rfind ("/" )
176
+
177
+ image_name = path [slash_index + 1 : dot_index ] + "_" + path [dot_index + 1 :]
178
+
179
+ return path [:slash_index ] + f"/{ image_name } _ASCII.txt"
180
+
181
+
182
+ def main ():
183
+ """
184
+ The main function.
185
+ """
186
+
187
+ image , path = input_image ()
188
+ char_ramp = input_ramp ()
189
+
190
+ contrast_factor = input_contrast ()
73
191
if contrast_factor < 0 :
74
- global char_ramp
75
192
char_ramp .reverse ()
76
193
contrast_factor = - contrast_factor
77
-
78
- # Fetch the name of the image file
79
- dot_index = path .rfind ('.' )
80
- slash_index = path .rfind ('\\ ' )
81
- if slash_index == - 1 :
82
- slash_index = path .rfind ('/' )
83
- 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
105
- if __name__ == '__main__' :
106
- photoascii ()
194
+
195
+ image = resize_image (ImageEnhance .Contrast (image ).enhance (contrast_factor ))
196
+ ascii_image = pixels_to_ascii (image , char_ramp )
197
+
198
+ with open (get_output_path (path ), "w" , encoding = "utf8" ) as file :
199
+ file .write (ascii_image )
200
+
201
+
202
+ if __name__ == "__main__" :
203
+ main ()
0 commit comments