While browsing Stack Overflow, I generally click on one or two links from the sidebar “Hot Network Questions”. It brings me to several interesting topics, not necessarily related to development. And this time, I found an interesting post: how do ASCII art image conversion algorithms work?
Images to ASCII Art is a free program to convert images into ASCII Art. Simply open an image and the program automatically recreates the image using ASCII characters. Once the image is converted to ASCII, users can adjust the image size, font face, styles and sizes, and save the image as text file or an image. Lastly, in this site, you can find TXT2GIF format to convert ASCII text to an image, unlike other tools. Glassgiant This site is furnished with a small program that works fairly in converting an image into ASCII art. It gives you a chance to change color scheme such as black on white or white on black with a variable font and width of ASCII.
ASCII art image conversion basically consists in two steps: converting our picture into gray colors, and map each pixel to a given character depending of the grayscale value. For instance, @ is darker than +, which is also darker than .. So, let’s try to implement such an algorithm in pure JavaScript.
For those in a hurry, you can test the converter directly in final demo, or read its source code directly on its GitHub repository.
First step is to allow our user to upload a picture. Hence, we need a file input. Moreover, as we are going to manipulate image pixels, we also need a canvas. Disc cover 3 1 3.
At this step, we can send a picture to our input, yet nothing would happen. Indeed, we need to plug the file input to our canvas element. It is done using the FileReader API:
On input change, we instantiate a new FileReader which would read the file, and once done, would load it into our canvas. Note we adapt the canvas size to the image one to not truncate it. The last two arguments of drawImage is the top left image margin: we want to start drawing our image from the top left corner (coordinates [0, 0]).
If we embed previous script on our HTML page and upload Homer, we can display it into our canvas element:
Note: if you want to snap a picture from your webcam, please refer to Taking Picture From Webcam Using Canvas post. https://isnayn.over-blog.com/2021/01/synthesizer-software-free-download.html.
Now our image has been uploaded, we need to convert it into gray colors. Each pixel color can be broken into three distinct components: red, green, and blue values, as in hexadecimal (#RRGGBB) colors in CSS. Computing gray scale of a pixel is simply averaging these three values together.
However, human eye is not equally sensitive to these three colors. For instance, our eyes are very sensitive to green color, while blue is only slightly perceived. Hence, we need to ponderate each colors using different weights. After taking a look on the (very) detailed Grayscale Wikipedia Page, we can compute the grayscale value using the following formula:
So, we need to iterate on each of our picture pixel, extract its RGB components, and replace each component by its related grayscale value. Fortunately, working on a canvas allows us to manipulate each pixel using getImageData function.
The for loop requires some explanations. We retrieve each pixels in the imageData.data object. However, it is a uni-dimensional array, each pixel being splitted into its four components: red, green, blue, and alpha (for transparency). We retrieve the RGB value from the three first data cells, compute our grayscale, and then, move on of 4 indexes to go at the start of the next pixel components.
In this snippet, we modified the original image data, causing our function to be impure. Indeed, I wasn’t able to find a way to update image data using a copy of our imageData variable.
Adding a call to convertToGrayScales function at the end of our image.onload listener, we can upload a picture in gray colors:
Now that we have a list of grayscale values for every pixel, we can map each of these value to a different character. Reason behind this mapping is simple: some characters are darker than others. For instance, @ is darker than ., which occupies less space on screen.
The following character ramp is generally used for this conversion:
Hence, mapping a gray scale value to its equivalent character can be done via:
We retrieve the corresponding character using a cross-shaped product: gray scale of 0 (black) should be $, and a white pixel (gray scale of 255) should be a space. We substract 1 to rampLength as arrays start at 0 index.
Let’s translate our input image into pure characters:
We use a pre tag in order to keep aspect ratio of our picture, as it uses a monospaced font.
Calling the drawAscii method at the end of our image.onload callback, we get the following result:
At first glance, it seems it doesn’t work. Yet, if we scroll horizontally, we notice some strings wandering through the screen. Our picture seems to be on a single line. And indeed: all our values are on a single dimensional array. Hence, we need to add a break line every width value:
Result is now far better, except for a detail…
Our image ASCII representation is huge. Indeed, we mapped any single pixel to a character, spread on a lot of pixels. Drawing a 10x10 small picture would then take 10 lines of 10 characters. Too big. We can of course keep this huge text picture and reduce font-size as shown in previous picture. Yet, that’s not optimal, especially if you want to share it by email.
When browsing the Web to check how other achieve such a resolution downgrade, we often find the average method:
This technique consists in taking sub-arrays of pixels and to compute their average grayscale. Then, instead of drawing 9 white pixels for the red section above, we would draw a single one, still completly white. Apple capture program.
I first dove into the code, trying to compute this average on the unidimensional array. Yet, after an hour of tying myself in knots, I remembered the next two arguments of drawImage canvas method: the output width and height. Their main goal is to resize picture before drawing it. Exactly what we have to do! I wasn’t able to find how this is done under the hood, but I guess this is using the same average process.
Let’s clamp our image dimension:
We focus on height first. Indeed, to better appreciate the artist behind their work, we need to contemplate their art without scrolling. Also note that we keep image aspect ratio to prevent some weird distortions. We now need to update our image.onload handler to use the clamped values:
If we upload our favorite Simpson character, here is the result:
Resolution has been decreased and we can’t see as many details as before, but that’s a mandatory drawback to get shareable ASCII art.
As usual, here are the related links:
Note that we only handle static image in this case, but some people also handle live video stream, such as the ASCII camera. Useless, therefore indispensable!
Hint: Data are able to be logged for members, so it may be better to log in before use
Customer service E-mail:support@fontke.com Customer service telephone:0086-591-88231988
Smith micro poser pro v11 0 os x. © 2009-2020 Fontke.com All rights reserved. General legal adviser: Fujian Yahou Law Firm Linpeng Lawyer 闽公网安备35010202000240号闽ICP备14021033号-1