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

ASCII 艺术生成器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (103投票s)

2005年7月10日

10分钟阅读

viewsIcon

1618113

downloadIcon

9925

ASCII Art 生成器,基于 ASP.NET。

** Windows 应用程序版本由 David Luu 提供,并非由我维护。

原始图像

ASCII Art 图像(彩色和单色)[点击放大查看]

ASCII Art 图像(纯彩色 HTML 和未格式化的纯文本)[点击放大查看]

引言

您是否曾见过一个 C# 应用程序能将给定图像转换为如上所示的基于文本的 ASCII Art 图像?

嗯,几个月前,我在 Code Project 上偶然发现了一篇 Daniel Fisher4 文章,讨论了创建一个这样的应用程序。据 Daniel 说,他在网上找不到任何进行图像到 ASCII 转换的 C# 应用程序,所以他决定自己写一个,因此有了他的文章。这又是一篇关于同一主题的文章,但具有稍稍增强的 ASCII Art 生成功能。我所做的是搜索了网络,找到了一些带有 PHP 图像到 ASCII 转换应用程序的网站,结合了他们的所有想法(包括 Daniel 的),并在 .NET 中实现了另一个(更增强的)ASCII Art 生成器版本。

有关我获得所有想法/信息的网站列表,或者如果您只是想了解更多关于 ASCII Art 的信息,请查看下面的“参考资料”部分。

使用代码(安装)

在上面提供的源代码文件中,您会找到以下两个 Visual Studio 项目

  • ASCII - 一个演示 ASCII Art 生成器功能的 ASP.NET 网页。
  • Library - 一个用于生成 ASCII Art 的库 (DLL)。

