ASCII art ()
[sketch]
- Tools: Python
- Source code: https://github.com/vec2pt/py-sketches
- Links:
* The image 4.1.04.tiff (and its fragment) from the USC-SIPI Image Database was used for example.
import string
from itertools import product
import numpy as np
from PIL import Image, ImageDraw, ImageFont
def min_max_scaling(array: np.ndarray) -> np.ndarray:
"""Min-Max scaling.
Args:
array: Array
Returns:
Min-Max Scaled array
"""
min_val = np.min(array)
max_val = np.max(array)
return (array - min_val) / (max_val - min_val)
def get_char_size(
font: ImageFont.ImageFont | ImageFont.FreeTypeFont,
kerning_offset: int = 0,
leading_offset: int = 0,
) -> tuple[int, int]:
"""Font character size.
Args:
font: Font
kerning_offset: Font kerning offset
leading_offset: Font leading offset
Returns:
Font character size (width, height)
"""
chars = string.ascii_letters + string.digits + string.punctuation
_, _, right, bottom = np.array([font.getbbox(c) for c in chars]).T
return (np.max(right) + kerning_offset, np.max(bottom) + leading_offset)
def ger_char_brightness(
char: str,
font: ImageFont.ImageFont | ImageFont.FreeTypeFont,
char_size: tuple[int, int],
) -> np.floating:
"""Measure character brightness.
Args:
char: Character
font: Font
char_size: Font character size
Returns:
Brightness
"""
image = Image.new("L", char_size)
draw = ImageDraw.Draw(image)
draw.text((0, 0), text=char, fill=255, font=font)
image_array = np.array(image)
return np.mean(image_array)
def ascii_art(
image: Image.Image,
font: ImageFont.ImageFont | ImageFont.FreeTypeFont,
ascii_width: int = 80,
chars: str = " '.0:HIJLM",
inversion: bool = False,
kerning_offset: int = 0,
leading_offset: int = 0,
as_image: bool = False,
) -> str | Image.Image:
"""Image to ASCII art converter.
Args:
image: Image
font: Font
ascii_width: ASCII art width (chars)
chars: Characters
inversion: Image inversion.
kerning_offset: Font kerning offset
leading_offset: Font leading offset
as_image: Return image
Returns:
ASCII art string / image
"""
char_size = get_char_size(font, kerning_offset, leading_offset)
measurement = np.array(
[ger_char_brightness(c, font, char_size) for c in chars]
)
chars_brightness = min_max_scaling(
measurement if inversion else -measurement
)
# Image processing
image_width, image_height = image.size
aspect_ratio = char_size[0] / char_size[1]
ascii_height = np.round(
image_height * ascii_width / image_width * aspect_ratio
).astype(int)
image_array = np.array(image.resize((ascii_width, ascii_height)))
scaled = min_max_scaling(image_array)
# Image representation as a matrix of symbol indices.
indices = np.abs(
scaled.reshape(*scaled.shape, 1) - chars_brightness.reshape(1, 1, -1)
).argmin(axis=2)
# String output
if not as_image:
ascii_art = "\n".join("".join(chars[i] for i in row) for row in indices)
return ascii_art
# Image.Image output
output_image = Image.new(
"L", (char_size[0] * ascii_width, char_size[1] * ascii_height), 255
)
draw = ImageDraw.Draw(output_image)
for i, j in product(range(ascii_width), range(ascii_height)):
x, y = char_size[0] * i, char_size[1] * j + leading_offset
draw.text((x, y), chars[indices[j][i]], 0, font)
return output_image
if __name__ == "__main__":
font = ImageFont.truetype(font="Inconsolata-Regular.ttf", size=16)
image = Image.open("4.1.04.tiff").convert("L")
chars = string.ascii_letters + string.digits + string.punctuation + " "
ascii_str = ascii_art(image=image, font=font, chars=chars)
print(ascii_str)
33zzzIs[s[xlTJtt777tt77tLL(L()JTlxlJ7t(cvvvi??vLtJ7tJTTfx[fJTTTllff[[sIzzIIIsT~
FIs[[s[[xflJt(()t)7t()(LccLLLL)))t)))(LLcvccvL)((tt((()t77tt7JJJJ7JlfxssIs[[[T~
s[fT((t7JJ77J777Jtt)Lcivvcvvccvvi**vLLLcvicL((LccL)LccL))(L())tt(t7tt7TTTTTTTt:
s[xTLcLL((tTTJ7))()LLcvvvvv?***vT3akw4Syssss[xJvvvvvvvvccccccLL(LcLtttL(t)cc(L:
x[ltt))(tt7JJJ7)LcLccvi?*?**vsAED8$b66dK8@#6wSA37i??iiiiiivvcLtt)(LLLcccL(LLt)~
t7))t(LcL)t(LttLcvcvvi?*1?swdDKH8Dqbmk68&MQ@8K#q62[c?*????iivcL(t)LcvvcLL(t77c:
)LcLcvvvvLcccLcvvvi***11z688$8008#qdkAXK@ggQQ0@88K62[v*11**?iivvccvvvic(Lccvi*,
LLccvivcLLcvvvvvv?*1|/?5888@g@$mdbKdAzSm8@pd$0QQ@Kdk223L*11*?ivvvvi???vvvvv?**,
cviiiiivcccviiii*11/\7m@80Q0@Ddddd6SoyA%$08dwH08$DKK6XddSt*1*?ivvvi*>>?vvi??i?,
t)vi???ivvvviii*111>zK@0gW08pbpdEEUXw2ySd88KK880QQQ08Kqd@DSJ****?ii*>1?i???ii*,
)Lv?**??i??i??*11*tS88@&W08K@@K#8$q65o3n3oo2yIIV6@&08mdbKQWmz>1***???iiiiiivi*-
t(Li******????*1?T4$@@@QW@0g0#6$q4os(1+!!^";__;"+xw88qdH@QM0w?111****??iii??i*'
((cv?*****ii?**vzwb$80&QQQQQ@SS4sv1/+^""""_:-,~;^=3D@8$@gMBQac11**11******???*'
cvii?****ii?**vFwK$bK@00QQQQ@ESyi>+!^^""";;~::_;"^*6DK80QQMMKC?*****1********1-
***?ii????***iyXK@pmqDH8@0QQQ@6nt?1r+^"""";__~__""!xdK0&QQQQQ#s?***??****11111-
??iviiii?*11*lw#Dqd6dbKHKK@QQDo7v*vJy2eL!^""""!/1?**ypQQQQWQWQmx11*****1111111,
vvcvvi?***11v2K@Hddd$@HKmdU5y[[FVflaSlyVt/^"^r)Iyos?\sWMQQQ00@H#Sv11**1111111|-
vvvvi??*111*lU8@@Hd6SUH8mmVt***Lt*/tv^!*J?^"+++^zXs2[?qQ&0008#dDDx\\11111111*>'
i??ii?**11*isXD8@@D655%d6qy7v1=!!++!^^!|?*+^+!^!=r\r\=SbXqgMQKd6Al1/>111111111,
?****11111*iIEdp8@$Dq4A5ZKSs(?r!^^^^^^=1*1!^!+^^""^^!>%dD88@8K4yzsL*1111111111,
*****11111*?ISdqpK88pUwa4@4nlv|!^^^^^+*vi>!"^+!";;"^+l@WQ0$dXSyJL?*1111111111|,
*111|>1|rr>*LaKDpK8$ddG4#KS3Ixv1\++!+>*tCs?=//+!^^^!1dMQQ&@$mU5ex?r/r|>111111|-
11r/////==/*V#8$$mqd6k4D@KSIs[Jv*1*1=++=//+!^^!/r=\12Qgg000000@@$wT|\//r|1111>,
111/\\===rLA6D$HKmd655kb$8dyfTt*++*zsv1r/++\1|/+++1[@Q08@0@DK$888q4[*r////rrr\'
111r\=+!!i2d#$KK$#6d6w466dHdos7?=!^!**+!!!+r*!""^/[$Q&@8KddoVyo4w5AFli1/\/r/\+'
11|//\=+1zkp@08H8dSX6UEE$$@g8GyJ?|!^!++\\++!^"^+v50008H8kIy4d5o5d66EUSl*//\=++'
11>||//r1sZDg000@dVaXddwGp@0@8mXVT1+^""";_;;"!1y@Q@88pbDXS60Q0KqKKH8gby(*+++++'
11|r|///\|[m&g&008ZoS4ddXw6$@8m6Z2ef?\!+!^^!!/yK$qD8$$8#b8gW0&@8K@0g@6y**=!++!'
|rrrrr\=++/V&gg0g&@654ZG66dq$8$dSyyV2oIL?1r+!LddK@0@@8KqpH8$$#dqq#8HUyv/>/+++!'
\/rrr/\\++!r6QWg0QQ0KdEkwX6#K8HD%Anx)v*r\+++1VzAm00@K6AAkdKH8HKq6SeJ*|\\r\+++!'
/r/////=+!!!Ld8@0000@8HKKK$8$8HKd5yTi*1r\=\?yTr(V6KpkS2VSk6db$0g6T1/+==++++++!'
//\\/\+++!/itCwm8@@@$d#D#dmqK8ppd4eTv?*11*Tnv++?[od8$bdGZVn2Ub$qo)1r++++++++++'
=+++==+++!*woo6DDKH8$mdd6%UXdqdd65on[Jt7[FI*+!!iFe6@0@H#q6X5VnysJL(7?**|\++++!'
+!!!++++!!/oUm@@8$K8Kd%XUddZS6dd%Sl[FCCIIf*=+!!+vIy6@W@K4yzscivcv?vLvccvi*1/+!'
++=/\+!!!!+Td@@00@@@pEwASE52la42oo)cTIz[sv|=!!!!+*L[SD$6oFFyT******?vviii**?1='
//r/+!!^^\sd@@g0@@@@Kd6kS4aI*vyTcJ(vTVosTns*++=+=/r>?sSw6dd6F*11*?****???*1111,