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

时间:2010-12-30 02:55:47

标签: c windows algorithm programming-languages colors

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

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

我仅限于这些颜色的4位调色板:

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

我的程序可以正常使用16种颜色,但是我很难找到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帮助的情况下尝试这样做。

我完全难以为此找出算法,甚至在查看我的逻辑是否有任何意义时也遇到了麻烦。

有人能够对此有所了解吗?所有的帮助表示赞赏,我已经碰壁了。

5 个答案:

答案 0 :(得分:1)

虽然这可能不是关于从RGB到彩色ASCII表示的直接答案,但8088 Corruption程序可能是一个很好的参考,可以了解人们从位图图像到CGA屏幕。

8088腐败计划旨在运行full-motion video with sound on an original IBM PC (Google Video link)

在解释视频编解码器是如何设计的(演示文稿可在archive.org获得)时,创作者尝试了几种技术,其中一种是使用“ASCII抖动字符”,但不满意“图片”的最终质量。

因此,他继续尝试一种方法,将多个像素映射为ASCII字符。例如,如果有两条线垂直重叠,则会在屏幕上绘制ASCII字符X

我实际上没有看过源代码(我相信它是用x86汇编编写的),但是根据我所阅读的技术描述,它可能值得一试看看。

答案 1 :(得分:0)

通常,您必须“发明”从任何RGB到您的特定彩色字符子集的映射。

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

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

当然,会有空单元格,因为我们最多只有256 * 16 * 16种颜色字符可供256 ^ 3种颜色使用,因此我们必须使用某种最近的最近填充单元格来更新空白颜色。 / p>

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

可以以相反的方式工作 - 使用生成的RGB混合物计算256x16x16表,然后搜索它以找到最适合输入RGB的混合物。

答案 2 :(得分:0)

我建议您阅读ImageMagick(Apache 2.0 licensequantization(色彩还原)文档作为起始位置。然后,您可以查看color quantization,其中我认为最常用的两种方法是median cut方法,或使用octrees

您可能也喜欢使用非RGB color space,例如Lab color space,因为它有一些不错的属性,欧几里德距离与感知差异更为一致(Src:Wikipedia )。

有几种dithering,有序模式抖动,随机抖动和纠错抖动,在这种情况下,我相信您希望纠错以减少明显的颜色错误。

答案 3 :(得分: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色调色板也是固定的(不一定是这种情况)。如果不是,那么您可能需要确定一种更有效的方法来找到最佳匹配颜色。我确信它可能比仅仅是详尽的搜索更聪明。

答案 4 :(得分: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(...) 函数 here。生成的图像如下所示: image with dithering

最后,没有 Lenna 测试图像,任何 asciiart 帖子都不完整。这向您展示了抖动的预期结果。 lenna