65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (22投票s)

2007年9月11日

CPOL

4分钟阅读

viewsIcon

125919

downloadIcon

2717

如何从图像文件生成几种类型的 ASCII 艺术。

Screenshot - bronco1.gif

引言

这篇文章是一篇“纯属娱乐”的教程,面向那些足够年长,还记得 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("&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();
    }
}

这个静态方法是从 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 字符。我最喜欢的球队(丹佛野马)的标志现在显示如下:

The Denver Broncos

关注点

许多人会注意到我没有使用 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 愉快!

© . All rights reserved.