关于 ncurses 颜色

作者:Jim Hall

为什么 ncurses 只支持八种颜色?

如果您研究过 curses 中可用的调色板,您可能会想知道为什么 curses 只支持八种颜色。《curses.h》包含文件定义了这些颜色宏


COLOR_BLACK
COLOR_RED
COLOR_GREEN
COLOR_YELLOW
COLOR_BLUE
COLOR_MAGENTA
COLOR_CYAN
COLOR_WHITE

但是为什么只有八种颜色,又为什么是这些特定的颜色呢?至少在 Linux 控制台上,如果您在 PC 上运行,颜色的起源可以追溯到 PC 硬件。

颜色简史

Linux 最初是一个 PC 操作系统,因此第一个 Linux 控制台是在文本模式下运行的 PC。要理解 PC 控制台上的调色板,您需要追溯到早期的 CGA 时代。在文本模式下,PC 终端有一个 16 色的调色板,编号从 0(黑色)到 15(白色)。背景色仅限于前八种颜色

  • 0. 黑色
  • 1. 蓝色
  • 2. 绿色
  • 3. 青色
  • 4. 红色
  • 5. 品红色
  • 6. 棕色
  • 7. 白色(“浅灰色”)
  • 8. 亮黑色(“灰色”)
  • 9. 亮蓝色
  • 10. 亮绿色
  • 11. 亮青色
  • 12. 亮红色
  • 13. 亮品红色
  • 14. 黄色
  • 15. 亮白色

这些颜色可以追溯到 CGA,IBM 的彩色/图形适配器,它来自早期的 PC 兼容计算机。这是对纯单色显示器的一次升级;顾名思义,单色只能显示黑色或白色。CGA 可以显示有限的颜色范围。

CGA 支持混合红色 (R)、绿色 (G) 和蓝色 (B) 颜色。在其最简单的形式中,RGB 要么“开”,要么“关”。在这种情况下,您可以以 2x2x2=8 种方式混合 RGB 颜色。表 1 显示了 RGB 的二进制和十进制表示。

表 1. RGB 的二进制和十进制表示
000 (0) 黑色
001 (1) 蓝色
010 (2) 绿色
011 (3) 青色
100 (4) 红色
101 (5) 品红色
110 (6) 黄色
111 (7) 白色

为了使颜色数量翻倍,CGA 增加了一个额外的位,称为“增强器”位。设置增强器位后,红色、绿色和蓝色将设置为最大值。如果不设置增强器位,每个 RGB 值都将设置为“中等”强度。让我们将增强器位表示为二进制颜色表示中的额外 1 或 0,如 iRGB(表 2)所示。

表 2. 使用增强器位
0000 (0) 黑色
0001 (1) 蓝色
0010 (2) 绿色
0011 (3) 青色
0100 (4) 红色
0101 (5) 品红色
0110 (6) 黄色
0111 (7) 白色
1000 (8) 亮黑色
1001 (9) 亮蓝色
1010 (10) 亮绿色
1011 (11) 亮青色
1100 (12) 亮红色
1101 (13) 亮品红色
1110 (14) 亮黄色
1111 (15) 亮白色

但是这里有一个问题:0000 黑色和 1000 黑色是相同的颜色。没有红色、绿色或蓝色可以增强,因此无论是否设置“增强器”位,黑色都是黑色。为了解决这个限制,CGA 实际上实现了一个修改后的 iRGB 定义,使用了两个中间值,大约为三分之一和三分之二强度。大多数“正常”模式(0 到 7)颜色使用三分之二强度的值,黄色除外,黄色被分配了三分之一的绿色值,这使得颜色变为棕色或橙色。要从“正常”模式转换为“明亮”模式,请将零值转换为三分之一强度,并将三分之二值转换为全强度。

表 3 显示了颜色表的另一个迭代,每个 RGB 值的颜色范围使用 0x0 到 0xF,其中 0x5 和 0xA 分别为三分之一和三分之二强度。

