const canvas = document.createElement("canvas")
const canvas = document.getElementById(<source>)
if (!source) return null
canvas.setAttribute("width", source.width)
canvas.setAttribute("height", source.height)
const context = canvas.getContext("2d")
context.drawImage(source, 0, 0)
const image_data = context.getImageData(0, 0, source.width, source.height)
const raw_data = image_data.data
This snippet creates a <canvas>
element and paints an image on it. The function getImageData
returns the raw data for the image. It's an array containing 4 bytes per image pixel (the red, green, blue and alpha channel respectively). It's possible to modify the array (named raw_data
in the snippet) to modify the image, like in the example below. For the purpose of converting to ASCII we don't need to modify it, but to read pixel color values from it to know what ASCII character to write.
In this example we have taken the RGB values and switched them up. The red has been moved to the green, the green to the blue, and the blue to the red. The result is a green-red costume, insted of a red-blue one.
We are going to map every pixel to a specific ASCII character using it's brightness. Brightness can be calculated with the formula below, which returns a number from 0 to 1, (0 being a black pixel and 1 being a white pixel).
// Traditional grayscale value
const getGrayscale = color=> (color.red + color.green + color.blue) / (255 * 3)
// Brightness value adjusts better to the humans eye's perception of color
const getBrightness = color=> (0.299 * color.red + 0.587 * color.green + 0.114 * color.blue) / 255
We are going to calculate the brightness of each pixel to map it to it's corresponding ASCII character. In our case, we are going to use the following ASCII values for the different brightness levels:
.,:;i1tfLCG08@
Please note that we are going to use a total of 15 characters, there is a whitespace for the brightest colors (it may be tough to spot). The mapping is actually pretty straightforward
// Traditional grayscale value
const getCharacter = brightness=> {
const ASCII = " .,:;i1tfLCG08@"
const end = ASCII.length - 1
return ASCII.charAt(end - Math.round(brightness * end))
}
Now we have all the tools for converting an image to ASCII, we need to fix the contrast, calculate the brightness, and map it to it's equivalent character.
There is, however, a small issue here: pixels are square, characters are not, they are larger. To fix this, instead of mapping every row of pixels we are going to map every other row. The resulting image (see below) has the same width, but half the height compared to the original.
With this final touch, we are ready to extract the characters
Let's see the magic in action.