Partager via


Linq to ASCII Art

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

Comments

  • Anonymous
    February 22, 2007
    the link does seem to work. Is there any other place i could download the sample and analyze the interesting code?

  • Anonymous
    February 22, 2007
    Try it now (I was fiddling with it a few minutes ago), if it still doesn't work for you then I'll find a better place to put it tomorrow.

  • Anonymous
    February 22, 2007
    And make sure you refresh the page since it was probably cached (the link changed).

  • Anonymous
    February 22, 2007
    The comment has been removed

  • Anonymous
    February 22, 2007
    Check the aalib project from back when and especially their wonderful demo called bb. AAlib description: AAlib is an portable ascii art GFX library. Link: http://aa-project.sourceforge.net/aalib/

  • Anonymous
    February 22, 2007
    The comment has been removed

  • Anonymous
    February 22, 2007
    The comment has been removed

  • Anonymous
    February 22, 2007
    I like your SetupConsole idea. But couldn't you take it a step further and wrap it in an IDisposable. Something like public class ActionDisposable : IDisposable {  Action _action  public ActionDisposable(Action action)  {    _action = action;  }  public void Dispose()  {    _action();  } } Then your code becomes: using (SetupConsole()) { ... } // restore now called implicitly Where SetupConsole is defined as: static IDisposable SetupConsole() {  var originalBackgroundColor = Console.BackgroundColor;  var originalForegroundColor = Console.ForegroundColor;  return new ActionDisposable(() =>    {      Console.BackgroundColor = originalBackgroundColor;      Console.ForegroundColor = originalForegroundColor;    }); }

  • Anonymous
    February 22, 2007
    I like it.  RAII to the rescue!

  • Anonymous
    February 22, 2007
    I made an image to ascii converter in C and made it into a site at http://www.asciiconvert.com Cheers!

  • Anonymous
    February 23, 2007
    Very interesting, but I have one [probably stupid] question.  What's with the "this" keyword for the image parameter in the GetPixels method? GetPixels(this Bitmap image, Rectangle rect)

  • Anonymous
    February 23, 2007
    Sean, This is one of the new language features in the upcoming C# 3.  Notice that the Bitmap class does not already have a method named GetPixels(Rectangle).  When you make a static method whose first parameter is preceded by 'this', you get to pretend that it is a method on that first parameter's class.  The first parameter can even just be an interface, so you can define a helper method against IEnumerable<T> and then every single IEnumerable<T> type that you have can be used as if that new method were a member.

  • Anonymous
    February 23, 2007
    I remember doing ASCII art 20 years ago...  I progressed to animated ASCII art; now that was cool.

  • Anonymous
    February 23, 2007
    Christian: Nice site.  I like the automatically generated gallery and you even have some of your pictures on wikipedia. Peter: I totally agree, ascii animation is awesome.

  • Anonymous
    February 23, 2007
    In your code, I notice the new ?? operator.  I think it's pretty clear what that one is doing, but I hadn't heard of any new operators before now.  It's hard to google for these things, but I found that there is also a !! operator that I don't quite get (how is it different from ||, for instance?).  Are there other new operators?

  • Anonymous
    February 23, 2007
    Totally minor point but I notice that your To function is basically the same as Enumerable.Range(int start, finish).  Your way could be considered more readable though.  Nicely mimicks ranges in Ruby.

  • Anonymous
    February 23, 2007
    The comment has been removed

  • Anonymous
    February 23, 2007
    Patrick: ?? is the only "new" operator that we have introduced since C# 1.1.  It actually first appears in C# 2.0 and is called the null coalescing operator.  If a programmer writes a ?? b then this is equivalent to a != null ? a : b. We don't have a !! operator in C#.  Although a programmer could write !!a which means not (not a)) which is equilavent to writing a.  In C and C++ programmers will write !!a to convert some value to either zero or one because the not operator in those languages does not require a boolean operand.  In Haskell the prelude includes a !! operator that indexes into a list.  So ['a','b','c'] !! 1 is equal to 'b'. Jafar: Yes, it is very close to the Enumerable.Range function.  Except that To is an extension method that takes the start as the implicit receiver.  I like writing  0.To(5) instead of  Enumerable.Range(0, 5) Derek: Thanks for the heads up about the book.  I'll probably check it out next week to read while I fly to a career fair.

  • Anonymous
    February 23, 2007
    FYI, here's where I read about !!, whether that article is accurate or not :) http://notgartner.wordpress.com/2006/11/26/c-30-adds-the-because-justification-operator/

  • Anonymous
    February 23, 2007
    Thanks for the link.  That article is hilarious.  I love it!! (notice the use of the just because operator)

  • Anonymous
    February 25, 2007
    looks simpler as a code, good idea to try to clearify and delete the confusion that linked Linq to sql generation, i posted on my blog trieng to show another use of epression trees. u can check it out on Implementing Domain Friendly, Predicates-like Specifications with C# 3.0 Expression trees http://sadekdrobi.com/?p=30 will be interested by your opinion.

  • Anonymous
    March 01, 2007
    Welcome to the twenty-second Community Convergence, the March CTP issue. I'm Charlie Calvert, the C#

  • Anonymous
    March 01, 2007
    Welcome to the twenty-second Community Convergence, the March CTP issue. I&#39;m Charlie Calvert, the

  • Anonymous
    March 02, 2007
    Hey, this is in regards to the general application of closures and debuggability. As you have demonstrated with your handling of console state closures can be a very succinct way of encapsulating state that it may be reinstated later. It seems to me though that in doing so you are effectively transferring ownership of said state to some anonymous type. If I stopped the program, would this state be readily available? I think not. That is not to say that it is not a valid technique, just a heads up that overuse could make you state more opaque, especially to those less versed in functional techniques.

  • Anonymous
    March 02, 2007
    Duncan: That is a great point to bring up.  You can in fact inspect the state just by expanding the delegate in ActionDisposable.  Since every delegate that is closed over some variables will have a Target field which will in turn contain all of the hoisted variables (transitively).  Perhaps we should consider adding a debugger feature to automatically show the captured locals of a delegate when inspecting the delegate.  Very good point indeed...

  • Anonymous
    March 02, 2007
    I had some compile issues (using March CTP): in EnumerableExtensions.cs: The type or namespace name 'Func' could not be found (are you missing a using directive or an assembly reference?) --> Adding "using System.Linq;" does solve it. ActionDisposable.cs: Using the generic type 'System.Action<T>' requires '1' type arguments --> Not sure how to fix this? Any comments on this? Thankx, Harry

  • Anonymous
    March 03, 2007
    Harry: The CTPs are actually about 2-3 months behind what we have actually done.  In the current code, all of the Funcs are in System not System.Linq and System also includes Action delegates (not just the one that takes one type parameter).  To fix it for the March CTP simply add the "using System.Linq" and then add the following delegate: delegate void Action();

  • Anonymous
    March 04, 2007
    "Perhaps we should consider adding a debugger feature to automatically show the captured locals of a delegate when inspecting the delegate" - I am sold already. Whatever can be done to surface state during debugging is valuble work indeed.

  • Anonymous
    March 06, 2007
    De entre la avalancha de vídeos, documentos y enlaces que con los que nos abrumó Charlie Calvert simultáneamente

  • Anonymous
    March 20, 2007
    The comment has been removed

  • Anonymous
    March 20, 2007
    Mark: Yes, that is a great idea and will implement it when I find the time.  If anyone gets to it first please post the source.

  • Anonymous
    March 20, 2007
    Hi there, I was just wondering if anyone can help me to extract the grayscale values of the different characters from the program, like printing the character and its grayscale value to the console or similar. I am trying to create a simple ASCII converter in Common Lisp and I don't really understand C#. Thank you in advance. Surya

  • Anonymous
    March 21, 2007
    Have you tried displaying the characters to a hidden buffer and then sampling from that?

  • Anonymous
    March 21, 2007
    The comment has been removed

  • Anonymous
    March 21, 2007
    posted my playing experience of playing with your little library here and VS Orcas March: http://judahgabriel.blogspot.com/2007/03/fun-with-c-3-programming.html

  • Anonymous
    March 21, 2007
    Surya: That works.  What kind of errors are you getting?  It works fine on our side (the code is actually a QA test now). Judah: Very cool.  Nice family photos.  Glad you are having fun with it.

  • Anonymous
    March 23, 2007
    Hi wesdyer, I found another way to get the grayscale values of the characters. I think I used the wrong program to compile. Does it need the newest VS Orcas? Sorry for all the trouble, I'm new to the C# concept. Thanks again for all the help. Surya

  • Anonymous
    March 26, 2007
    It is possible that you are using the wrong program to compile it.  Use Visual Studio Orcas to compile it.  Specifically try using the March CTP or Beta 1 when it comes out.  The May 2006 CTP, may (no pun intended) not work.

  • Anonymous
    April 17, 2007
    this is cool http://photo2text.com

  • Anonymous
    April 08, 2009
    Hey, i'm sorry, im a total noob, but your program looks amazing. When you download the code what do you do with it to get it working? Craig

  • Anonymous
    May 28, 2010
    The comment has been removed

  • Anonymous
    May 28, 2010
    The comment has been removed

  • Anonymous
    June 06, 2010
    The comment has been removed

  • Anonymous
    October 19, 2010
    The comment has been removed