Last night I was searching for an audio version of Painters and Hackers by Paul Graham.  Pretty soon I had completely forgotten about the book and found myself reading the Wikipedia article about Hackers.  Isn't Internet search great?

Of all of the things in the article, the one thing that captured my attention was the ASCII picture of Adrian Lamo.  I immediately thought, "How cool is that!?"  So instead of popping off my current stack frame and returning to searching for Painters and Hackers, I began thinking about how to create such a picture.  I certainly did not want to create it by hand.  I didn't even want to pick which characters or colors would be printed to the screen.  So accordingly, I began thinking of writing a program to do it for me.

I grabbed the latest bits and began writing code.  A few hours later, I had a working program with a few bells and whistles too.

ASCII Art

Here is an example of converting Gandalf from pixels to ASCII:

How to Convert and Image to ASCII Art

The process works like this:

Determine how big of an area will be used for displaying the ASCII art.  By default, the width of the image will be the width of the console's buffer and the height will be computed so as the preserve the ratio of width to height from the original image.

Now divide the original image into as many rectangles as will be used to display the ASCII.  For each rectangle in the original picture determine its grayscale value by averaging the grayscale values of each pixel.  The grayscale value of a pixel is:

GrayScale = .3 * Red + .59 * Green + .11 * Blue

We then increase the contrast of each region by some amount (d is the luminosity of a region, contrast is any double but usually around 1.5):

public static double Contrast(this double d, double contrast)
{
  return (((d - .5) * contrast) + .5).Bound(0, 1);
}

Finally, for each region in the ASCII art we choose from an array of possible ASCII figures (sorted) by the grayscale value for that region.  The ASCII figure with the closest luminosity is picked.

The only point that I haven't covered is how the array of ASCII figures was generated.  I could have done this by hand if I wanted to, but I didn't want to.  Instead, I generate the grayscale figures each time.  Each figure consists of a character and a console color either ConsoleColor.DarkGray, ConsoleColor.Gray, or ConsoleColor.White.  The characters are one of the alphanumeric characters or the special symbols (things like %, $, @, ...).  The program generates all of the combinations and then measures their grayscale value by drawing them to a hidden bitmap and then computing their average grayscale value.  The figures are sorted by this value and a final grayscale is built.

A Few Snippets

If you take a look at the source, you will notice that it is written rather different than most C# code.  A lot of the code resides in extension methods and there are a number of lambdas and queries.  In fact, loops are kind of rare in the code.  For example, here is a function that returns all of the pixels in a rectangular region of a bitmap.

public static IEnumerable<Color> GetPixels(this Bitmap image, Rectangle rect)
{
  return from y in rect.Top.To(rect.Bottom)
         from x in rect.Left.To(rect.Right)
         select image.GetPixel(x, y);
}

The To function is a helpful little function that I wrote.

public static IEnumerable<int> To(this int start, int end)
{
  var diff = end - start > 0 ? 1 : -1;
  for (var current = start; current != end; current += diff)
    yield return current;
}

Of course, I also could have written Iterate and then written To in terms of that.

public static IEnumerable<T> Iterate<T>(T initialValue, Func<T, bool> predicate, Func<T, T> next)
{
  for (var current = initialValue; predicate(current); current = next(current))
    yield return current;
}

public static IEnumerable<int> To(this int start, int end)
{
  var diff = end - start > 0 ? 1 : -1;
  return Iterate(start, x => x != end, x => x + diff);
}

Another interesting part is setting up the display and then restoring the original display properties of the console.

using (SetupConsole())

  ...

Where SetupConsole is defined as:

static ActionDisposable SetupConsole()
{
  var originalBackgroundColor = Console.BackgroundColor;
  var originalForegroundColor = Console.ForegroundColor;
  return new ActionDisposable(() =>
    {
      Console.BackgroundColor = originalBackgroundColor;
      Console.ForegroundColor = originalForegroundColor;
    });
}

Notice that the original state of the console is captured only in the SetupConsole method and does not clutter the rest of the code.  The closure that is created contains all of the necessary information to restore the console to its original state.  This way we hide data even from private members of the same class.  If they don't need to know, then they don't need to know.  Instead only a means for restoring the original state is provided.

I'm sure that the program can be improved a lot.  So if you have any comments, suggestions, or bugs then just post a comment.  Enjoy!

Update:

I took the comments as well as some of my own ideas and improved the ASCII art generator.  I also modified the How to Convert an Image to ASCII Art section to reflect some changes.  Thank you to everyone that contributed.

Download the Source

File iconAsciiPictureSource.zip

File iconAsciiPictureSourcev1.1.zip