正如其他答案已经指出的那样,使用ASCII阴影字符来从16个基本颜色生成更多颜色的技术称为抖动。但是,抖动会以一些图像分辨率为代价。还可以查看传奇8088 Corruption / 8088 Domination程序。
我想为您提供一些代码,以便算法地找到颜色对和抖动阴影字符。以下方法适用于Windows / linux控制台以及通过SSH和在Windows的Linux子系统中。
一般步骤如下:
- 将源图像缩小到控制台分辨率
- 将每个像素的颜色映射到最匹配的控制台颜色
- 使用所选颜色绘制块/阴影字符
作为测试图像,我使用了一个HSV颜色映射:
![source image](https://istack.dev59.com/2LWFQ.webp)
首先,这里有16种双倍垂直分辨率的颜色。使用块字符(char)223
(▀),您可以通过使用文本/背景颜色独立绘制每个字符的上半部分和下半部分来使垂直分辨率加倍。为了匹配颜色,我使用目标和探针颜色rgb组件之间的距离向量,并强制测试所有16种不同的颜色。函数sq(x)
返回平方x*x
。
int get_console_color(const int color) {
const int r=(color>>16)&255, g=(color>>8)&255, b=color&255;
const int matches[16] = {
sq(r- 0)+sq(g- 0)+sq(b- 0),
sq(r- 0)+sq(g- 55)+sq(b-218),
sq(r- 19)+sq(g-161)+sq(b- 14),
sq(r- 58)+sq(g-150)+sq(b-221),
sq(r-197)+sq(g- 15)+sq(b- 31),
sq(r-136)+sq(g- 23)+sq(b-152),
sq(r-193)+sq(g-156)+sq(b- 0),
sq(r-204)+sq(g-204)+sq(b-204),
sq(r-118)+sq(g-118)+sq(b-118),
sq(r- 59)+sq(g-120)+sq(b-255),
sq(r- 22)+sq(g-198)+sq(b- 12),
sq(r- 97)+sq(g-214)+sq(b-214),
sq(r-231)+sq(g- 72)+sq(b- 86),
sq(r-180)+sq(g- 0)+sq(b-158),
sq(r-249)+sq(g-241)+sq(b-165),
sq(r-242)+sq(g-242)+sq(b-242)
};
int m=195075, k=0;
for(int i=0; i<16; i++) if(matches[i]<m) m = matches[k=i];
return k;
}
![image in the console with 16 colors and double vertical resolution](https://istack.dev59.com/Xpe50.webp)
16种颜色是相当有限的。因此,解决方法是使用抖动技术,混合两种颜色以获得更好的颜色,但代价是图像分辨率降低。我使用阴影字符
(char)176
/
(char)177
/
(char)178
(Windows)或
\u2588
/
\u2584
/
\u2580
(Linux);它们分别表示为 (░/▒/▓)。在我使用的12x7字体大小中,颜色混合比例分别为1:6、2:5和1:2。要找到您字体设置的混合比例,请在控制台中打印这三个阴影字符,截屏后放大并计算像素数。
这三种不同的阴影比率将16种基本颜色转换为惊人的616种颜色,不包括重复颜色。为了匹配最接近的颜色,我首先使用阴影字符比例混合颜色,然后计算目标与探针RGB颜色分量之间的距离向量,并对所有探针颜色组合进行暴力计算。为了编码使用的阴影字符以及前景和背景颜色的两种颜色,我使用位移将其全部放入一个
int
返回值中。
int get_console_color_dither(const int color) {
const int r=(color>>16)&255, g=(color>>8)&255, b=color&255;
const int red [16] = { 0, 0, 19, 58,197,136,193,204,118, 59, 22, 97,231,180,249,242};
const int green[16] = { 0, 55,161,150, 15, 23,156,204,118,120,198,214, 72, 0,241,242};
const int blue [16] = { 0,218, 14,221, 31,152, 0,204,118,255, 12,214, 86,158,165,242};
int m=195075, k=0;
for(int i=0; i<16; i++) {
for(int j=0; j<16; j++) {
const int mixred=(red[i]+6*red[j])/7, mixgreen=(green[i]+6*green[j])/7, mixblue=(blue[i]+6*blue[j])/7;
const int match = sq(r-mixred)+sq(g-mixgreen)+sq(b-mixblue);
if(match<m) {
m = match;
k = i<<4|j;
}
}
}
for(int i=0; i<16; i++) {
for(int j=0; j<16; j++) {
const int mixred=(2*red[i]+5*red[j])/7, mixgreen=(2*green[i]+5*green[j])/7, mixblue=(2*blue[i]+5*blue[j])/7;
const int match = sq(r-mixred)+sq(g-mixgreen)+sq(b-mixblue);
if(match<m) {
m = match;
k = 1<<8|i<<4|j;
}
}
}
for(int i=0; i<16; i++) {
for(int j=0; j<i; j++) {
const int mixred=(red[i]+red[j])/2, mixgreen=(green[i]+green[j])/2, mixblue=(blue[i]+blue[j])/2;
const int match = sq(r-mixred)+sq(g-mixgreen)+sq(b-mixblue);
if(match<m) {
m = match;
k = 2<<8|i<<4|j;
}
}
}
return k;
}
最后,您可以通过位移和位掩码提取阴影字符和两种颜色:
const int dither = get_console_color_dither(rgb_color);
const int textcolor=(dither>>4)&0xF, backgroundcolor=dither&0xF;
const int shade = dither>>8;
string character = ""
switch(shade) {
#if defined(_WIN32)
case 0: character += (char)176; break;
case 1: character += (char)177; break;
case 2: character += (char)178; break;
#elif defined(__linux__)
case 0: character += "\u2591"; break;
case 1: character += "\u2592"; break;
case 2: character += "\u2593"; break;
#endif
}
print(character, textcolor, backgroundcolor);
这里提供了print(...)
函数链接。生成的图像如下所示:
![image with dithering](https://istack.dev59.com/JARKx.webp)
最后,没有ASCII艺术帖子是完整的,没有Lenna测试图像。这将向您展示抖动的效果。
![lenna](https://istack.dev59.com/BuSYJ.webp)