Click here to Skip to main content
Click here to Skip to main content

Using C# To Generate ASCII Art From An Image

, 11 Sep 2007 CPOL
Rate this:
Please Sign up or sign in to vote.
How to generate several types of ASCII art from an image file.

Screenshot - bronco1.gif

Introduction

This article is a "just for fun" how-to for some of those out there old enough to remember the heyday of BBS'es and MUDs. I was always fascinated by the talent of the folks good enough to make ASCII art that looked like real images. That in mind, years later (many), I decided to create an algorithm that would convert a real image into its ASCII representation. Even if you don't use this for that purpose, there should be some little tidbits of code that you might enjoy, even if you're not a fan of the early internet.

Background

There are a few kinds of ASCII art. I've tried to represent at least three of those styles here in this article. First, we'll do a single character ASCII drawing, and just set the colors of each character to the same colors as each pixel in the image. Next, we'll reduce the image to a grayscale drawing, then output the HTML in varying shades of gray, still using a single ASCII character. Finally, we'll reduce the image to grayscale, then, based on the shade of gray for each pixel, output different ASCII characters that have a different "shade". By shade, I'm simply talking about how dark a character will appear on a white background. So, for example, "#" appears to be much darker than ":" on a white background.

You can experiment with changing the character constants in the code to output different effects. I got particularly interesting results when I reversed the order of the output characters to get a "negative" effect.

Using the code

I've documented the code in the solution, but I will include my favorite method here for review. This method is the third type of ASCII art (as I mentioned above). It will take a posted file (via HTTP), read the pixels in, get the grayscale value of each pixel, then find the appropriate ASCII character to output.

public static string GrayscaleImageToASCII(System.Drawing.Image img)
{
    StringBuilder html = new StringBuilder();
    Bitmap bmp = null;

    try
    {
        // Create a bitmap from the image

        bmp = new Bitmap(img);

        // The text will be enclosed in a paragraph tag with the class

        // ascii_art so that we can apply CSS styles to it.

        html.Append("<br/&rt;");

        // Loop through each pixel in the bitmap

        for (int y = 0; y < bmp.Height; y++)
        {
            for (int x = 0; x < bmp.Width; x++)
            {
                // Get the color of the current pixel

                Color col = bmp.GetPixel(x, y);

                // To convert to grayscale, the easiest method is to add

                // the R+G+B colors and divide by three to get the gray

                // scaled color.

                col = Color.FromArgb((col.R + col.G + col.B) / 3,
                    (col.R + col.G + col.B) / 3,
                    (col.R + col.G + col.B) / 3);

                // Get the R(ed) value from the grayscale color,

                // parse to an int. Will be between 0-255.

                int rValue = int.Parse(col.R.ToString());

                // Append the "color" using various darknesses of ASCII

                // character.

                html.Append(getGrayShade(rValue));

                // If we're at the width, insert a line break

                if (x == bmp.Width - 1)
                    html.Append("&lt;br/&rt");
            }
        }

        // Close the paragraph tag, and return the html string.

        html.Append("&lt;/p&rt;");

        return html.ToString();
    }
    catch (Exception exc)
    {
        return exc.ToString();
    }
    finally
    {
        bmp.Dispose();
    }
}

This static method is called from the Default.aspx web form. The Default.aspx page holds our File Upload input, as well as three buttons that call the associated methods. In a nutshell, we're doing the following:

  • Convert the Image object into a Bitmap object
  • Enclose the output in HTML paragraph tags
  • Loop through each pixel in the bitmap, and obtain the color
  • Strip the color information from the pixel (see below)
  • Find the character to use based on the new shade (see below)
  • Aggregate all of the characters, then return the HTML

Converting to grayscale

The simplest way to convert a pixel to grayscale is by taking each pixel's Red, Green, and Blue components, dividing the summed value for each by three, and building a new color like so:

// Get the color of the current pixel

Color col = bmp.GetPixel(x, y);

// To convert to grayscale, the easiest method is to add

// the R+G+B colors and divide by three to get the gray

// scaled color.

col = Color.FromArgb((col.R + col.G + col.B) / 3,
    (col.R + col.G + col.B) / 3,
      (col.R + col.G + col.B) / 3);

