如何实现十六进制颜色值转ncurses颜色值的算法?
问题:十六进制颜色转ncurses颜色值的算法实现
我想写一个能把十六进制颜色转换成ncurses颜色值的算法,比如下面的示例:
std::string bar = "#A400C6"; init_pair(1, foo(bar), COLOR_BLACK); // foo应该返回COLOR_MAGENTA(也就是5) attron(COLOR_PAIR(1)); wprintw(win, "Example"); attroff(COLOR_PAIR(1));
我尝试过让foo函数在整数数组里查找近似值,但没成功。
补充:我写的有问题的算法
class RGB { public: uint8_t r; uint8_t g; uint8_t b; RGB(uint8_t r, uint8_t g, uint8_t b) { this->r = r; this->g = g; this->b = b; } }; uint8_t cvaluesForRAndB[3] = {0, 24, 178}; uint8_t cvaluesForG[4] = {0, 24, 104, 178}; // 我的KDE终端默认颜色 RGB terminalColors[8] = {{0, 0, 0}, {178, 24, 24}, {24, 178, 24}, {178, 104, 24}, {24, 24, 178}, {178, 24, 178}, {24, 178, 178}, {178, 178, 178}}; bool compareRGB(RGB a, RGB b) { return a.r == b.r && a.g == b.g && a.b == b.b; } int foo(std::string hex) { std::string vals[3] = splitWithLength(2, (hex[0] == '#') ? hex.substr(1, 6) : hex); // splitWithLength会按指定长度分割字符串,比如splitWithLength(2, "000cba")返回{"00", "0c", "ba"} uint8_t uintVals[3]; for (uint8_t i = 0; i < 3; i++) { uintVals[i] = std::stoi(vals[i], nullptr, 16); } RGB res(findClosest(cvaluesForRAndB, 3, uintVals[0]), findClosest(cvaluesForG, 4, uintVals[1]), findClosest(cvaluesForRAndB, 3, uintVals[2])); for (uint8_t i = 0; i < 8; i++) { if (compareRGB(terminalColors[i], res)) { return i; } } return 0; }
(代码写在头文件里,质量很差)
解决方案
原代码的核心问题
- 近似匹配逻辑错误:你用
findClosest把RGB分量分别匹配到固定值,再和终端预设颜色对比,但这种分量独立匹配的方式会导致生成的res可能根本不在terminalColors数组里,最后只能返回默认的0。比如示例中的#A400C6,转换后R匹配178、G匹配0、B匹配178,生成的(178,0,178)和terminalColors[5]的(178,24,178)G分量不同,匹配失败返回0,而非预期的5。 - 未实现真正的颜色相似度计算:应该直接计算输入RGB和每个终端预设颜色的颜色距离,选距离最小的那个,而非先强行映射分量再精确匹配。
改进后的实现
第一步:实现颜色距离计算
常用的颜色相似度计算可以用欧几里得距离,或者更贴合人眼感知的加权距离(给绿色更高权重,因为人眼对绿色更敏感)。这里先给出直观的欧几里得距离实现:
#include <cmath> #include <string> #include <cstdint> #include <vector> class RGB { public: uint8_t r; uint8_t g; uint8_t b; RGB(uint8_t r, uint8_t g, uint8_t b) : r(r), g(g), b(b) {} }; // 我的KDE终端默认颜色 RGB terminalColors[8] = {{0, 0, 0}, {178, 24, 24}, {24, 178, 24}, {178, 104, 24}, {24, 24, 178}, {178, 24, 178}, {24, 178, 178}, {178, 178, 178}}; // 计算两个RGB颜色的欧几里得距离 double rgbDistance(const RGB& a, const RGB& b) { double dr = a.r - b.r; double dg = a.g - b.g; double db = a.b - b.b; return sqrt(dr*dr + dg*dg + db*db); }
第二步:修改foo函数逻辑
直接遍历所有终端预设颜色,计算输入颜色与每个颜色的距离,返回距离最小的颜色索引:
// 按指定长度分割字符串的健壮实现 std::vector<std::string> splitWithLength(size_t len, const std::string& s) { std::vector<std::string> res; for (size_t i = 0; i < s.size(); i += len) { res.push_back(s.substr(i, len)); } return res; } int foo(const std::string& hex) { // 解析十六进制字符串 std::string hexStr = hex; if (!hexStr.empty() && hexStr[0] == '#') { hexStr = hexStr.substr(1); } // 非法输入直接返回默认黑色 if (hexStr.size() != 6) { return 0; } auto parts = splitWithLength(2, hexStr); uint8_t r = static_cast<uint8_t>(std::stoi(parts[0], nullptr, 16)); uint8_t g = static_cast<uint8_t>(std::stoi(parts[1], nullptr, 16)); uint8_t b = static_cast<uint8_t>(std::stoi(parts[2], nullptr, 16)); RGB input(r, g, b); // 遍历找距离最小的颜色 int closestIndex = 0; double minDistance = rgbDistance(input, terminalColors[0]); for (int i = 1; i < 8; ++i) { double dist = rgbDistance(input, terminalColors[i]); if (dist < minDistance) { minDistance = dist; closestIndex = i; } } return closestIndex; }
第三步:优化细节
- 如果想要更精准的人眼感知匹配,可以改用加权距离公式:
double rgbDistance(const RGB& a, const RGB& b) { double dr = a.r - b.r; double dg = a.g - b.g; double db = a.b - b.b; // 加权距离,符合人眼对RGB的敏感度 return sqrt(0.299*dr*dr + 0.587*dg*dg + 0.114*db*db); } - 增加了输入合法性检查,避免无效十六进制字符串导致的异常。
测试示例
对于输入#A400C6(RGB(164, 0, 198)),计算和terminalColors[5](RGB(178,24,178))的距离约为34.23,是所有预设颜色中最小的,因此会返回5,也就是COLOR_MAGENTA,符合预期。
内容的提问来源于stack exchange,提问作者DKorjey
相关产品推荐
相关产品推荐