安装项目

  1. 首先,将文件解压缩到一个空目录。我们称之为目录 ASCIIArt
  2. 接下来,在 IIS 中创建一个虚拟目录,指向上面提到的 ASCII 子文件夹。
  3. 之后,为 ASCII 子文件夹赋予以下(本地)用户帐户的 *读取* 权限
    • IUSR_<MachineName>
    • ASPNET (参见下面的注意
  4. 另外,为(本地)用户帐户赋予 Images 子文件夹(位于 ASCII 子文件夹下)的 *读取/写入* 权限
    • ASPNET (参见下面的注意

    注意: 在某些机器上(尤其是服务器),IIS 实际上并不使用 ASPNET 帐户来运行 ASP.NET 页面。要确切了解您的 IIS 使用的是哪个帐户,只需将以下代码复制并粘贴到记事本中,并将其保存为 who.aspx,然后将文件放在 IIS 下的 Web 文件夹中,并在 Internet Explorer 中查看它。该页面将显示 ASP.NET 正在运行的正确用户帐户。

    <%@ Page Language="C#" %>
    <%@ import Namespace="System.Security.Principal" %>
    <html>
      <body>
        ASP.NET User Account: <b><%= WindowsIdentity.GetCurrent().Name %></b>
      </body>
    </html>

库逻辑

我的图像到 ASCII 转换库的逻辑其实并不复杂。(真的不复杂!)该库源自 IMG2ASCII1Boosty's ASCII Artist2Daniel Fisher's ASCII Art with C# 4ASCGEN 6Playing with ColorMatrix8,基本思想如下

  1. 从用户提供的允许的 ASCII 字符集中,从预定义的 XML 数据文件中找出每个字符的亮度/亮度值。(数据主要基于 IMG2ASCII 1ASCII.sql
  2. 构建一个上述字符的数组,并按其亮度/亮度排序。
  3. 将给定图像调整到适当的比例/尺寸。这有两种方法。简单的方法是使用 System.Drawing.Bitmap 的内置图像调整功能
    using System;
    using System.Drawing;
    
    public GetResizedImage (Image originalImage, 
                         int newWidth, int newHeight)
    {
        return (new Bitmap (originalImage, newWidth, 
                                          newHeight));
    }

    上述方法速度很快,但可能无法产生高质量的调整后图像,并且您无法选择仅部分/区域的图像进行处理。另一种选择是使用 System.Drawing.Graphics,如下所示

    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);
    }
  4. 现在,如果您需要修改图像的亮度、对比度、饱和度和伽马,请执行以下操作
    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);
    }

    现在,在这个阶段,您可能想知道我们是否可以将 *步骤 3* 和 *4* 合并在一起(假设您使用了上面的第二种调整方法),答案很简单:*是的*。合并后的版本如下所示

    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);
    }

    但是,使用上述合并方法有一个 *陷阱*。为了说明这一点,请考虑您想将一个 *巨大* 的图像缩小到一个小尺寸,然后将其转换为 ASCII Art 的情况。如果您使用合并方法,您将需要在调整大小之前转换 *整个* 图像。这会花费大量时间。因此,最好先调整大小,然后再进行转换,因此上面提供了两种独立的方法。

    当然,您可能会争辩说,如果有人想将小图像放大然后进行转换,上述情况将会颠倒。但事实是,这在现实生活中并不经常发生,所以使用这两种独立的方法仍然是更好的选择。

    但是,您总是可以实现这两种方法,并根据原始尺寸与调整后尺寸的比例来选择正确的方法。这留给您自己决定。

  5. 在上面的 *步骤 4* 中,我们使用了一个名为 CreateColorMatrix() 的自定义方法。此方法仅计算指定 *亮度*、*对比度* 和 *饱和度* 的合适 5x5 颜色矩阵。此颜色矩阵用于通过矩阵乘法转换图像。实际上,此矩阵的构建也基于矩阵乘法。在本文中,我不会讨论矩阵乘法是如何工作的。但如果您想了解更多,可以查看 MSDN 网站10,它对与 ColorMatrix 对象的使用相关的该主题进行了出色的解释。

    警告: 实现 CreateColorMatrix() 方法涉及大量的数学细节。如果您觉得这很枯燥,请跳过下面的详细信息,直接转到 这里 获取此方法的源代码。

    以下是 *亮度*、*对比度* 和 *饱和度* 矩阵的实际矩阵值

    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).

    请注意,饱和度矩阵有三个特殊常量:lumRlumGlumB。它们代表每个 RGB 值对亮度(亮度)值贡献的比例。简而言之,像素的亮度计算如下

    // 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;

    根据以上信息,我们可以计算出正确的颜色矩阵来转换给定图像。要使用这三个矩阵,我们需要将它们相乘得到一个单一的转换矩阵(使用矩阵乘法)。乘法结果如下

         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

    因此,基于上述推导的转换矩阵,我们可以继续实现 CreateColorMatrix() 方法:

    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}
        }));
    }
  6. 首先,您需要创建一个纯灰度(饱和度 = 0)的转换图像。亮度和对比度可以由用户指定。然后,对于灰度图像中的每个像素,获取该像素的亮度/亮度,从上面 *步骤 2* 中构建的字符数组中找到具有相似亮度/亮度比例的匹配 ASCII 字符,然后输出该字符。
    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());
    }
  7. 有时,您需要以彩色显示转换后的图像,或者以彩色显示 ASCII Art 输出。在这种情况下,您需要根据用户指定的饱和度创建一个额外的转换图像。然后,对于生成的每个 ASCII 字符,根据转换后的彩色图像中相应像素的颜色对其进行着色。例如,如果您在网页上显示 ASCII Art,您可以使用 HTML 的 <font> 标签来更改字符的颜色。
    // 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>");
  8. 上面的代码实际上不是最优的,因为它总是用单独的 <font> 标签包围每个字符,即使相邻字符具有相同的颜色。因此,您可以优化上面的代码,使得具有相同字体颜色的相邻字符共享相同的 <font> 标签。

    例如,HTML ASCII Art 输出

    <font color="#FFCC00">W</font><font color="#FFCC00">N</font>

    应该更改为

    <font color="#FFCC00">WN</font>
  9. 最后,如果您将 ASCII Art 显示在网页上,您应该将整个 ASCII 图像包装在一个格式正确的块中,以减小字符之间的间距。您可以使用样式表来实现此目的
    FontSize = (user-given font-size in px)
    LineHeight = Math.Round(FontSize * 3.0 / 5.0);
    
    <pre style=
       "font-size: FontSizepx; line-height: LineHeightpx">
    ...
    </pre>

除了上述逻辑之外,该库还进行了一些其他方面的处理,以提供更多功能。但是,就 ASCII Art 的“*技术*”而言,这些并不那么重要。

网页

