Converting an Image to ASCII Art in C#

Because we all need a Will Smith meme in ASCII art form.
Apr 23 2009 by James Craig

I'm not sure if I have to tell you what ASCII art is or not... I might as well talk about what it is just in case. ASCII art is a graphic design technique where you use ASCII characters to represent something. I'm fairly sure that if you're on my site you know what the heck it is and you've seen it numerous times... Anyway, there is a technique of taking existing images and converting them to ASCII art.

The way this technique works, is we figure out the brightness of an individual pixel and insert the correct ASCII character that corresponds with that brightness (obviously looping through the image doing this for each pixel. This is done in only a couple of steps:

  1. Figure out the ASCII characters we want to use
  2. Convert the image to black and white
  3. Loop over the image and insert ASCII characters for pixels...

So there really isn't much to it... But lets look at each step:

What ASCII Characters Should I Use #

Generally speaking you want a range of characters with each one having a slightly different brightness and weight. For example:

"#", "@", "%", "=", "+", "*", ":", "-", ".", " "

The characters above go from dark to light. Each of those characters will represent a range within the pixels. For example, if we used the characters above in that order, we'd have each character taking up a range of 28.333. The more characters, the tighter the range but figuring out where each character ranks in terms of brightness can be difficult.

Convert The Image To Black And White #

I've covered this before here. If you really wanted to, you could add up the values and divide by 3 (considering accuracy isn't really needed in ASCII art conversion) but you might as well use the ColorMatrix. Another thing you may want to do on this step is resize the image. ASCII art tends to stretch the image's height so scrunching it vertically can be done at this stage to help fight that (or you can just ignore every other line like I'm going to do).

Loop Over The Image #

 /\*
Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.\*/

#region Usings
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;
#endregion

namespace Utilities.Media.Image
{
/// <summary>
/// Used to create ASCII art
/// </summary>
public class ASCIIArt
{
#region Public Static Functions

/// <summary>
/// Converts an image to ASCII art
/// </summary>
/// <param name="Input">The image you wish to convert</param>
/// <returns>A string containing the art</returns>
public static string ConvertToASCII(Bitmap Input)
{
bool ShowLine = true;
using (Bitmap TempImage = Image.ConvertBlackAndWhite(Input))
{
BitmapData OldData = Image.LockImage(TempImage);
int OldPixelSize = Image.GetPixelSize(OldData);
StringBuilder Builder = new StringBuilder();
for (int x = 0; x < TempImage.Height; ++x)
{
for (int y = 0; y < TempImage.Width; ++y)
{
if (ShowLine)
{
Color CurrentPixel = Image.GetPixel(OldData, y, x, OldPixelSize);
Builder.Append(\_ASCIICharacters\[((CurrentPixel.R \* \_ASCIICharacters.Length) / 255)\]);
}

}
if (ShowLine)
{
Builder.Append(System.Environment.NewLine);
ShowLine = false;
}
else
{
ShowLine = true;
}
}
Image.UnlockImage(TempImage, OldData);
return Builder.ToString();
}
}

#endregion

#region Private Variables

private static string\[\] \_ASCIICharacters = { "#", "#", "@", "%", "=", "+", "\*", ":", "-", ".", " " };

#endregion
}
}

The code above uses a couple of functions from my utility library, mainly Image.LockImage, UnlockImage, etc. These functions are there to speed things up a bit but can be removed/replaced with the built in GetPixel function (or you can simply download my utility library and get access to pretty much everything that I've ever posted on my site). Anyway, the code above loops through the image, takes each pixel and multiplies the R value (although G or B would work as well since this is greyscale) by the number of entries in our ASCII character array, then dividing it by 255. This in turn gets us the character that we're going to use. You'll notice that it toggles on and off for every other line (it's the cheap way of fighting the vertical stretching that ASCII art tends to do to an image). Also, you'll notice that it uses my utility library for converting the image to black and white.

Anyway, that's all there is to it really. Converting an image to ASCII art, as you can see above, is incredibly simple. The only difficult portion is figuring out the brightness of an individual character but the characters I use above work fairly well. So try it out, leave feedback, and happy coding.