来自多彩世界的控制台——C#控制台输出彩色字符画

· 浏览次数 : 0

小编点评

## Summary of the content This document describes the creation of colorful characters using C# and the challenges faced along the way. **Key Points:** * **Inspiration:** The project originated from wanting to generate characters using C# by drawing on a Bitmap image. * **Challenges:** * Dealing with different image formats and colors. * Mapping brightness values to characters. * Handling the console background and foreground colors. * **Solutions:** * Using Colorful Console library for efficient color output. * Implementing custom ANSI color codes for more flexibility. * Utilizing the `ENABLE_VIRTUAL_TERMINAL_PROCESSING` flag to access deeper color control. * **Result:** * Achieves colorful character generation with proper foreground and background. * Requires additional libraries and kernel32 functions for implementation. **Overall:** This project showcases the challenges and solutions involved in creating colorful characters with various image formats and color combinations. It demonstrates the power of libraries and custom solutions in achieving desired visual effects.

正文

引言

看到酷安上有这样一个活动,萌生了用 C# 生成字符画的想法,先放出原图。


 
酷安手绘牛啤

 
 

§1 黑白

将图像转换成字符画在 C# 中很简单,思路大致如下:

  1. 加载图像,逐像素提取明度。
  2. 根据明度映射到字符列表中对应的字符。
  3. 输出字符。

GetChars函数负责将传入的图像按一定比例导出字符画的字符串。hScale为横向比例,即每次跳过的横向像素数;vScale为纵向比例,在控制台中输出推荐为hScale的 2 倍。

private static string GetChars(Bitmap bmp, int hScale, int vScale)
{
    StringBuilder sb = new StringBuilder();
    for (int h = 0; h < bmp.Height; h += vScale)
    {
        for (int w = 0; w < bmp.Width; w += hScale)
        {
            Color color = bmp.GetPixel(w, h);
            float brightness = color.GetBrightness(); // 这里的明度也可以使用 RGB 分量合成
            char ch = GetChar(brightness);
            sb.Append(ch);
        }
        sb.AppendLine();
    }
    return sb.ToString();
}

GetChar负责做明度到字符的映射工作,由于brightness取值范围为 [0, 1],所以需要乘 0.99 防止index越界。listChar是可用的字符列表,自定义只需遵循一条规则,从左往右字符应该越来越复杂。

private static readonly List<char> listChar = 
    new List<char>() { ' ', '^', '+', '!', '$', '#', '*', '%', '@' };
private static char GetChar(float brightness)
{
    int index = (int)(brightness * 0.99 * listChar.Count);
    return listChar[index];
}

调用函数,输出结果。初具雏形,黑白样式减少了不少神韵。


 
 

§2 有限彩色

2.1 Console

一开始希望通过改变Console.ForegroundColor属性来改变色彩,但是残酷的事实是这个属性只接受ConsoleColor枚举中的 16 个颜色。将全彩图片映射成 16 色输出,费力不讨好,遂求其他方法。

2.2 Colorful.Console

找到了一个彩色控制台的库 Colorful Console。看网页介绍挺厉害的,RGB、渐变色、多色输出……妥了,这肯定符合我们的需要,通过 nuget 可以直接添加到项目中。
在引用区域加一行,就可以把代码中的ConsoleColorfulConsole替代。

using Console = Colorful.Console;

GetChars函数需要改变一下,因为每个字符的颜色不同,所以要在函数里面增加输出。好简单,输出内容后面加个颜色的参数就可以了。

private static string GetChars(Bitmap bmp, int hScale, int vScale, bool shouldDraw)
{
    StringBuilder sb = new StringBuilder();
    for (int h = 0; h < bmp.Height; h += vScale)
    {
        for (int w = 0; w < bmp.Width; w += hScale)
        {
            Color color = bmp.GetPixel(w, h);
            float brightness = color.GetBrightness();
            char ch = GetChar(brightness);
            if (shouldDraw)
            {
                Console.Write(ch, color);
            }
            sb.Append(ch);
        }
        if (shouldDraw) { Console.WriteLine(); }
        sb.AppendLine();
    }
    return sb.ToString();
}

