ASCII 艺术生成器






4.97/5 (103投票s)
2005年7月10日
10分钟阅读

1618113

9925
ASCII Art 生成器,基于 ASP.NET。
** Windows 应用程序版本由 David Luu 提供,并非由我维护。
原始图像
ASCII Art 图像(彩色和单色)[点击放大查看]
ASCII Art 图像(纯彩色 HTML 和未格式化的纯文本)[点击放大查看]
引言
您是否曾见过一个 C# 应用程序能将给定图像转换为如上所示的基于文本的 ASCII Art 图像?
嗯,几个月前,我在 Code Project 上偶然发现了一篇 Daniel Fisher 的4 文章,讨论了创建一个这样的应用程序。据 Daniel 说,他在网上找不到任何进行图像到 ASCII 转换的 C# 应用程序,所以他决定自己写一个,因此有了他的文章。这又是一篇关于同一主题的文章,但具有稍稍增强的 ASCII Art 生成功能。我所做的是搜索了网络,找到了一些带有 PHP 图像到 ASCII 转换应用程序的网站,结合了他们的所有想法(包括 Daniel 的),并在 .NET 中实现了另一个(更增强的)ASCII Art 生成器版本。
有关我获得所有想法/信息的网站列表,或者如果您只是想了解更多关于 ASCII Art 的信息,请查看下面的“参考资料”部分。
使用代码(安装)
在上面提供的源代码文件中,您会找到以下两个 Visual Studio 项目
- ASCII - 一个演示 ASCII Art 生成器功能的 ASP.NET 网页。
- Library - 一个用于生成 ASCII Art 的库 (DLL)。
安装项目
- 首先,将文件解压缩到一个空目录。我们称之为目录 ASCIIArt。
- 接下来,在 IIS 中创建一个虚拟目录,指向上面提到的 ASCII 子文件夹。
- 之后,为 ASCII 子文件夹赋予以下(本地)用户帐户的 *读取* 权限
- IUSR_<MachineName>
- ASPNET (参见下面的注意)
- 另外,为(本地)用户帐户赋予 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 转换库的逻辑其实并不复杂。(真的不复杂!)该库源自 IMG2ASCII1、Boosty's ASCII Artist2、Daniel Fisher's ASCII Art with C# 4、ASCGEN 6 和 Playing with ColorMatrix8,基本思想如下
- 从用户提供的允许的 ASCII 字符集中,从预定义的 XML 数据文件中找出每个字符的亮度/亮度值。(数据主要基于 IMG2ASCII 1 的 ASCII.sql)
- 构建一个上述字符的数组,并按其亮度/亮度排序。
- 将给定图像调整到适当的比例/尺寸。这有两种方法。简单的方法是使用
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); }
- 现在,如果您需要修改图像的亮度、对比度、饱和度和伽马,请执行以下操作
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 的情况。如果您使用合并方法,您将需要在调整大小之前转换 *整个* 图像。这会花费大量时间。因此,最好先调整大小,然后再进行转换,因此上面提供了两种独立的方法。
当然,您可能会争辩说,如果有人想将小图像放大然后进行转换,上述情况将会颠倒。但事实是,这在现实生活中并不经常发生,所以使用这两种独立的方法仍然是更好的选择。
但是,您总是可以实现这两种方法,并根据原始尺寸与调整后尺寸的比例来选择正确的方法。这留给您自己决定。
- 在上面的 *步骤 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).
请注意,饱和度矩阵有三个特殊常量:
lumR
、lumG
和lumB
。它们代表每个 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} })); }
- 首先,您需要创建一个纯灰度(饱和度 = 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()); }
- 有时,您需要以彩色显示转换后的图像,或者以彩色显示 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>");
- 上面的代码实际上不是最优的,因为它总是用单独的
<font>
标签包围每个字符,即使相邻字符具有相同的颜色。因此,您可以优化上面的代码,使得具有相同字体颜色的相邻字符共享相同的<font>
标签。例如,HTML ASCII Art 输出
<font color="#FFCC00">W</font><font color="#FFCC00">N</font>
应该更改为
<font color="#FFCC00">WN</font>
- 最后,如果您将 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 图像文件而不是在线查看。
已知问题和建议的解决方案
- 字符权重(亮度/亮度)信息主要来自 IMG2ASCII 1,但不幸的是,它并不十分准确。
- 某些块符号与其他字符的固定宽度不同。因此,如果您使用块符号,可能会发生失真。
- 我建议使用自定义字符集来弥补以上两个问题。默认选择的自定义字符集是“
.:,;+ijtfLGDKW#
”(不含引号)。(此字符集源自 IMG2TXT 3 中使用的 ASCII 数据。) - 如果您只需要彩色 HTML,那么只需将字母 '
W
' 用作自定义字符集。 - 如果您包含 XML 数据文件中不存在的自定义字符,那么这些字符将自动从自定义字符集中删除。这是由于设计所致。
就是这样!
如果您想查看上述逻辑的完整实现,只需下载上面提供的源代码文件,然后查看源代码。请注意,本文中的 C# 代码是实际代码的简化版本;我省略了所有与本文目的无关的内容,例如错误处理和释放资源。
此外,在我的源代码文件中,我将开括号放在行的末尾,而不是新行的开头。现在,我不会和你们争论哪种风格是我们应该使用的最佳风格。就个人而言,我使用行末括号,因为我觉得这样更容易阅读长代码行。(每个行首括号都会占用一行代码,所以长代码行会变得更长。)而且,我在定位块的开始和结束时没有问题,因为使用了缩进。
除此之外,玩得开心!:)
如果您有任何建议/评论,请随时写信给我。
参考文献
在构建此 Web 应用程序时参考了以下网站
- IMG2ASCII - 开源 PHP 图像到 ASCII 转换器.
- Boosty's ASCII Artist - 一个在线 PHP 图像到 ASCII 转换器(带源代码).
- IMG2TXT - 在线图像到 ASCII 转换器.
- Daniel Fisher's ASCII Art with C#.
- Glass-giant's ASCII Artist - 在线图像到 ASCII 转换器.
- ASCGEN - 功能强大的基于 .NET 的 ASCII Art 生成器(带源代码)- 感谢 Sire404 提供此链接!.
- 多个颜色矩阵 - 在 .NET 中转换图像.
- 玩转 ColorMatrix - 转换图像的亮度、对比度、饱和度、色调和伽马.
- Linux KDE 打印库的图像转换源代码.
- MSDN - 重新着色图像.
以下网站包含有关 ASCII Art 的大量信息
待办事项列表
- 修改网页代码,允许用户更改图像的亮度、对比度和饱和度。
- 修改网页代码,允许用户仅选择图像的一部分进行 ASCII Art 输出。
- 修改网页架构,以便无需将文件上传到服务器的永久物理位置即可生成其 ASCII Art。
- 在 ASCII Art 库中实现图像的色调调整。