來自多彩世界的控制臺——C#控制臺輸出彩色字符畫

引言

看到酷安上有這樣一個活動,萌生了用 C# 生成字符畫的想法档痪,先放出原圖字柠。


酷安手繪牛啤

§1 黑白

將圖像轉(zhuǎn)換成字符畫在 C# 中很簡單探越,思路大致如下:

  1. 加載圖像,逐像素提取明度窑业。
  2. 根據(jù)明度映射到字符列表中對應的字符钦幔。
  3. 輸出字符。

GetChars函數(shù)負責將傳入的圖像按一定比例導出字符畫的字符串常柄。hScale為橫向比例鲤氢,即每次跳過的橫向像素數(shù);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是可用的字符列表,自定義只需遵循一條規(guī)則品姓,從左往右字符應該越來越復雜寝并。

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

調(diào)用函數(shù),輸出結(jié)果腹备。初具雛形衬潦,黑白樣式減少了不少神韻。


§2 有限彩色

2.1 Console

一開始希望通過改變Console.ForegroundColor屬性來改變色彩植酥,但是殘酷的事實是這個屬性只接受ConsoleColor枚舉中的 16 個顏色镀岛。將全彩圖片映射成 16 色輸出,費力不討好友驮,遂求其他方法漂羊。

2.2 Colorful.Console

找到了一個彩色控制臺的庫 Colorful Console⌒读簦看網(wǎng)頁介紹挺厲害的拨与,RGB、漸變色艾猜、多色輸出……妥了买喧,這肯定符合我們的需要捻悯,通過 nuget 可以直接添加到項目中。
在引用區(qū)域加一行淤毛,就可以把代碼中的ConsoleColorfulConsole替代今缚。

using Console = Colorful.Console;

GetChars函數(shù)需要改變一下,因為每個字符的顏色不同低淡,所以要在函數(shù)里面增加輸出姓言。好簡單,輸出內(nèi)容后面加個顏色的參數(shù)就可以了蔗蹋。

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

然而現(xiàn)實再一次殘酷起來何荚,輸出結(jié)果一片黑,使用白色背景看一看猪杭。



可能看不清餐塘,不過牛角的位置確實有幾個字符不是黑色,那我們換張圖片來看皂吮〗渖担可以看到確實有彩色輸出,不過效果尚可的僅限最前面的一些字符蜂筹,之后白色完全不見了需纳。



在測試官網(wǎng)上的操作都沒有問題后,我陷入了深深的思考艺挪,NMD不翩,為什么?直到我看到了官網(wǎng)上最下面的一段話麻裳。

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 個函數(shù)掰读。

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函數(shù)里一開始增加以下三行秘狞,-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);

因為我們要修改的是字符的前景色烁试,所以把上一節(jié)中GetChars函數(shù)里的

Console.Write(ch, color);

替換為

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

輸出結(jié)果如下,完美拢肆。


尾聲

多彩的細節(jié)减响,巧妙的象征寒跳,這就是青春啊(不是)莲蜘。
而這個項目真正的用法:


項目鏈接

推薦閱讀

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末暇矫,一起剝皮案震驚了整個濱河市鄙才,隨后出現(xiàn)的幾起案子颂鸿,更是在濱河造成了極大的恐慌,老刑警劉巖咒循,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件据途,死亡現(xiàn)場離奇詭異,居然都是意外死亡叙甸,警方通過查閱死者的電腦和手機颖医,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來裆蒸,“玉大人熔萧,你說我怎么就攤上這事×诺唬” “怎么了佛致?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長辙谜。 經(jīng)常有香客問我俺榆,道長,這世上最難降的妖魔是什么装哆? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任罐脊,我火速辦了婚禮,結(jié)果婚禮上蜕琴,老公的妹妹穿的比我還像新娘萍桌。我一直安慰自己,他們只是感情好凌简,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布上炎。 她就那樣靜靜地躺著,像睡著了一般雏搂。 火紅的嫁衣襯著肌膚如雪藕施。 梳的紋絲不亂的頭發(fā)上寇损,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機與錄音铅碍,去河邊找鬼润绵。 笑死,一個胖子當著我的面吹牛胞谈,可吹牛的內(nèi)容都是我干的尘盼。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼烦绳,長吁一口氣:“原來是場噩夢啊……” “哼卿捎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起径密,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤午阵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后享扔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體底桂,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年惧眠,在試婚紗的時候發(fā)現(xiàn)自己被綠了籽懦。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡氛魁,死狀恐怖暮顺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情秀存,我是刑警寧澤捶码,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站或链,受9級特大地震影響惫恼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜澳盐,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一祈纯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧洞就,春花似錦、人聲如沸掀淘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽革娄。三九已至倾贰,卻和暖如春冕碟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背匆浙。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工安寺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人首尼。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓挑庶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親软能。 傳聞我的和親對象是個殘疾皇子迎捺,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350