然而现实再一次残酷起来,输出结果一片黑,使用白色背景看一看。


 
 

可能看不清,不过牛角的位置确实有几个字符不是黑色,那我们换张图片来看。可以看到确实有彩色输出,不过效果尚可的仅限最前面的一些字符,之后白色完全不见了。


 
 

在测试官网上的操作都没有问题后,我陷入了深深的思考,NMD,为什么?直到我看到了官网上最下面的一段话。

Colorful.Console can only write to the console in 16 different colors (including the black that's used as the console's background, by default!) in a single console session. This is a limitation of the Windows console itself (ref: MSDN), and it's one that I wasn't able to work my way around. If you know of a workaround, let me know!

Colorful.Console只能同时输出 16 种颜色,果然原版Console能接受的ConsoleColor枚举也是 16 种颜色是算计好的。可恶,难道只能到此为止了吗?
我不甘心。

§3 全彩

终于,我找到了这个 visual studio - Custom text color in C# console application? - Stack Overflow。在下面 Alexei Shcherbakov 和 Olivier Jacot-Descombes 的回答中,我看到了希望。

Since Windows 10 Anniversary Update, console can use ANSI/VT100 color codes
You need set flag ENABLE_VIRTUAL_TERMINAL_PROCESSING(0x4) by SetConsoleMode
Use sequences:
"\x1b[48;5;" + s + "m" - set background color by index in table (0-255)
"\x1b[38;5;" + s + "m" - set foreground color by index in table (0-255)
"\x1b[48;2;" + r + ";" + g + ";" + b + "m" - set background by r,g,b values
"\x1b[38;2;" + r + ";" + g + ";" + b + "m" - set foreground by r,g,b values
Important notice: Internally Windows have only 256 (or 88) colors in table and Windows will used nearest to (r,g,b) value from table.

有了这个神奇的ENABLE_VIRTUAL_TERMINAL_PROCESSING(0x4),就可以随意修改前后景颜色了。说干就干,首先需要增加一个NativeMethods类,用来 Call kernel32.dll里的 3 个函数。

using System;
using System.Runtime.InteropServices;

namespace Img2ColorfulChars
{
    internal class NativeMethods
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool SetConsoleMode(IntPtr hConsoleHandle, int mode);
        
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool GetConsoleMode(IntPtr handle, out int mode);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr GetStdHandle(int handle);
    }
}

然后在主程序Main函数里一开始增加以下三行,-11代表STD_OUTPUT_HANDLE(GetStdHandle function - Windows Console | Microsoft Docs), 0x4就是上面所说的ENABLE_VIRTUAL_TERMINAL_PROCESSING

var handle = NativeMethods.GetStdHandle(-11);
NativeMethods.GetConsoleMode(handle, out int mode);
NativeMethods.SetConsoleMode(handle, mode | 0x4);

因为我们要修改的是字符的前景色,所以把上一节中GetChars函数里的

Console.Write(ch, color);

替换为

Console.Write($"\x1b[38;2;{color.R};{color.G};{color.B}m{ch}");

输出结果如下,完美。


 
 

尾声

多彩的细节,巧妙的象征,这就是青春啊(不是)。
而这个项目真正的用法:


 
 

项目链接

推荐阅读

 

 



作者:Kabuto_W
链接:https://www.jianshu.com/p/8a083421c11d
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

与来自多彩世界的控制台——C#控制台输出彩色字符画相似的内容:

来自多彩世界的控制台——C#控制台输出彩色字符画

引言 看到酷安上有这样一个活动,萌生了用 C# 生成字符画的想法,先放出原图。 酷安手绘牛啤 §1 黑白 将图像转换成字符画在 C# 中很简单,思路大致如下: 加载图像,逐像素提取明度。 根据明度映射到字符列表中对应的字符。 输出字符。 GetChars函数负责将传入的图像按一定比例导出字符画的字符

