Images to Ascii art
Ascii art has always been an interesting topic to me. Though it’s style may seem a little dated it still finds plenty of use amongst modern terminal emulators today.
The idea of creating images using nothing but text also decouples viewing pictures from having a device or software capable of interpreting the file format and rendering the image to a screen.
The Methodology
The idea behind ascii art is nothing new and the technique I’m trialing is based on this 1997 piece. The essence of the method is:
- Convert the image to grayscale
- Map each pixel to a text character
- Write the text file, OR display to the console
Converting the image to grayscale
As always I underestimated the different approaches for doing this. There are multiple implementations to do with how humans percieve red, green and blue colour channels.
A blog post could stand on it’s own to list the various merits of each appraoch but I’ve opted for the simplest, averaging out each value e.g (R+G+B)/3
Mapping pixels
The real magic of ascii art happens via this character array:
$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,"^`'.
The idea being that whiteness can be represented pixel dense characters and darkness by pixel sparse characters and that’s really the whole trick.
Picking an image
I’ve chosen to attempt to recreate an image of my (recently passed :’( ) dog Dexter. This image is large, too large to insist upon a character per pixel mapping strategy.
Leaning upon some basic scaling we’ll make it smaller and apply the process above, the original:
The result:
LLqkcp0w\kmiw/[0dOhu0WWXu)>[<f[iY)QQzn!0rcY-npUJqC
dbcJm0d0h* mYYwCUbLwC%@|x)(|x\vxj-zxn?cQrcr\mUfuhZ
JoJqqkm{0wjX{Z tqO}dUh&{(__{?|Yxc\+JxrpY{p|bLJqaJ8
mdYYobpoq0JmQoOaQ0d8Wtht1]|+(U+\XpXvd_hYzu~XQqqqM}
q0xqu0pkoomdkU0ZcZp%dL%rj-?}(rv{Q-vnY#)p?qOnh}~rYX
UJ0UucwwObmpbmdpQQQUUZ8rxl1{ftxnYvCL()czr\_r}fzY}t
hUkwntavqqkmLCOQvCLUuq)j\~1v\[xxYL\Zc0Q)cYjO#uzktZ
?1jdpdLw0dZkwmOqbuoXQCkdCX?xrf+}f1CnpzbqrLu(}Xnuf1
OcXCOh]Zk0wphbXmdL@UCh#o*W&|}t1xv(Ld+frp0YJq}\#C0x
1wYfmrOJumrkpU0XuwmOqZb*o%8XCYvjf|tU{*cJ0oYXkd{/r_
COfmzxUOOYaX*aW#WWW&a&oW%%&#ZuJpYrffrt()J[fU1/u0kw
,0tX|uhbhkoQ%pqXJcv@%b#%%B%BM*hWWLc\xtjfZUbd/cQ[|u
m)aOX8#BaohvOLCJ*k*M*%8W*h%#MkmW*dL\0vwrj)k](uj\\\
\[hJLw*kxhULucuXfZZdhhWZjdMY~'*aOzZco*pozZrtv|i10p
_Z1v0|/zcwXvL&81ujpw1z%Om-%Mn^mw8b*oMM@u{aZltc8j\n
LwpY#Ct1jjYxzWoavnn/rQdO&wW@8k%*o%8WdQtW-rCl8Jzf8n
n+[]?{1)zOt0Ldb|x/xuxCIkzo%kM##8L&pmc8MXzzQkQqrZXc
~+|(t`}?[[Ovz&QtzwzU8CfxM$%Bp@bahhwxmpQaWM0Cdf(mXZ
j~) xt]X){1tQB&cw[@mQZmm8B%Wmb@W&ZChh*#*WMM@L|(cj[
l_,^,t+uc/j\MW%&zY&8WYBB%&%&cMoh#hbp*#*Mb80pCdWLr%
,I{d+.+>}<;{]nB8&qb#%M%&Bo8*oWW#%8WooWZZMQ*fQ|#Yod
.z?|?.{}}(\itzYx#B%*M888%&WWWM&%*a8&B&o#aOz0rQCL#o
n{. ?\]x\xffJmr~iI@@*h/k(khM%%BBBB%%$&@z8mzLYCMcO
"?`t{!]_i|!Udx0aI!,/\/U)}tnpaM8W%BB%B%%%@&Wnqrwhpq
f`)ji:ji{1jC00nmI"`xJcUv(r?)}a*&*8oBB%%xOcp0thuXnn
~i>C.XrQ~uww!uhr`'^}]0Urrr<)t\U&Wh*&8B@X*WzUCCdOjC
>-)f Y;xfLvwxYwZ^``Juq\f{UYx{jXzoW#BMBQuMkw0oLQboc
[t_a p0-^>Z0XJw(`l"m1LwL\rvx)\Z}n$BcB%QCwhbqCOZMaZ
(m|q\pm>vLOLZoO0,:_I,[o/n/uUCur{(Q@nZLUomCYCuJ*x#n
-(\,IUv?jOmv*Wzx:"I):CCcnn}zvj0v0m8xpcOnrUJYz%upwq
]1X:;/jU}\ZufnoI~t:I|U8X(YzXvf~)thbYYO0tddc0xZUv*O
|\1x[)1|u#o0{/8":_CZCoJrYCfY/j{c10Bd#zWZL8QwUQfOou
"-;{C({X0Zo~(Lhl-cx<ZJCYx)j(Oz<)j@YmbxmLUzOr0X*YOv
t[<qxxbdoZq1-?k>j_Jp*o1riJvxxz/+avpkmOCCu8UccUoprJ
|]jO]rQOLwX"(pr'LJfUw#}b|rzrr\~rXYYrzjYq0k*xzo0zOZ
zJt|rwommpm;0rW>)@0dp&~huYzvutjd|vdxJn|kdXUuUbqv/Y
pucZwmhhpxl:[#c:\w/Zqp\|]/cZr/YJOJn&(hLdnQmXpw0bdO
{cbdYkIUY[nUn*;m*LhkY%z>Unmn\C\YzLQuw0pwfkUfJbqUmZ
Can you see him? Neither can I…
Fixing it (kinda)
The problem with this representation is that there isn’t enough contrast, we’ll remove the background and try again:
^v\J)c.
[ bxcQkmxI
>{vqJucQczwq~
.vb0UUYXUOOa&&Q
qw8w00owr0WWMLY
0*B8Wwh&W000a&mY
.M*Wh8wZk8bmndp&Wwu
8&880uvhj#8uZX&MYL.
%8WzfttqQ/rmLYB*dJk
._8*vCYvfXZcwXaXB%wo0
m8OvcLkfxzvvzhC%&kwq
$8WbY?CwxnjvUkZ#a8Yz
Q8M%a%JqzCJLwp**%Waqu
*W&WWoZkf\CkoMMukWum0
%88kMbpwjtxkaJ*ohMLUl
a&8*bmqJJr0cw#hWwbqY?
WM&8hadkCOz\X%*W%&qMU
M&8%|)}+x1nkb88q&*hmz1
%W%8mdfu}\qMm*8o%okwhk
k888%0uYvOphwqW#Bx#oax
%8M8%8MOwozaaWM&%mWaQ(
%8&W88##z*kxqcWW%Bk#Ur
MW*&8%&cwqznW0#BBB%Mk
>*#M%8%o8dxrpdk%%8%WY
capo88M%mkkLuL#k888&8O
.kWkM&8W%@h8wohWo*MZ-
.k*&M**#h%*CdM\/wawdb
po&WMhM8kOW0#(?lwoa0w
hMW8*&aMk&W#op^:aapLc
a#&%WW&MhkMoMoYmp8WMkz
[W8%%&&&*hk##oo8Mbw&dU]
,MM&M%W&Wohooq8Wd*o*kMMI
oW88W%%MWM#WWkZ&Q*bkb8#
wC!.' 1n.&o888B%#*##oM&W%ommh#*{
'Wkwdpp8bMWM%BB%MW##a88%B%hdOo
WWWb#@@W&8*&B%B%Wak0#aa%%W08u.
x {8MMB@%%8MhdmQJq*8avo
&W%BB%%%*ahMZ**#abom
W8%%%%B%*ha*qbUa0*hX
BB%%88B%M**#kb&hdQ*^
8*B%%WWoMa#dzMbZ%o
Q@B%W8r ho#kX:|QWW
hW&, *aoY XbM
#q*mr +#M.
q&&oU `>`
8&8ac
8W#LU.
w%mUCb
.{b0*c
xjzUp
The result is certainly more satisfying, however if we increase the scaling and zoom out upon the text generated we can get some even better results:
There are multiple things that we can tweak here including the character map so we can fine tune the results. This is where I’ll be leaving it today though