使用 C# 从图像生成 ASCII 艺术






4.64/5 (22投票s)
如何从图像文件生成几种类型的 ASCII 艺术。
引言
这篇文章是一篇“纯属娱乐”的教程,面向那些足够年长,还记得 BBS 和 MUD 全盛时期的读者。我一直对那些能够创作出逼真图像的 ASCII 艺术大师们的才华感到着迷。考虑到这一点,多年后(很多年),我决定创建一个算法,可以将真实图像转换为其 ASCII 表示。即使您不以此为目的使用它,其中也应该有一些小代码片段可能会让您感兴趣,即使您不是早期互联网的粉丝。
背景
ASCII 艺术有几种类型。我在本文中尝试表示了其中至少三种风格。首先,我们将进行单字符 ASCII 绘图,并将每个字符的颜色设置为与图像中每个像素的颜色相同。接下来,我们将图像简化为灰度图,然后在 HTML 中以不同深浅的灰色输出,仍然使用单个 ASCII 字符。最后,我们将图像简化为灰度图,然后根据每个像素的灰度深浅,输出具有不同“深浅”的 ASCII 字符。这里的深浅,我仅仅是指字符在白色背景上显示有多黑。例如,“#”在白色背景上比“:”显得黑得多。
您可以尝试更改代码中的字符常量以输出不同的效果。当我颠倒输出字符的顺序以获得“负片”效果时,我得到了特别有趣的结果。
使用代码
我在解决方案中记录了代码,但我将在下面包含我最喜欢的方法供您审阅。这种方法是第三种 ASCII 艺术类型(如我上面提到的)。它将接收一个文件(通过 HTTP)上传,读取像素,获取每个像素的灰度值,然后找到合适的 ASCII 字符进行输出。
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("<br/&rt");
}
}
// Close the paragraph tag, and return the html string.
html.Append("</p&rt;");
return html.ToString();
}
catch (Exception exc)
{
return exc.ToString();
}
finally
{
bmp.Dispose();
}
}
这个静态方法是从 Default.aspx 网页表单调用的。Default.aspx 页面包含我们的文件上传输入框,以及三个调用相关方法的按钮。简而言之,我们正在执行以下操作:
- 将
Image
对象转换为Bitmap
对象 - 将输出包含在 HTML 段落标签中
- 循环遍历位图中的每个像素,并获取颜色
- 剥离像素的颜色信息(见下文)
- 根据新的深浅查找要使用的字符(见下文)
- 将所有字符聚合起来,然后返回 HTML
转换为灰度
将像素转换为灰度的最简单方法是获取每个像素的红色、绿色和蓝色分量,将每个分量的值相加后除以三,然后像这样构建新颜色:
// 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);
将颜色转换为字符
为了做到这一点,需要进行一些实验。我随演示代码包含的值似乎效果不错,但您可以随意尝试不同的字符集。为了将灰度深浅转换为字符,我们实际上只需要一个值。我选择使用新的红色值。
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;
}
您可以使用任意数量的常量。9个常量似乎提供了不错的结果。我选择的常量值如下:
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 = " ";
现在,当我们获得每个像素的灰度深浅时,我们只需输出相应的 ASCII 字符。我最喜欢的球队(丹佛野马)的标志现在显示如下:
关注点
许多人会注意到我没有使用 HTML 文本写入器来构建 HTML。简单来说,这比我需要的开销要大。StringBuilder
似乎效果很好,虽然它不能保证我拥有正确的格式,但它是迄今为止最快的解决方案。
样式由 CSS 文件(已包含)处理。因此,在开头的段落标签中添加了“class='ascii_art'”部分。您可能会注意到 VS2005 IDE 在 CSS 设计器中标记了“line-spacing”属性。不用担心……IE 和 Firefox 都知道如何处理它。行间距可以使字符靠得很近,因此行之间没有太多空白。此外,使用等宽字体(Lucida Console、Courier New、Terminal 等)至关重要,否则您的图像将极其失真。
目前,该解决方案仅在回发时对 Default.aspx 页面执行 Response.Write()
HTML。这显然可以修改为发布到单独的页面,但我为了简单性和说明起见,选择不这样做。
祝您 ASCII 愉快!