Java并发Map的面试指南:线程安全数据结构的奥秘

简介 在计算机软件开发的世界里,多线程编程是一个重要且令人兴奋的领域。然而,与其引人入胜的潜力相伴而来的是复杂性和挑战,其中之一就是处理共享数据。当多个线程同时访问和修改共享数据时,很容易出现各种问题,如竞态条件和数据不一致性。 本文将探讨如何在Java中有效地应对这些挑战,介绍一种强大的工具——并

【算法】湖心岛上的数学梦--用c#实现一元多次方程的展开式

每天清晨,当第一缕阳光洒在湖面上,一个身影便会出现在湖心小岛上。她坐在一块大石头上,周围被茂盛的植物环绕,安静地沉浸在数学的世界中。 这个姑娘叫小悦,她的故事在这个美丽的湖心小岛上展开。每天早晨,她都会提前来到湖边,仔细观察水下的植物,然后抽出时间来钻研一元x次方程。她身上的气息混合着湖水的清新和植

[转帖]Linux 与 Unix 到底有什么不同?

https://www.oschina.net/translate/differences-between-linux-and-unix?print 如果你是一名20多岁或30多岁的软件开发人员,那么你已成长在一个由Linux主导的世界中。数十年来,它一直是数据中心的重要参与者,尽管很难找到明确的操

[转帖]把VIM打造成一个真正的IDE(2)

作者是 Dante 发布于 2009年10月17日 in Vim. OK,上一篇文章,我们已经配置好了一个可以正常使用的VIM,那么在我们真正来到程序员的VIM世界之前,希望你能在VIM里面再多加下面几个配置。 set go= "无菜单、工具栏 对,让我真正抛弃鼠标,进入美妙的VIM之旅吧! 首先说

任正非:天空足够大,世界会越来越兴盛

中国将来如果建立自己的标准体系,那这个标准体系肯定要比美国的好。美国的标准是从1970年代开始建立的,已经50多年了。他们的衣服补来补去,到处是补丁。我们这几年新做衣服,为什么还要照着它那个标准做呢?我们直接做比美国好的标准。除了中国用,全世界都会用。

以开发之名 | bilibili会员购让IP在眼前动起来

随着ACG文化(二次元文化)影响力的不断提升,哔哩哔哩平台上衍生品消费群体不断扩大,手办行业迅速崛起。2017年,B站推出ACG衍生品消费品牌bilibili会员购,涵盖二次元手办销售等多项业务,拓展了IP内容的消费边界,致力于满足Z世代用户的IP文化娱乐消费需求。 多年来,bilibili会员购高

C#学习笔记--面向对象三大特征

C#核心 面向对象--封装 用程序来抽象现实世界,(万物皆对象)来编程实现功能。 三大特性:封装、继承、多态。 类与对象 声明位置:namespace中 样式:class 类名{} 命名:帕斯卡命名法(首字母大写) 实例化对象:根据类来新建一个对象。Person p=new Person(); 成员

如何使用前端表格控件实现多数据源整合?

前言 作为表格产品的典型应用场景之一,几乎所有的行业都会存在类 Excel 报表开发这样的应用场景,而在这些应用场景中,经常会遇见下面的这些痛点: 报表数据往往来自多个不同的数据源,需要报表系统能够同时连接多个数据源,并融合不同的数据格式 实际的报表中需要对数据结果进行逻辑计算,例如销售的环比和同比

如何在前端应用中合并多个 Excel 工作簿

本文由葡萄城技术团队于博客园原创并首发。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。 前言|问题背景 SpreadJS是纯前端的电子表格控件,可以轻松加载 Excel 工作簿中的数据并将它们呈现在前端浏览器应用的网页上。 在某些情况下,您可能需要将来自多