读取控制台颜色调色板的RGB值

5

在C或C++中,有没有直接读取颜色调色板RGB值的方法?特别感兴趣的是xterm(和其他终端)使用的扩展色彩空间,可以定义高达256种终端颜色。

土豆

问题在于,我想定义自己的颜色(使用ANSI转义序列,例如\e]4;3;rgb:cc/78/33\e\\,或直接在c中定义),但我需要在重新定义它们之前保存用户的颜色(以防万一他们已经重新定义了它们),这样当我的程序完成时就可以恢复它们。覆盖用户设置不好。

目前,我正在尝试以客户端方式使用ANSI转义序列来解决这个问题。但由于我找不到如何获取颜色,所以开始考虑在c或c++中解决这个问题。

该解决方案将编写为一个Ruby gem,并带有本地扩展(基本上是嵌入式c或c++代码),我将努力获得跨平台解决方案,即使主要目标是OS X,其次是Linux环境...

酱汁

从我的初始实验中,我已经可以为颜色调色板中的代码点定义任何颜色。我也可以轻松地恢复默认的系统颜色(因为它们符合ANSI标准)。我已经找了很多方法以使用ANSI转义代码实现这一点,但没有找到。我想这是在某个地方保存在内存中的,如果有办法找到它,读取颜色就应该很容易...

甜点

总结迄今为止评论中的信息:

看起来唯一一致的做法是在不同的颜色下打印屏幕上的█字符,并从中获取颜色。由于这个项目应该跨越三个主要操作系统的平台,并且Linux目前有3个显示管理器正在使用或即将使用,而Windows有两个(7和8),我只能想象将是多么令人愉快的几个小时 :)

所以我的“解决方案”™是只覆盖用户的颜色(如果他们有除系统默认值之外的任何东西...实际上是相当少见的)。我将提供一个设置文件,用户可以告诉插件应该还原哪些颜色,如果他们对系统默认值不满意。务实而无聊,但这让我再次开始了这个项目 :)


愚蠢的问题,但你是否在SO上查找了一些相关的问题?例如,这个问题讨论了关于获取区域或整个屏幕的几种解决方案的变化。我想知道是否可以使用像::GetPixel这样的东西适用于你? - ryyker
我已经阅读了SO和SuperUser等网站上发现的所有相关问题。这里的问题是,您处于控制台环境中,因此获取像素值可能不是一个选择,因为您无法依赖于有图形环境。我真正想要的是一个转义序列或直接访问内存中的颜色表的方法。不过似乎不可能 :/ - Jonas Schubert Erlandsson
MS Windows将16个控制台颜色保存在一个16x3的字节数组中(palette[16][3])(3表示RGB值)。这个调色板数组从系统中提取出来,经过处理后再通过API放回系统中,这些API可以放入你自己的getpalette()和setpalette()函数中。如果你所针对的其他操作系统也有类似的API,那么你可以根据目标操作系统在编译时定义来修改你的get-setpalette()函数。我可以分享一些功能性的MS Windows代码,但只能根据请求分享...因为在跨平台请求中发布特定于操作系统的代码是不被赞同的。 - greg spears
3个回答

2

[编辑1] 抱歉,这并没有解决问题,但对于其他人,我添加了DAC调色板IO访问

看一下旧的EGA / VGA参考资料...

  • 您可以通过I / O访问调色板
  • 我认为它是端口0x03C8、0x03C9十六进制。
  • 当然,在现代操作系统中,您无法访问它
  • 因此,在DOS-BOX或任何其他地方尝试并保存原始调色板值,它们应该相同。

要进行直接访问,请尝试以下操作:

BYTE r,g,b,c=5; // R,G,B values are 6 bit only !!!
out 0x3C8,c;    // set color index to work with <0,255>
in  r,0x3C9;    // read color pallete for color c
in  g,0x3C9;    // not sure if it should be r,g,b 
in  b,0x3C9;    // or b,g,r ... i did not use it for too many years
out 0x3C8,c;    // set color index to work with <0,255>
out 0x3C9,r;    // write color pallete for color c
out 0x3C9,g;
out 0x3C9,b;

C/C++ 没有 in、out 操作,因此使用以下代码:

BYTE i,o;       // this must be local !!!
WORD port;      // this must be local !!!
asm {
    mov dx,port // in i,port
    in al,dx
    mov o,al

    mov dx,port // out port,o
    mov al,o
    out dx,al
    }

正如问题所指出的那样,这是关于保留用户颜色而不仅仅是恢复系统默认值的问题。获取默认值的静态映射足够容易 :) - Jonas Schubert Erlandsson
有了EGA/VGA的IO访问权限,您可以单独获取和设置256色调色板中的任何颜色,这是大多数控制台通常操作的。唯一的问题是您是否具有IO访问权限... - Spektre
PS. 调色板存储在图形卡上,而不是CPU访问的内存空间中,因此这样就是您想要的内存访问方式...如果控制台不在文本模式下,则仍应模拟256色EGA/VGA模式。如果不是,则它实际上不是控制台,只是一些仿真,唯一的方法是先前提到的屏幕GetPixel(屏幕而不是窗口!!!)。在这种情况下,只需编写具有不同颜色的256个字符并读取其字体颜色(由于抗锯齿需要添加一些过滤)。 - Spektre
我接受这是一个可行的方法 :) 在这种情况下唯一的问题是,我正在将其编写为Ruby gem,然后C/C++代码将被编译为本机扩展。我必须在OS X、Linux和Windows上处理它,并承诺尝试在至少6个不同的显示管理器上使其工作会很有趣 o_= 我真的希望有一个更加规范的解决方案,但可能不存在(在这种情况下,我将通过恢复系统默认颜色来覆盖用户设置并退出 :)). - Jonas Schubert Erlandsson
如果你所针对的所有平台中都没有提供相应的API,那么这可能是唯一的解决方案。很抱歉无法提供更多帮助。另外,我找到了我的旧汇编和Pascal源代码,其中IO地址为0x3C8,colorix;然后在r,0x3C9;在g,0x3C9;在b,0x3C9;用于调色板读取,或者使用outs进行调色板设置。 - Spektre
谢谢,我会尝试一下DMA方法,看看能走多远,如果不行,我还可以放弃。用Adam Savage的话说:“失败总是一个选择” :) - Jonas Schubert Erlandsson

