![]() |
Web Development »
Charts, Graphs and Images »
Images and multimedia
Intermediate
ASCII Art GeneratorBy Sau Fan LeeASCII Art generator in ASP.NET. |
C#, Javascript, XML, CSS.NET 1.1, Win2K, WinXP, Win2003, ASP.NET, WebForms, IIS 5.1, IIS 6, VS.NET2003, IE 6.0, IE 5.5, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
** The Windows application port is provided by David Luu, and is not maintained by me.

Original image
ASCII Art image (colored and monochrome) [click for enlarged view]
ASCII Art image (pure color-HTML and unformatted text-only) [click for enlarged view]
Have you ever seen a C# application that converts a given image to a text-based ASCII Art image like the ones shown above?
Well, a few months ago, I stumbled across an article4 on Code Project by Daniel Fisher which talks about creating an application that does just this. According to Daniel, he was not able to find any C# application on the web that does image-to-ASCII conversions, so he decided to write his own, and hence his article. This is yet another article on the same topic, but with a slightly more enhanced ASCII Art generation. What I did was I searched through the web, found some web sites with image-to-ASCII conversion applications in PHP, combined all their ideas (including Daniel's), and implemented another (more enhanced) version of the ASCII Art generator in .NET.
For the list of web sites from where I got all my ideas/information, or if you just want to know more about ASCII Art in general, please check out the References section given below.
In the source files included above, you will find the following two Visual Studio projects:
To install the project:
Note: On some machines (especially servers), IIS doesn't actually use the ASPNET account to run ASP.NET pages. To find out exactly what account your IIS is using, just copy and paste the following code into Notepad and save it as who.aspx, then put the file into a web-folder under IIS and view it in Internet Explorer. The page will display the correct user account that ASP.NET is running under.
<%@ Page Language="C#" %>
<%@ import Namespace="System.Security.Principal" %>
<html>
<body>
ASP.NET User Account: <b><%= WindowsIdentity.GetCurrent().Name %></b>
</body>
</html>
The logic of my Image-To-ASCII conversion library is actually not that complicated. (No, really!) Derived from IMG2ASCII1, Boosty's ASCII Artist2, Daniel Fisher's ASCII Art with C# 4, ASCGEN 6, and Playing with ColorMatrix8, the basic idea is as follows:
System.Drawing.Bitmap's built-in image resize feature:
using System;
using System.Drawing;
public GetResizedImage (Image originalImage,
int newWidth, int newHeight)
{
return (new Bitmap (originalImage, newWidth,
newHeight));
}
The above method is fast, but may not produce a high-quality resized image, and you don't have the option to select only a portion/section of the image to work with. The other alternative is to use System.Drawing.Graphics, as follows:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
// NOTE: section = portion of the image you want to use.
public GetResizedImage (Image originalImage,
Rectangle section, int newWidth, int newHeight)
{
// Create an empty bitmap with the new size.
Bitmap resizedImage = new Bitmap (newWidth,
newHeight);
Rectangle imageArea = new Rectangle (0, 0,
newWidth, newHeight);
// Use Graphics object to draw the
// resized image onto the bitmap.
Graphics g = Graphics.FromImage (resizedImage);
g.InterpolationMode =
InterpolationMode.HighQualityBicubic;
g.DrawImage (originalImage, imageArea, section.X,
section.Y, section.Width, section.Height,
GraphicsUnit.Pixel);
return (resizedImage);
}
using System;
using System.Drawing;
using System.Drawing.Imaging;
// NOTE1: brightness = Amount of 'sunlight' in picture.
// = -1.0 to 1.0, -1 = pitch-black,
// 0 = original, 1 = total white
// contrast = Amount of difference between red,
// green, and blue colors.
// = 0.0 or above, 0 = complete gray,
// 1 = original, higher = glaring white
// saturation = The amount of 'grayscale-ness'
// in picture.
// = 0.0 or above, 0 = grayscale,
// 1 = original (colors),
// higher = very 'colorful'
// gamma = extra brightness correction to picture.
// = 0.0 or above, 0 = total white,
// 1 = original, higher = darker
//
// NOTE2: hue is not implemented in this version.
// NOTE3: The implementation of the CreateColorMatrix()
// method will be discussed later.
public GetTransformedImage (Image resizedImage,
float brightness, float contrast,
float saturation, float gamma)
{
// Create yet another new image for
// the color transformation.
Bitmap transformedImage =
new Bitmap (resizedImage.Width, resizedImage.Height);
Rectangle imageArea =
new Rectangle (0, 0, resizedImage.Width,
resizedImage.Height);
// Set up the image transformation parameters.
ImageAttributes transformData = new ImageAttributes();
transformData.SetColorMatrix (
CreateColorMatrix (brightness, contrast, saturation));
transformData.SetGamma (gamma);
// Transform the image.
Graphics g = Graphics.FromImage (transformedImage);
g.DrawImage (resizeImage, imageArea, imageArea.X,
imageArea.Y, imageArea.Width,
imageArea.Height, GraphicsUnit.Pixel,
transformData);
return (transformedImage);
}
Now, at this stage, you may be wondering whether we can merge Step 3 and 4 together (assuming you use the second resizing method above), the short answer is: yes. The merged version will look like the following:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
public GetResizedAndTransformedImage (
Image originalImage, Rectangle section,
int newWidth, int newHeight,
float brightness, float contrast,
float saturation, float gamma)
{
// Create an empty bitmap with the new size.
Bitmap newImage = new Bitmap (newWidth,
newHeight);
Rectangle imageArea = new Rectangle (0, 0,
newWidth, newHeight);
// Set up the image transformation parameters.
ImageAttributes transformData =
new ImageAttributes();
transformData.SetColorMatrix (CreateColorMatrix(
brightness, contrast, saturation));
transformData.SetGamma (gamma);
// Use Graphics object to draw the resized and
// transformed image onto the bitmap.
Graphics g = Graphics.FromImage (newImage);
g.InterpolationMode =
InterpolationMode.HighQualityBicubic;
g.DrawImage (originalImage, imageArea,
section.X, section.Y, section.Width,
section.Height, GraphicsUnit.Pixel,
transformData);
return (newImage);
}
However, there is a catch in using the above merged method. To illustrate this, consider the case where you want to resize a HUGE image down to a small size and then convert it to ASCII Art. If you use the merged method, you will need to transform the entire image before the resizing takes place. This takes a lot of time. Therefore, it is better to resize it first before transforming it, hence the two separate methods given above.
Of course, you may argue that if somebody wants to resize a small image to a larger one and then transform it, the above situation will be reversed. But the fact is that this doesn't happen often in real life, so using the two separate methods will still be a better option.
But then, you could always implement both ways and choose the correct way depending on the original vs. resized size ratio. I leave this up to you.
CreateColorMatrix(). This method simply calculates a proper 5x5 color matrix for the specified brightness, contrast, and saturation. This color matrix is used for transforming the image by means of matrix multiplication. In fact, the construction of this matrix is also based on matrix multiplication. In this article, I will not talk about how matrix multiplication works. But if you want to know more about it, you can check out the MSDN Site10 for an excellent explanation on this topic in relation to the usage of the ColorMatrix object.
Warning: Implementing the CreateColorMatrix() method involves a lot of mathematical details. If you find this boring, simply skip the details given below and go straight here for the source code of this method.
The following shows the actual matrix values of the brightness, contrast, and saturation matrices:
Brightness Matrix Contrast Matrix Saturation Matrix
R G B A W R G B A W R G B A W
R [1 0 0 0 0] R [c 0 0 0 0] R [sr+s sr sr 0 0]
G [0 1 0 0 0] G [0 c 0 0 0] G [ sg sg+s sg 0 0]
B [0 0 1 0 0] B [0 0 c 0 0] B [ sb sb sb+s 0 0]
A [0 0 0 1 0] A [0 0 0 1 0] A [ 0 0 0 1 0]
W [b b b 0 1] W [t t t 0 1] W [ 0 0 0 0 1]
b = brightness c = contrast s = saturation
t = (1.0 - c) / 2.0 sr = (1 - s) * lumR
Legend sg = (1 - s) * lumG
R = red sb = (1 - s) * lumB
G = green
B = blue lumR = 0.3086 or 0.2125
A = alpha (transparency) lumG = 0.6094 or 0.7154
W = white (always = 1) lumB = 0.0820 or 0.0721
- The brightness matrix is a simple translation
matrix on the RGB elements.
- The contrast matrix is a scaling matrix on the RGB elements.
The extra translation parameters in the contrast matrix is used
for shifting the base color (when c = 0)from black to gray.
- The saturation matrix re-adjust the RGB color distribution so
that at s = 0, R = G = B = luminance (brightness in grayscale).
Notice that the saturation matrix has three special constants: lumR, lumG, and lumB. These represent the proportion of each RGB value that contributes to the luminance (brightness) value. In short, luminance for a pixel is calculated as follows:
// Formula for calculating luminance
// based on NTSC standard
// (as described in ITU-R Recommendation BT.709)
double luminance =
0.2125 * red + 0.7154 * green + 0.0721 * blue;
// Alternate formula for calculating
// luminance for linear RGB space.
// (Widely used in color hue and saturation)
double luminance =
0.3086 * red + 0.6094 * green + 0.0820 * blue;
// DON'T use the following NTSC
// formula for YIQ Luminance.
// (It's not used for linear RGB space.)
// double luminance =
0.299 * red + 0.587 * green + 0.114 * blue;
From the above information, we can calculate the proper color matrix to transform a given image. To use all three matrices, we need to multiply them together into one single transformation matrix (using matrix multiplication). The result of multiplication is as follows:
R G B A W R G B A W R G B A W
R [1 0 0 0 0] R [c 0 0 0 0] R [sr+s sr sr 0 0]
G [0 1 0 0 0] G [0 c 0 0 0] G [ sg sg+s sg 0 0]
B [0 0 1 0 0] X B [0 0 c 0 0] X B [ sb sb sb+s 0 0]
A [0 0 0 1 0] A [0 0 0 1 0] A [ 0 0 0 1 0]
W [b b b 0 1] W [t t t 0 1] W [ 0 0 0 0 1]
Brightness Matrix Contrast Matrix Saturation Matrix
R G B A W
R [c(sr+s) c(sr) c(sr) 0 0 ]
G [ c(sg) c(sg+s) c(sg) 0 0 ]
===> B [ c(sb) c(sb) c(sb+s) 0 0 ]
A [ 0 0 0 1 0 ]
W [ t+b t+b t+b 0 1 ]
Transformation Matrix
So, based on the above derived transformation matrix, we can proceed to implement the CreateColorMatrix() method:
using System;
using System.Drawing.Imaging;
private const float LumR = 0.3086f; // or 0.2125f
private const float LumG = 0.6094f; // or 0.7154f
private const float LumB = 0.0820f; // or 0.0721f
private ColorMatrix CreateColorMatrix (float brightness,
float contrast, float saturation)
{
if (brightness < -1f) brightness = -1f;
if (brightness > 1f) brightness = 1f;
if (contrast < 0f) contrast = 0f;
if (saturation < 0f) saturation = 0f;
float Wf = (1f - contrast) / 2f + brightness;
float Rf = (1f - saturation) * LumR * contrast;
float Gf = (1f - saturation) * LumG * contrast;
float Bf = (1f - saturation) * LumB * contrast;
float Rf2 = Rf + saturation * contrast;
float Gf2 = Gf + saturation * contrast;
float Bf2 = Bf + saturation * contrast;
return (new ColorMatrix (new float[][]
{
new float[] {Rf2, Rf, Rf, 0f, 0f},
new float[] {Gf, Gf2, Gf, 0f, 0f},
new float[] {Bf, Bf, Bf2, 0f, 0f},
new float[] {0f, 0f, 0f, 1f, 0f},
new float[] {Wf, Wf, Wf, 0f, 1f}
}));
}
using System;
using System.Drawing;
using System.Text;
public string GetAsciiArt (Image originalImage,
Rectangle section, int outputWidth,
int outputHeight, float brightness,
float contrast, float saturation, float gamma)
{
StringBuilder asciiArt = new StringBuilder();
char[] AsciiCharSet = ... ; // From Step 2 above.
// Resize and transform image to grayscale.
Bitmap resizedImage = GetResizedImage (originalImage,
section, outputWidth, outputHeight)
Bitmap grayImage = GetTransformedImage (resizedImage,
brightness, contrast, 0, gamma);
/* dostuff 1 ... */
// Notice that we are forcing the
// saturation to be 0 (grayscale) above.
// At this stage the user-given saturation
// is not used.
// Loop through every pixel in grayscale image.
for (int y = 0; y < outputHeight; y++)
{
for (int x = 0; x < outputWidth; x++)
{
// In grayscale, R = G = B.
byte lum = grayImage.GetPixel (x, y).R;
int index =
((int) lum) * AsciiCharSet.Length / 256;
/* dostuff 2 ... */
asciiArt.Append (AsciiCharSet[index]);
/* dostuff 3 ... */
}
asciiArt.Append ("\n");
}
return (asciiArt.ToString());
}
<font> tags to change the character color.
// Replace /* dostuff 1 */ in Step 6 above
// with the following code.
Bitmap colorImage = GetTransformedImage (resizedImage,
brightness, contrast, saturation, gamma);
// Notice that we are now using the
// user-given saturation above.
// Replace /* dostuff 2 */ in Step 6 above
// with the following code.
// Get RGB values, no Alpha.
int color =
(colorImage.GetPixel (x, y).ToArgb() & 0xFFFFFF);
string hexColor = "00000" + color.ToString ("X");
asciiArt.Append ("<font color='#");
asciiArt.Append (hexColor.Substring (hexColor.Length - 6));
asciiArt.Append ("'>");
// Replace /* dostuff 3 */ in Step 6
// above with the following code.
asciiArt.Append ("</font>");
<font> tag, even if adjacent characters have the same color. Therefore, you can optimize the code given above so that adjacent characters with the same font color will share the same <font> tags.
For example, the HTML ASCII Art output:
<font color="#FFCC00">W</font><font color="#FFCC00">N</font>
should be changed to:
<font color="#FFCC00">WN</font>
FontSize = (user-given font-size in px)
LineHeight = Math.Round(FontSize * 3.0 / 5.0);
<pre style=
"font-size: FontSizepx; line-height: LineHeightpx">
...
</pre>
Besides the above logic, the library also does a few other bits and pieces to provide more functionality. However, those are not so important as far as the 'technology' of ASCII Art is concerned.
The web page uses the following information to generate the ASCII image.


.:,;+ijtfLGDKW#" (without the quotes). (This character set is derived from the ASCII data used in IMG2TXT 3.) W' for the custom character set. If you want to see the full implementation of the above logic, just download the source files given above and have a look at the source codes. Note that the C# code presented in this article is a simplified version of the actual code; I have left out all that is irrelevant to the purpose of this article, such as error-handling and freeing resources.
Also, in my source files, I have put open curly braces at the end of a line, not at the beginning of a new line. Now, I'm not going to argue with you guys about which style is the best one that we should use. Personally, I use the end-of-line curly braces because I find it easier to read long lines of code. (Each start-of-line curly brace use up one line of code, so long lines of code will get even longer.) And no, I don't have a problem locating the start and end of a block since indentation is used.
Other than that, have fun! :)
Please don't hesitate to write me suggestions/comments if you have any.
The following web sites were referred to while building this web application:
The following web site contains lots of information on ASCII Art in general:
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 28 May 2007 Editor: Sean Ewington |
Copyright 2005 by Sau Fan Lee Everything else Copyright © CodeProject, 1999-2009 Web19 | Advertise on the Code Project |