4. 色彩管理基础

在 DRM CRTC 驱动中做 Gamma 校正与色彩管理,第一步不是写寄存器,而是把“颜色到底被怎样描述”这件事讲清楚。显示管线里每一个环节——GPU 合成、CRTC 扫描输出、编码器、面板——都在用某种数值表示颜色,如果这些数值背后的色彩空间、色域、白点和传递函数不一致,那么即使 Gamma LUT 数值算得再精确,最终画面依然会偏色、发灰或过曝。本节把这四个基础概念串成一条完整的“颜色描述链”,为后续 Gamma LUT、CTM(Color Transformation Matrix)、3D LUT 的配置打地基。

一、色彩空间:给数值一个“坐标系”

色彩空间(Color Space)本质上是“数值 → 颜色”的映射规则。同一个三元组 (1.0, 0.0, 0.0),在 sRGB 里是标准红,在 BT.2020 里却是更饱和的红。因此色彩空间必须包含三件事:基色坐标(红绿蓝三原色在色度图上的位置)、白点、传递函数。三者缺一,颜色就无法被唯一还原。

在 DRM 中,色彩空间通常通过 connector 的属性暴露,例如 Colorspace 或 max bpc 配合 EDID 中的色彩特性。常见的有 sRGB、BT.709、BT.2020、DCI-P3、Adobe RGB 等。驱动需要根据用户空间(如 compositor)下发的属性,把 CRTC 的输出格式与下游编码器、面板能力对齐。

二、色域:色彩空间的“面积”

色域(Gamut)是色彩空间在色度图上覆盖的范围,用三角形面积直观表示。sRGB 与 BT.709 基色几乎重合,覆盖约 35% 的 CIE 1931 可见色;DCI-P3 覆盖约 45%;BT.2020 覆盖约 75%。色域越大,能显示的颜色越鲜艳,但对面板和背光要求也越高。

工程上最容易踩的坑是“大色域面板直接显示 sRGB 内容”:面板原生是 P3,输入却是 sRGB 数值,如果不做色域映射,红色会被拉得过分饱和,肤色发橙。正确做法是在 CRTC 的 CTM 或 3D LUT 中做 gamut mapping,把 sRGB 基色映射到 P3 基色,而不是简单地把数值原样送出去。

三、白点:颜色的“锚”

白点是色彩空间中最关键的参考点,决定了“什么叫做白”。常见白点有 D65(6504K,sRGB/BT.709/BT.2020 使用)、DCI-P3 的 DCI 白点(约 6300K)、D50(印刷)。白点偏移会直接导致整幅画面偏冷或偏暖。

在 DRM 驱动里,白点通常不单独作为属性,而是隐含在色彩空间和 CTM 中。如果面板白点与内容白点不一致,需要通过 CTM 做白点适配(White Point Adaptation),常用 Bradford 或 CAT02 色适应矩阵。忽略这一步,画面会出现“整体偏蓝”或“整体偏黄”的典型症状。

四、传递函数:数值与亮度的非线性关系

传递函数(Transfer Function)描述“编码数值”与“实际光亮度”之间的关系,分为 OETF(编码端)和 EOTF(显示端)。sRGB 使用近似 2.2 的 gamma 曲线,PQ(SMPTE ST 2084)用于 HDR,HLG 用于广播 HDR。Gamma 校正的本质,就是在 CRTC 中插入一条反向曲线,抵消面板的 EOTF,使最终输出符合目标传递函数。

下面这张图把“色彩空间 → 色域 → 白点 → 传递函数”四要素与 DRM 管线中的对应配置点串起来,帮助建立整体认知:

色彩描述链与 DRM 配置点 色彩空间 基色坐标 + 白点 + 传递函数 色域 sRGB / P3 / BT.2020 三角形覆盖面积 白点 D65 / DCI / D50 色适应参考 传递函数 sRGB / PQ / HLG OETF / EOTF DRM 显示管线 Framebuffer → Plane → CRTC(Gamma LUT / CTM / 3D LUT)→ Encoder → Connector → Panel Colorspace 属性 CTM / 3D LUT 白点适配矩阵 Gamma LUT 关键对应关系: • 色彩空间决定“数值含义”,色域决定“能显示多鲜艳”,白点决定“什么叫做白”,传递函数决定“亮度如何编码”。 • 四者必须端到端一致;任一环节错配,Gamma LUT 与 CTM 的校正都会失效或产生新的偏色。