0

使用xterm(以及一些类似的终端),您可以使用转义序列来获取颜色调色板。这在XTerm控制序列中有记录:

OSC Ps ; Pt ST

设置文本参数。一些控制序列返回信息:
Ps = 4 ; c ; spec ⇒ 将颜色号c更改为由spec指定的颜色。
如果给出"?"而不是名称或RGB规范,则xterm会回复一个相同形式的控制序列,可用于设置相应的颜色。因为一个控制序列中可以给出多个颜色号和规范对,所以xterm可以做出多个回复。

这样做可能会很慢。xterm提供了用于在堆栈上操作颜色调色板的转义序列:

CSI Pm # P
将当前动态和ANSI调色板颜色推入堆栈(XTPUSHCOLORS),xterm。可以使用参数(范围在1到10之间的整数,因为默认值0将被推送)将调色板存储到堆栈中而不进行推送。

CSI Pm # Q
弹出堆栈以设置动态和 ANSI 调色板颜色(XTPOPCOLORS),xterm。参数(范围在 1 到 10 的整数,因为默认值 0 将弹出)可用于从堆栈中恢复调色板而不弹出。

iTerm2 和 Windows Terminal 的更改日志表明它们支持这些 xterm 功能。


-1
/* Hi -- I think this C code will reveal all that you need to do all that you asked.  Best wishes...*/
/*--------------------------------------------------------------------------

 CONSOLE_LEGACY_PALETTE.C

 Code by Greg Spears 
 Created 2022.10.22 and released into the public domain.
 Tested, use at your own risk.
---------------------------------------------------------------------------*/

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

/* This is what the palette used to be before it was updated by Microsoft in 2017.*/
BYTE legacy_palette[16][3] = {
{0,0,0 },
{0,0,128},
{0,128,0},
{0,128,128},
{128,0,0},
{128,0,128},
{128,128,0},
{192,192,192},
{128,128,128},
{0,0,255},
{0,255,0},
{0,255,255},
{255,0,0},
{255,0,255},
{255,255,0},
{255,255,255}
};

/* Used for displaying the color palette by printf()*/
static char *szTextColors[] = {
"BLACK",
"BLUE",
"GREEN",
"CYAN",
"RED",
"MAGENTA",
"BROWN",
"LIGHTGRAY",
"DARKGRAY",
"LIGHTBLUE",
"LIGHTGREEN",
"LIGHTCYAN",
"LIGHTRED",
"LIGHTMAGENTA",
"YELLOW",
"WHITE"
};


/*--------------------------------------------------------------------------

set_legacy_palette()

DATE:       REASON:
2022.10.22 - Created 

Params: none
Returns: TRUE if the console palette updates successfully.  FALSE if not.

---------------------------------------------------------------------------*/
int set_legacy_palette(void)
{
    CONSOLE_SCREEN_BUFFER_INFOEX csbix;
    int iii;
    HANDLE hConOut = GetStdHandle(STD_OUTPUT_HANDLE);

    if(!hConOut)
    {
        printf("set_legacy_palette():  bad handle: hConOut");
        return FALSE;
    }

    /* Use GetConsoleScreenBufferInfoEx() to get the console palette*/
    csbix.cbSize = sizeof(csbix);
    if(!GetConsoleScreenBufferInfoEx(hConOut, &csbix))
    {
        printf("GetConsoleScreenBufferInfoEx() failed!");
        return FALSE;
    }

    printf("printing color palette: --------------------------\n");
    for (iii = 0; iii < 16; iii++)
        printf("ColorTable[%d] =  \t%-12s [%3d,%3d,%3d]\n", iii, szTextColors[iii], 
            GetRValue(csbix.ColorTable[iii]), 
            GetGValue(csbix.ColorTable[iii]), 
            GetBValue(csbix.ColorTable[iii]));
    printf("--------------------------------------------------\n");

    /* Fill the palette strcuture with legacy RGB values*/
    for(iii = 0; iii < 16; iii++)
        csbix.ColorTable[iii] = 
            RGB(legacy_palette[iii][0], legacy_palette[iii][1], legacy_palette[iii][2]);

    /* use SetConsoleScreenBufferInfoEx() to send the updated palette to the OS.
    */
    if (!SetConsoleScreenBufferInfoEx(hConOut, &csbix))
    {
        printf("set_legacy_palette(): SetConsoleScreenBufferInfoEx() failed!");
        return FALSE;
    }
    return TRUE;
}

祝你好运,在Mac和Linux上让它正常工作。 - n. m.
顺便提一下,这张图片对比了新版和传统版的Windows控制台调色板。上面的代码恢复了传统版的调色板。两张图片都是相同的控制台应用程序,只是其中一张恢复了传统版的调色板,因此更加明亮。https://imgur.com/a/ay0ZAy7编辑:如果有人需要帮助找到相关的API以在Linux或Mac上实现控制台调色板转换,请留言。 - greg spears

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