Converting colors to characters

To do this required some experimenting. The values I've included with the demo code seem to work fairly well, but feel free to experiment with different character sets. In order to convert the gray shade to a character, we really only need one value. I chose to use the new Red value.

private static string getGrayShade(int redValue)
{
    string asciival = " ";

    if (redValue >= 230)
    {
        asciival = WHITE;
    }
    else if (redValue >= 200)
    {
        asciival = LIGHTGRAY;
    }
    else if (redValue >= 180)
    {
        asciival = SLATEGRAY;
    }
    else if (redValue >= 160)
    {
        asciival = GRAY;
    }
    else if (redValue >= 130)
    {
        asciival = MEDIUM;
    }
    else if (redValue >= 100)
    {
        asciival = MEDIUMGRAY;
    }
    else if (redValue >= 70)
    {
        asciival = DARKGRAY;
    }
    else if (redValue >= 50)
    {
        asciival = CHARCOAL;
    }
    else
    {
        asciival = BLACK;
    }

    return asciival;
}

You can use as few or as many constants as you like. 9 seemed to provide good results. The constant values that I chose are are as follows:

private const string BLACK = "@";
private const string CHARCOAL = "#";
private const string DARKGRAY = "8";
private const string MEDIUMGRAY = "&";
private const string MEDIUM = "o";
private const string GRAY = ":";
private const string SLATEGRAY = "*";
private const string LIGHTGRAY = ".";
private const string WHITE = " ";

So now, as we get the gray shade for each pixel, we just output the corresponding ASCII character. The logo of my favorite team (Denver Broncos) now appears as follows:

The Denver Broncos

Points of interest

Many of you will notice that I did not use an HTML Text Writer to build the HTML. Simply put, it was more overhead than I needed. StringBuilder seemed to work very well, and though it doesn't afford me the luxury of ensuring I have the right formatting, it is by far the fastest solution.

Styling is handled by a CSS file (included). For this reason, the "class='ascii_art'" bit was added to the opening paragraph tag. You may notice that the VS2005 IDE flags the "line-spacing" attribute in the CSS designer. Don't worry... both IE and Firefox know how to handle it. Line spacing keeps the characters close so there is not a lot of whitespace between the lines. Also, it is important to use a MONOTYPE font (Lucida Console, Courier New, Terminal, etc.) for the font, otherwise your image will be extremely skewed.

Currently, the solution just performs a Response.Write() of the HTML to the Default.aspx page on postback. This could obviously be modified to post to a separate page, but I chose not to for purposes of simplicity and illustration.

Happy ASCII'ing!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

UsualDosage
Architect
United States United States
I have been an ASP.NET/C# Programmer/Software Architect for about 10 years, specializing in web architecture, user interface, and user experience. I formerly wrote business applications for mortgage banking front-ends in C++ before switching to the .NET Framework many years ago, and I've never looked back. I'm an evangelist of HTML5 and web standards, and spend the majority of my time working on front end design, performance and scale.

My primary website is located at http://www.usualdosage.com

Comments and Discussions

 
GeneralMy vote of 5 PinmemberPerić Željko29-Jul-12 9:11 
GeneralYou got my 5.. Pinmembershivam_kalra17-Dec-09 23:37 
GeneralPossibly re-inventing the wheel...there has been a similar article written prior to yours Pinmemberdaluu30-Dec-07 15:17 
GeneralRe: Possibly re-inventing the wheel...there has been a similar article written prior to yours PinmemberUsualDosage31-Dec-07 10:16 
GeneralGrayScale Pinmemberterrymohre17-Sep-07 12:02 
GeneralRe: GrayScale PinmemberUsualDosage19-Sep-07 10:09 
Generalcheck this out ... PinmemberDr Satan17-Sep-07 11:40 
GeneralRe: check this out ... PinmemberUsualDosage19-Sep-07 10:10 
Generalimages by block, not only pixel [modified] Pinmemberroberto galbiati12-Sep-07 0:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411023.1 | Last Updated 11 Sep 2007
Article Copyright 2007 by UsualDosage
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid