color - HCT 色彩空间¶
CFColor 是对 QColor 的扩展,增加了 HCT(Hue-Chroma-Tone)色彩空间支持。HCT 是 Material Design 3 采用的色彩空间,比 RGB 或 HSL 更符合人类对颜色的直觉感知——色相、鲜艳度、明度三个维度正交,调整其中一个不会影响其他两个。这在做动态主题系统时特别重要。
为什么需要 HCT¶
RGB 空间的问题是"不直观"——你想把颜色变亮一点,结果色相也跟着变了;想把蓝色调得更鲜艳,结果明度也变了。HCT 解决了这个问题:
#include "ui/base/color.h"
using namespace cf::ui::base;
// RGB 方式:想要一个更亮的蓝色...
QColor rgbBlue(0, 0, 255);
QColor brighterBlue = rgbBlue.lighter(120); // 结果可能偏紫了
// HCT 方式:保持色相和色度,只调整色调
CFColor hctBlue(240.0f, 80.0f, 50.0f); // 蓝色,中高饱和度,中等亮度
CFColor brighterHCT = CFColor(hctBlue.hue(), hctBlue.chroma(), 70.0f);
// 色相 240° 不变,鲜艳度不变,只是更亮了
这就是 Material Design 3 能从单个"种子颜色"生成整套主题的原因——在 HCT 空间里,你可以独立控制颜色的各个维度。
构造颜色¶
CFColor 可以从多种方式构造:
// 从 RGB 值
CFColor red(255, 0, 0);
// 从 QColor
QColor qColor("#FF0000");
CFColor fromQColor(qColor);
// 从十六进制字符串
CFColor fromHex("#FF0000");
CFColor fromHexWithAlpha("#80FF0000"); // 50% 透明的红色
// 从 HCT 值
CFColor fromHCT(240.0f, 80.0f, 50.0f); // 蓝色,饱和度 80,明度 50
HCT 值的范围:
- hue:0° 到 360°,绕色相环一圈
- chroma:0 到 150,值越大越鲜艳(超过某个值可能无法表示)
- tone:0 到 100,0 是纯黑,100 是纯白
⚠️ 并非所有 (hue, chroma, tone) 组合都能映射到有效的 RGB。超出 sRGB 色域的组合会被裁剪,所以设置 chroma 时要保守一点,一般别超过 100。
获取 HCT 分量¶
构造后可以随时获取 HCT 值:
CFColor color("#6750A4"); // Material Purple
float h = color.hue(); // 262.5°(紫色偏蓝)
float c = color.chroma(); // 约 52(中等鲜艳度)
float t = color.tone(); // 约 42(中等偏暗)
// 基于这些值生成变体
CFColor lighterVariant(color.hue(), color.chroma() * 0.8f, color.tone() + 20);
CFColor darkerVariant(color.hue(), color.chroma() * 1.2f, color.tone() - 20);
HCT 值在构造时计算并缓存,所以访问是 O(1) 的。别担心性能问题。
相对亮度¶
relativeLuminance() 计算 WCAG 标准的相对亮度,用于对比度计算:
CFColor text("#000000");
CFColor bg("#FFFFFF");
float lumText = text.relativeLuminance(); // ~0.0(黑色)
float lumBg = bg.relativeLuminance(); // ~1.0(白色)
// 对比度 = (Lmax + 0.05) / (Lmin + 0.05)
float ratio = (std::max(lumText, lumBg) + 0.05) /
(std::min(lumText, lumBg) + 0.05);
这个函数在 color_helper.h 的 contrastRatio() 里被使用,一般不需要直接调用。但如果你想自己实现一些可访问性相关的逻辑,可以用它。
与 QColor 互操作¶
CFColor 内部维护了一个 QColor,可以随时获取:
CFColor myColor("#FF0000");
// 获取原生 QColor 用于 Qt API
QColor qColor = myColor.native_color();
// 直接用在 QPainter 里
QPainter painter(this);
painter.setBrush(myColor.native_color());
painter.drawEllipse(center, 50, 50);
CFColor 不是 QColor 的子类,而是组合关系。这样设计是为了避免 QColor 的隐式转换带来的意外——QColor(const char*) 会把字符串当成颜色名,而我们希望 hex 字符串有明确的解析行为。
使用建议¶
做主题系统时,推荐的工作流:
- 选择一个"种子颜色"(通常是品牌色)
- 转换到 HCT 空间
- 在 HCT 空间生成各种变体(调整 tone 生成深浅色,调整 chroma 生成去饱和变体)
- 用
color_helper.h的tonalPalette()自动完成这个步骤
// 种子颜色
CFColor seed("#6750A4");
// 生成完整色调板
QList<CFColor> palette = tonalPalette(seed);
// 使用预定义的色调
CFColor primary = palette[6]; // 主要色
CFColor onPrimary = palette[10]; // 主要色上的文字
CFColor surface = palette[4]; // 表面色
这样生成的主题保证色彩和谐,避免了手工调整 RGB 时容易出现的"脏色"问题。