关于 ncurses 颜色
为什么 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_BLUE
或 COLOR_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 库的其他功能,例如如何创建窗口和框架。
资源