网页使用以下信息生成 ASCII 图像。

  • 图像 URL 或上传新图像。
  • 在 ASCII 图像中使用所有字母。
  • 在 ASCII 图像中使用所有数字。
  • 在 ASCII 图像中使用所有基本符号(非 Unicode 符号,与字体无关的亮度)
  • 在 ASCII 图像中使用所有扩展符号(非 Unicode 符号,依赖于字体的亮度)
  • 在 ASCII 图像中使用所有块符号(Unicode 符号:块、竖线等)
  • 仅使用自定义用户定义的字符集。
  • 用户定义的字体大小。
  • 用户定义的背景颜色。
  • 仅使用单一(用户定义)字体颜色。
  • 使用多种字体颜色(全彩)。
  • 使用多种字体颜色(灰度)。
  • 输出图像下采样(减小图像尺寸),从 1x 到 16x。
  • 自定义输出图像尺寸(宽度 & 高度),可以是像素或百分比。
  • 仅文本输出(无样式表格式,适用于在记事本或其他纯文本编辑器中显示的文本图像)。
  • 下载生成的 ASCII 图像文件而不是在线查看。

已知问题和建议的解决方案

  1. 字符权重(亮度/亮度)信息主要来自 IMG2ASCII 1,但不幸的是,它并不十分准确。
  2. 某些块符号与其他字符的固定宽度不同。因此,如果您使用块符号,可能会发生失真。
  3. 我建议使用自定义字符集来弥补以上两个问题。默认选择的自定义字符集是“ .:,;+ijtfLGDKW#”(不含引号)。(此字符集源自 IMG2TXT 3 中使用的 ASCII 数据。)
  4. 如果您只需要彩色 HTML,那么只需将字母 'W' 用作自定义字符集。
  5. 如果您包含 XML 数据文件中不存在的自定义字符,那么这些字符将自动从自定义字符集中删除。这是由于设计所致。

就是这样!

如果您想查看上述逻辑的完整实现,只需下载上面提供的源代码文件,然后查看源代码。请注意,本文中的 C# 代码是实际代码的简化版本;我省略了所有与本文目的无关的内容,例如错误处理和释放资源。

此外,在我的源代码文件中,我将开括号放在行的末尾,而不是新行的开头。现在,我不会和你们争论哪种风格是我们应该使用的最佳风格。就个人而言,我使用行末括号,因为我觉得这样更容易阅读长代码行。(每个行首括号都会占用一行代码,所以长代码行会变得更长。)而且,我在定位块的开始和结束时没有问题,因为使用了缩进。

除此之外,玩得开心!:)

如果您有任何建议/评论,请随时写信给我。

参考文献

在构建此 Web 应用程序时参考了以下网站

  1. IMG2ASCII - 开源 PHP 图像到 ASCII 转换器.
  2. Boosty's ASCII Artist - 一个在线 PHP 图像到 ASCII 转换器(带源代码).
  3. IMG2TXT - 在线图像到 ASCII 转换器.
  4. Daniel Fisher's ASCII Art with C#.
  5. Glass-giant's ASCII Artist - 在线图像到 ASCII 转换器.
  6. ASCGEN - 功能强大的基于 .NET 的 ASCII Art 生成器(带源代码)- 感谢 Sire404 提供此链接!.
  7. 多个颜色矩阵 - 在 .NET 中转换图像.
  8. 玩转 ColorMatrix - 转换图像的亮度、对比度、饱和度、色调和伽马.
  9. Linux KDE 打印库的图像转换源代码.
  10. MSDN - 重新着色图像.

以下网站包含有关 ASCII Art 的大量信息

待办事项列表

  • 修改网页代码,允许用户更改图像的亮度、对比度和饱和度。
  • 修改网页代码,允许用户仅选择图像的一部分进行 ASCII Art 输出。
  • 修改网页架构,以便无需将文件上传到服务器的永久物理位置即可生成其 ASCII Art。
  • 在 ASCII Art 库中实现图像的色调调整。

历史

  • 2005年7月12日
    • 文章:添加了一些额外的 ASCII Art 示例图像。
    • 文章:添加了“参考资料”部分。
    • 文章:添加了“历史”部分。
    • 文章:文字表达进行了小范围的修饰。
  • 2005年7月13日
    • 文章:添加了两个额外的 ASCII Art 示例图像。
  • 2005年7月16日
    • 代码:改进了 ASCII Art 库中的图形处理逻辑。
    • 文章:添加了“待办事项列表”部分。
    • 文章:根据代码的最新更改,修改了“库逻辑”部分中的大量内容。
  • 2007年5月23日
    • 添加了 David Luu 提交的 Windows 桌面 GUI 版本。
© . All rights reserved.