如何将一个8位颜色值拆分为两个4位颜色值?

5

所以我写了一个程序,它可以读取位图并使用windows.h在控制台中打印。

Windows(在控制台中)允许我为每个字符空间设置两种颜色 - 前景色和背景色。

对于这些颜色,我受限于4位调色板:

http://www.infotart.com/blog/wp-content/uploads/2008/06/windows_4bit_color_swatches.png

我的程序可以处理16种颜色,但我在处理256种颜色时遇到了问题(或者说我不确定能否处理256种颜色)。
我需要将索引颜色的RGB值(来自于256种8位颜色,例如224, 64, 0)显示为16种可用颜色中的两种,并将其中一种进行抖动。
前景字符将是ASCII抖动字符之一(我认为是176、177、178)。
因此,我认为每个背景都需要具有R、G、B值的0、128、255等,而前景可以是0、32、64、96、128、160、192、224或255。
因此,如果我有RGB=192,0,0的数字,我可以将背景设置为RGB=128,0,0,并将前景设置为RGB=255,0,0,使用ASCII字符176(25%抖动)。
如果我有单独的抖动字符用于红色、绿色和蓝色,那么这似乎会很简单,但不幸的是我没有。
我知道控制台是一个糟糕的选择,但我必须尝试并且不使用Windows GDI的帮助来完成这个任务。
我完全被这个算法难住了,甚至很难确定我的逻辑是否有意义。
有人能够为此提供一些帮助吗?任何帮助都将不胜感激,我已经碰到了障碍。

http://caca.zoy.org/wiki/libcaca - Ignacio Vazquez-Abrams
5个回答

2
尽管这可能不是关于从RGB到有色ASCII表示的直接答案,但8088 Corruption程序可能是一个很好的参考,可以让人们了解从位图图像到CGA屏幕的方法。
8088 Corruption程序旨在在原始IBM PC上运行全动态视频和声音(Google视频链接)
在对视频编解码器的设计进行解释时(演示文稿可在archive.org上获得),创建者尝试了几种技术,其中之一是使用“ASCII抖动字符”,但对“图片”的最终质量不满意。
因此,他继续尝试一种方法,将多个像素映射到一个ASCII字符中。例如,如果有两条垂直重叠的线,则在屏幕上绘制ASCII字符X
我实际上没有查看源代码(我相信是用x86汇编语言编写的),但从我阅读到的使用技术的描述来看,这可能值得一看。

+1 我不确定这是否提供了一个答案,但那真是太棒了!我猜你得用过旧的IBM CGA PC才能理解那是多么令人印象深刻吧!? - Clifford

1

一般来说,您需要“发明”一种从任何RGB到特定子集的彩色字符的映射。

由于确切的公式很难计算,我可能会坚持使用一个巨大的预先计算的查找表。表必须是三维的(一个维度为R,G,B),每个维度在[0..255]之间。表的每个单元格应包含三个信息(打包在2个字节中):表示字符、前景颜色和背景颜色。

表应按以下方式预先计算:对于要用作输出的每个字符,选择每个前景和背景颜色,然后计算显示该颜色的字符的结果RGB混合。然后,给定RGB混合坐标的单元格应更新为该字符和颜色的信息。

当然会有空单元格,因为我们最多只有256*16*16种彩色字符的变化,适用于256^3种颜色,因此我们必须使用某种最接近填充单元格的方法来更新空颜色。

然后,对于任何输入像素,我们只需查找该表,检索字符和颜色,并将它们放入输出中。

也可以以相反的方式工作-计算具有结果RGB混合的256x16x16表,然后搜索它以找到最适合输入RGB的混合。


0

