-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgenerator.py
171 lines (129 loc) · 5.44 KB
/
generator.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
from PIL import Image, ImageFont, ImageDraw, ImageEnhance
from search import binarySearch, leftmost, rightmost
import os
import argparse
# returns 2D array of rgb data [0, 0] corresponding to top left corner of image
def extractRGBdata(image):
data = list(image.getdata())
width, height = image.size
rgbVals = []
count = 0
row = []
for vals in data:
row.append(vals[0:3])
count += 1
if count % width == 0: # we've finished one horizontal line of pixels, start new line
rgbVals.append(row)
row = []
return rgbVals
def printRGBvals(rgbVals):
for row in rgbVals:
for pixel in row:
print("{}\t".format(pixel), end="")
print("")
def getUnoptimizedCharDict(font):
charDict = {}
for i in range(32, 127):
# for i in list(range(32, 127)) + list(range(9617, 9620)) + list(range(9608, 9609)):
# for i in list(range(32, 127)) + list(range(9617, 9620)):
charImage = Image.new('RGB', (70, 90), (0, 0, 0)) # new blank image
draw = ImageDraw.Draw(charImage) # create ImageDraw object
draw.text((0, 0), chr(i), fill=(255, 255, 255), font=font) # draw char onto image
bIndex = getBrightnessAverage(extractRGBdata(charImage))
charDict[bIndex] = chr(i) # put our char into our dict using the format brightnessAvg:char
return charDict
# returns average brightness of a specified patch of pixels (Doesn't have to be a whole image!)
def getBrightnessAverage(rgbData):
width = len(rgbData[0])
height = len(rgbData)
bTotal = 0 # brightness total
for row in rgbData:
for rgbPixel in row:
bTotal += (sum(rgbPixel) / 3) # add brightness of each pixel
bAverage = bTotal / (width * height)
return bAverage
# this function just adjusts our character weightings to a scale of [0, 255]
# e.g. unoptCharDict may have keys [0, 84], which we don't want
def getOptimizedCharDict(font):
unoptCharDict = getUnoptimizedCharDict(font)
optCharDict = {}
min, max = getMinMaxKey(unoptCharDict)
for bAverage, char in unoptCharDict.items():
scaledB = int((255 * (bAverage - min)) / (max - min)) # truncate each key so that we can have integer indexes
optCharDict[scaledB] = char
return optCharDict
def getMinMaxKey(charDict):
keys = list(charDict.keys())
min = 999
max = -999
for key in keys:
if key < min:
min = key
if key > max:
max = key
return min, max
def getCharWeightings():
font = ImageFont.truetype("Fonts/Menlo.ttc", 100)
optimizedCharDict = getOptimizedCharDict(font)
optimizedCharDict[0] = chr(32) # manually make sure space character is included in weightings
return optimizedCharDict
# returns a list of keys in our char weightings dictionary
def getCharWeightingsKeys(weightings):
keyList = []
for key in weightings.keys():
keyList.append(key)
keyList.sort()
return keyList
def mapRGBtoChars(rgbData, weightings, keys):
rgbWidth = len(rgbData[0])
rgbHeight = len(rgbData)
# here we make sure that our ascii art always fits within the width of our terminal
terminalWidth = int(os.popen('tput cols', 'r').read()) # width is returned as str so convert to int
if rgbWidth <= terminalWidth:
pixelPatchLen = 1
else:
# ceil our number then * 2 to account for spaces taking up half our possible terminal area
pixelPatchLen= int(rgbWidth / terminalWidth + 1) * 2
for row in range(0, rgbHeight, pixelPatchLen):
for col in range(0, rgbWidth, pixelPatchLen):
pixelPatch = getPixelPatch(row, col, pixelPatchLen, rgbData, rgbHeight, rgbWidth)
patchAvgBrightness = int(getBrightnessAverage(pixelPatch))
print(getBrightnessToChar(patchAvgBrightness, weightings, keys), end=" ")
print("")
def getPixelPatch(row, col, pixelPatchLen, rgbData, rgbHeight, rgbWidth):
pixelPatch = []
for patchRow in range(row, min(row + pixelPatchLen, rgbHeight)):
pixelPatchRow = []
for patchCol in range(col, min(col + pixelPatchLen, rgbWidth)):
pixelPatchRow.append(rgbData[patchRow][patchCol])
pixelPatch.append(pixelPatchRow)
return pixelPatch
def getBrightnessToChar(bAverage, weightings, keyList):
# warning: binarySearch only returns INDEX of closest brightness value, not brightness value itself
keyIndex = binarySearch(keyList, bAverage)
if keyIndex != -1:
return weightings[keyList[keyIndex]]
pred = leftmost(keyList, bAverage)
succ = rightmost(keyList, bAverage)
if bAverage - pred > succ - bAverage:
return weightings[keyList[succ]]
else:
return weightings[keyList[pred]]
if __name__ == "__main__":
charWeightings = getCharWeightings()
charWeightingsKeys = getCharWeightingsKeys(charWeightings)
parser = argparse.ArgumentParser(prog='generator.py', description='Output an image in ascii form')
parser.add_argument('image', type=str, help='path of the image to be converted to ascii form')
args = parser.parse_args()
img = Image.open(args.image)
contraster = ImageEnhance.Contrast(img)
img = contraster.enhance(2)
mapRGBtoChars(extractRGBdata(img), charWeightings, charWeightingsKeys)
# charList = []
# for comb in charWeightings.items():
# charList.append(comb)
#
# charList.sort()
# for comb in charList:
# # print(comb[1], end=" ")
# print(comb)