This article explores an iOS app, written in a functional Swift style, that converts an image to ASCII art. For example, when given the famous Lenna photograph…
…it creates a string that, when printed, looks something like this…
Zooming into her lovely face reveals that the image is actually text!
The Xcode project is available at https://github.com/ijoshsmith/swift-ascii-art
The big picture
Each pixel in an image is mapped to an ASCII value, referred to here as a symbol.
An image’s pixels are first transformed to an intermediary value, as seen below:
Let’s take this step-by-step.
First a pixel’s color is converted to a grayscale color. The grayscale color’s intensity (i.e. brightness) is normalized to a value between 0 and 1, where black is 0 and white is 1.
The next step is, to me, the most interesting part of the algorithm. Each color intensity value is translated to an ASCII symbol. Later we will visit the AsciiPalette class, which supports that.
Lastly, rows of ASCII symbols are joined to form a giant, multi-line string: the ASCII art.
AsciiArtist implements the three steps outlined above; as seen on lines 31 to 33.
An AsciiArtist object relies on Pixel and AsciiPalette, which we’ll look at next.
Transforming pixels to intensities
Pixel represents the color at a specific image coordinate. A color consists of four bytes; one byte per channel (red, green, blue, and alpha). AsciiArtist asks a Pixel to determine its color intensity, which, as previously mentioned, is calculated by normalizing the pixel’s grayscale value to a percentage. Let’s see how that works…
In case you’re wondering about those weight values on lines 47 to 49, they are industry-standard numbers used for grayscale conversion, as explained here.
Transforming intensities to symbols
The symbolFromIntensity function in AsciiArtist transforms a color intensity to an ASCII symbol, by converting a normalized intensity value to an array index. That array is provided by AsciiPalette. The first symbol in the array is used for very dark pixels and the last symbol is for very bright pixels. Here is that AsciiArtist function again, for easy reference:
The question is: which ASCII symbols should be in the array, and in what order?
A poorly designed symbol palette yields improperly shaded ASCII art:
A better symbol palette produces a vastly superior result:
My approach to designing a good symbol palette is to let the computer figure it out. The AsciiPalette class renders each symbol to a separate image, with black text on a white background. It then sorts the symbols by the number of white pixels in their images. The more white pixels in the image, the higher the symbol’s intensity. The blank space character (‘ ‘) has the highest intensity value since it contains only white pixels, and would therefore be the last symbol in the array.
The AsciiPalette designated initializer requires a UIFont argument because the choice of font affects how a character is rendered, impacting the number of white pixels around it.
The code in this article is available at https://github.com/ijoshsmith/swift-ascii-art