五、四要素对照与常见配置

下表把四个概念在 DRM 驱动中的典型表现、常见取值和错配症状整理成清单,便于排障时快速定位:

概念 DRM 中的体现 常见取值 错配典型症状
色彩空间 Connector Colorspace 属性、EDID 色彩特性 sRGB、BT.709、BT.2020、DCI-P3 整体色调偏移、饱和度异常
色域 CTM / 3D LUT 中的基色映射矩阵 sRGB→P3、P3→BT.2020 红色过饱和、肤色发橙
白点 CTM 中的色适应矩阵(Bradford/CAT02) D65、DCI、D50 画面整体偏蓝或偏黄
传递函数 CRTC Gamma LUT、PQ/HLG 元数据 sRGB gamma 2.2、PQ、HLG 暗部死黑、高光过曝、灰阶断层

六、案例:一次“偏蓝”的排障复盘

某项目使用 P3 面板,播放 sRGB 内容时用户反馈“整体偏蓝、暗部发灰”。排查过程如下:

  1. 确认 Framebuffer 格式为 DRM_FORMAT_XRGB8888,内容标记为 sRGB。
  2. 读取 Connector 的 Colorspace 属性,发现被设置为 Default,未显式声明 sRGB。
  3. 检查 CRTC 的 CTM,发现矩阵为单位矩阵,未做 sRGB→P3 的基色映射,也未做 D65→DCI 白点适配。
  4. 检查 Gamma LUT,发现仍使用面板出厂默认曲线,未按 sRGB EOTF 反向补偿。

修复方案:在用户空间下发 Colorspace=sRGB,驱动侧在 CTM 中写入 sRGB→P3 基色映射矩阵并叠加 D65→DCI 白点适配,同时按 sRGB gamma 2.2 生成反向 Gamma LUT。修复后偏蓝消失,暗部层次恢复。

常见误区:很多工程师把 Gamma LUT 当成“万能校正工具”,试图用一条曲线同时解决色域、白点和传递函数问题。这是错误的。Gamma LUT 是一维的,只能处理亮度通道的非线性;色域和白点属于三维色彩变换,必须由 CTM 或 3D LUT 承担。混用会导致校正相互干扰,越调越偏。
工程建议:在驱动初始化阶段,先读取 EDID 中的色彩特性与面板原生色域,建立“内容色彩空间 → 面板色彩空间”的映射表;再根据目标传递函数生成 Gamma LUT。把四要素当作一条链来配置,而不是四个独立参数。

七、配置片段示例

以下是一个典型的属性设置与 Gamma LUT 生成片段,展示四要素如何落到代码层面:

/* 1. 声明内容色彩空间 */
drmModeObjectSetProperty(fd, connector_id,
    DRM_MODE_OBJECT_CONNECTOR,
    prop_colorspace, DRM_MODE_COLORIMETRY_sRGB);

/* 2. 配置 CTM:sRGB -> P3 基色映射 + D65 -> DCI 白点适配 */
struct drm_color_ctm ctm = {
    .matrix = {
        /* 3x3 矩阵,定点数格式 */
        ...
    }
};
drmModeObjectSetProperty(fd, crtc_id,
    DRM_MODE_OBJECT_CRTC, prop_ctm, &ctm);

/* 3. 生成 sRGB gamma 2.2 反向 LUT */
for (int i = 0; i < lut_size; i++) {
    double x = (double)i / (lut_size - 1);
    double y = pow(x, 1.0 / 2.2);   /* 反向补偿 */
    gamma_lut[i].red   = (uint16_t)(y * 0xFFFF);
    gamma_lut[i].green = (uint16_t)(y * 0xFFFF);
    gamma_lut[i].blue  = (uint16_t)(y * 0xFFFF);
}
drmModeObjectSetProperty(fd, crtc_id,
    DRM_MODE_OBJECT_CRTC, prop_gamma_lut, gamma_lut);

理解色彩空间、色域、白点和传递函数,是后续所有 Gamma 与色彩管理配置的前提。它们不是四个孤立名词,而是一条从内容到面板的完整描述链。只有这条链上每一环都对齐,CRTC 中的 Gamma LUT 和 CTM 才能真正发挥作用,画面才能既准确又好看。