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. 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
+
9
47
print ("Choose a Character Sequence!" )
10
48
print ("1 - Basic" )
11
49
print ("2 - Standard (Short)" )
@@ -17,90 +55,123 @@ def set_char_ramp():
17
55
18
56
try :
19
57
choice = int (choice )
20
- except Exception :
58
+ except ValueError :
21
59
print ("Invalid Input!" )
22
- exit ()
60
+ sysexit ()
23
61
24
62
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 :
35
73
custom_ramp = input ("Enter your Character Sequence from High density to Low: " )
36
74
if len (custom_ramp ) == 0 :
37
75
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 )
43
78
79
+ print ("Invalid Input!" )
80
+ sysexit ()
44
81
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
82
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
+
65
98
try :
66
99
contrast_factor = float (contrast_factor )
67
- except Exception :
100
+ except ValueError :
68
101
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
+
79
146
dot_index = path .rfind ('.' )
80
147
slash_index = path .rfind ('\\ ' )
148
+
81
149
if slash_index == - 1 :
82
150
slash_index = path .rfind ('/' )
151
+
83
152
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
+
105
176
if __name__ == '__main__' :
106
- photoascii ()
177
+ main ()
0 commit comments