表 3. 颜色表,每个 RGB 值的颜色范围使用 0x0 到 0xF,其中 0x5 和 0xA 分别为三分之一和三分之二强度
0000 (#000) 黑色
0001 (#00A) 蓝色
0010 (#0A0) 绿色
0011 (#0AA) 青色
0100 (#A00) 红色
0101 (#A0A) 品红色
0110 (#A50) 棕色
0111 (#AAA) 白色
1000 (#555) 亮黑色
1001 (#55F) 亮蓝色
1010 (#5F5) 亮绿色
1011 (#5FF) 亮青色
1100 (#F55) 亮红色
1101 (#F5F) 亮品红色
1110 (#FF5) 亮黄色
111 (#FFF) 亮白色

您可能想知道为什么只有八种背景颜色。请注意,DOS 也支持“闪烁”属性。设置此属性后,您的文本可以闪烁。


Bbbbffff

这就是一个完整的字节!从右到左数:四位表示文本前景色(0000 黑色到 1111 亮白色),三位编码背景色(000 黑色到 111 白色),一位用于“闪烁”属性。

而且,这就是 curses 如何获得 16 种文本颜色的:八种标准强度文本颜色和八种高强度文本颜色。在 Linux 控制台上,这些基本上与旧 DOS 系统中使用的颜色相同。这也是为什么您经常在一些旧的 DOS 程序员参考资料中看到“棕色”标记为“黄色”,因为至少在 DOS 系统上,它最初是普通的“黄色”,然后才出现增强器位。同样,您也可能看到“灰色”表示为“亮黑色”,因为“灰色”实际上是设置了增强器位的“黑色”。

示例程序

让我用一个简单的程序演示 Linux 终端颜色。这个颜色演示将迭代使用 curses 的所有可用颜色组合。

首先,我需要一个简单的函数来创建所有可能的颜色对


void init_colorpairs(void)
{
    int fg, bg;
    int colorpair;

    for (bg = 0; bg <= 7; bg++) {
        for (fg = 0; fg <= 7; fg++) {
            colorpair = colornum(fg, bg);
            init_pair(colorpair, curs_color(fg), curs_color(bg));
        }
    }
}

init_colorpairs() 函数还依赖于一个“转换”函数,该函数将标准强度 CGA 颜色编号(0 到 7)转换为 curses 颜色编号,使用 curses 常量名称,如 COLOR_BLUECOLOR_RED


short curs_color(int fg)
{
    switch (7 & fg) {           /* RGB */
    case 0:                     /* 000 */
        return (COLOR_BLACK);
    case 1:                     /* 001 */
        return (COLOR_BLUE);
    case 2:                     /* 010 */
        return (COLOR_GREEN);
    case 3:                     /* 011 */
        return (COLOR_CYAN);
    case 4:                     /* 100 */
        return (COLOR_RED);
    case 5:                     /* 101 */
        return (COLOR_MAGENTA);
    case 6:                     /* 110 */
        return (COLOR_YELLOW);
    case 7:                     /* 111 */
        return (COLOR_WHITE);
    }
}

为了为每种前景色和背景色创建一个可预测的颜色对编号,我还需要一个函数 colornum(),用于根据经典颜色字节设置整数位模式


int colornum(int fg, int bg)
{
    int B, bbb, ffff;

    B = 1 << 7;
    bbb = (7 & bg) << 4;
    ffff = 7 & fg;

    return (B | bbb | ffff);
}

通常指示文本闪烁的 B 位在我的颜色演示程序中未使用,因此我始终将 B 设置为 1,以保证永远不会分配颜色对 0。在 curses 中,颜色对 0 保留用于默认的前景色和背景色。那应该是黑色背景上的白色文本,但为了安全起见,我总是为黑色背景上的白色定义自己的组合。对于前景色 7(白色,二进制 111)和背景色 0(黑色,二进制 000),位模式如下所示


10000111

这是一个十进制值 135。

init_colorpairs() 之后,我的程序可以使用 curses 函数 COLOR_PAIR() 的包装器来设置每种颜色组合。我的包装器函数还使用 A_BOLD 属性打开或关闭粗体文本


void setcolor(int fg, int bg)
{
    /* set the color pair (colornum) and bold/bright (A_BOLD) */

    attron(COLOR_PAIR(colornum(fg, bg)));
    if (is_bold(fg)) {
        attron(A_BOLD);
    }
}

void unsetcolor(int fg, int bg)
{
    /* unset the color pair (colornum) and
       bold/bright (A_BOLD) */

    attroff(COLOR_PAIR(colornum(fg, bg)));
    if (is_bold(fg)) {
        attroff(A_BOLD);
    }
}

is_bold() 函数只是测试是否设置了 iRGB 值(前景色 8 到 15)上的“增强器”位,使用简单的位掩码


int is_bold(int fg)
{
    /* return the intensity bit */

    int i;

    i = 1 << 3;
    return (i & fg);
}

有了这些,创建颜色演示程序就很容易了


/* color-demo.c */

#include <curses.h>
#include <stdio.h>
#include <stdlib.h>

int is_bold(int fg);
void init_colorpairs(void);
short curs_color(int fg);
int colornum(int fg, int bg);
void setcolor(int fg, int bg);
void unsetcolor(int fg, int bg);

int main(void)
{
    int fg, bg;

    /* initialize curses */

    initscr();
    keypad(stdscr, TRUE);
    cbreak();
    noecho();

    /* initialize colors */

    if (has_colors() == FALSE) {
        endwin();
        puts("Your terminal does not support color");
        exit(1);
    }

    start_color();
    init_colorpairs();

    /* draw test pattern */

    if ((LINES < 24) || (COLS < 80)) {
        endwin();
        puts("Your terminal needs to be at least 80x24");
        exit(2);
    }

    mvaddstr(0, 35, "COLOR DEMO");
    mvaddstr(2, 0, "low intensity text colors (0-7)");
    mvaddstr(12, 0, "high intensity text colors (8-15)");

    for (bg = 0; bg <= 7; bg++) {
        for (fg = 0; fg <= 7; fg++) {
            setcolor(fg, bg);
            mvaddstr(fg + 3, bg * 10, "...test...");
            unsetcolor(fg, bg);
        }

        for (fg = 8; fg <= 15; fg++) {
            setcolor(fg, bg);
            mvaddstr(fg + 5, bg * 10, "...test...");
            unsetcolor(fg, bg);
        }
    }

    mvaddstr(LINES - 1, 0, "press any key to quit");

    refresh();

    getch();
    endwin();

    exit(0);
}

示例输出

当您运行该程序时,您会看到 16 种文本颜色和 8 种背景颜色的所有组合,总共 16x8=128 种不同的颜色对。

""

图 1. 颜色演示控制台

图 1 显示了我如何设置图形终端以反映文本模式终端,包括标准文本颜色。图形终端程序(如 GNOME 终端)支持广泛的颜色,因为它们可以利用 X Window System 的可用调色板。请注意,您可以在这些程序中更改可用颜色。大多数颜色都非常接近其控制台对应颜色,但有些颜色看起来却大相径庭。例如,GNOME 终端的默认调色板将 DOS 棕色替换为黄色(图 2)。

""

图 2. 颜色演示 GNOME 终端

通过颜色,您可以更清晰地表示信息。此颜色演示只是迭代所有颜色组合,以显示每种颜色与另一种颜色的外观。

当然,这个例子只是颜色。您可以利用 curses 做更多的事情,具体取决于您的程序需要做什么。在后续文章中,我将演示 ncurses 库的其他功能,例如如何创建窗口和框架。

资源

Jim Hall 是一位开源软件倡导者和开发人员,可能最出名的是 FreeDOS 的创始人。Jim 在 GNOME 等开源软件项目的可用性测试方面也非常活跃。在工作中,Jim 是 Hallmentum 的 CEO,这是一家 IT 执行咨询公司,帮助 CIO 和 IT 领导者进行战略规划和组织发展。

加载 Disqus 评论