我建议从阅读ImageMagick(Apache 2.0 许可证量化(颜色减少)文档开始。然后,您可以查看颜色量化,其中我认为最受欢迎的两种方法是中位数剪切方法或使用八叉树

你可能更喜欢在非 RGB 颜色空间 中工作,例如 Lab 颜色空间,因为它具有一些良好的特性,欧几里得距离更符合感知差异(来源:维基百科)。

有几种抖动方式,有序模式抖动、随机抖动和误差校正抖动,在这种情况下,我相信您想要使用误差校正来减少明显的颜色误差。


他在控制台模式下没有那种灵活性 - 仅有16种固定预定义颜色,每个字符空间两种颜色以及三种固定抖动图案。这就是问题所在。 - Clifford
@Clifford:是的,你说得对,我忽略了他的限制,即在MS-Windows下为控制台应用程序完成此操作。谢谢。 - mctylr

0
4位色块具有RGB值,当您将其中两个与抖动字符混合时,RGB值将是每个单独的RGB元素的加权平均值。权重取决于使用的抖动模式,因此对于棋盘格模式,每个RGB值的权重都相等,因此红色+绿色变为:
[255,0,0] + [0,128, 0] = [(255+0)/2, (0+128)/2, (0+0)/2] = [127, 64, 0](一种棕色)
其他模式的权重将由前景像素与背景像素的比例确定。
要有效地找到最接近的颜色可能是困难的部分!使用三个字符、16种颜色和两个前景/背景选项,组合数量很大,尽管我想色域中可能会有很大的间隙。如果您只需要从256色调色板转换为这些组合之一,而不是从完整的RGB转换,则简单的解决方案是编写一个程序,也许是穷举搜索每个256种颜色的最佳匹配的前景、背景和抖动组合,并生成一个查找表,然后可以在最终应用程序中使用该表进行直接查找。

当然,这种固定查找表的方法只适用于256色调色板也是固定的情况(这并不一定是这样)。如果不是这样,那么您可能需要确定更有效的查找最佳匹配颜色的方法。我相信有可能比单纯的穷举搜索更聪明。


0

正如其他答案已经指出的那样,使用ASCII阴影字符来从16个基本颜色生成更多颜色的技术称为抖动。但是,抖动会以一些图像分辨率为代价。还可以查看传奇8088 Corruption / 8088 Domination程序。

我想为您提供一些代码,以便算法地找到颜色对和抖动阴影字符。以下方法适用于Windows / linux控制台以及通过SSH和在Windows的Linux子系统中。

一般步骤如下:

  1. 将源图像缩小到控制台分辨率
  2. 将每个像素的颜色映射到最匹配的控制台颜色
  3. 使用所选颜色绘制块/阴影字符

作为测试图像,我使用了一个HSV颜色映射: source image

首先,这里有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), // color_black      0   0   0   0
        sq(r-  0)+sq(g- 55)+sq(b-218), // color_dark_blue  1   0  55 218
        sq(r- 19)+sq(g-161)+sq(b- 14), // color_dark_green 2  19 161  14
        sq(r- 58)+sq(g-150)+sq(b-221), // color_light_blue 3  58 150 221
        sq(r-197)+sq(g- 15)+sq(b- 31), // color_dark_red   4 197  15  31
        sq(r-136)+sq(g- 23)+sq(b-152), // color_magenta    5 136  23 152
        sq(r-193)+sq(g-156)+sq(b-  0), // color_orange     6 193 156   0
        sq(r-204)+sq(g-204)+sq(b-204), // color_light_gray 7 204 204 204
        sq(r-118)+sq(g-118)+sq(b-118), // color_gray       8 118 118 118
        sq(r- 59)+sq(g-120)+sq(b-255), // color_blue       9  59 120 255
        sq(r- 22)+sq(g-198)+sq(b- 12), // color_green     10  22 198  12
        sq(r- 97)+sq(g-214)+sq(b-214), // color_cyan      11  97 214 214
        sq(r-231)+sq(g- 72)+sq(b- 86), // color_red       12 231  72  86
        sq(r-180)+sq(g-  0)+sq(b-158), // color_pink      13 180   0 158
        sq(r-249)+sq(g-241)+sq(b-165), // color_yellow    14 249 241 165
        sq(r-242)+sq(g-242)+sq(b-242)  // color_white     15 242 242 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

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; // (char)176: pixel ratio 1:6
            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; // (char)177: pixel ratio 2:5
            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; // (char)178: pixel ratio 1: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 // Windows/Linux
}
print(character, textcolor, backgroundcolor);

这里提供了print(...)函数链接。生成的图像如下所示: image with dithering

最后,没有ASCII艺术帖子是完整的,没有Lenna测试图像。这将向您展示抖动的效果。 lenna


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接