色彩管理
现代显示设备已远超 sRGB 色域——Apple 设备默认使用 Display P3,专业显示器支持 Adobe RGB 甚至 Rec. 2020。如果应用始终以 sRGB 渲染,那么在广色域屏幕上将无法展现更鲜艳的红色、更纯净的绿色和更深邃的蓝色。TGFX 的色彩管理系统提供了精确控制颜色从像素数据到屏幕显示全过程的能力。
核心概念
色彩管理围绕三个核心要素:
- ColorSpace:描述一个色彩空间,由传输函数(Transfer Function)和色域基色(Gamut Primaries)两部分组成。传输函数定义线性光值与编码值之间的非线性映射关系(即 gamma 曲线),色域基色定义红、绿、蓝三原色在 CIE 色度图上的位置
- ColorType:描述像素的内存编码格式(如
RGBA_8888、RGBA_F16、RGBA_1010102),决定每个通道的比特深度和排列顺序 - AlphaType:描述 alpha 通道的语义(
Premultiplied预乘 /Unpremultiplied非预乘 /Opaque不透明)
三者共同构成 ImageInfo,完整描述一块像素数据的属性:
auto info = ImageInfo::Make(width, height,
ColorType::RGBA_F16,
AlphaType::Premultiplied,
0, // rowBytes, 0 for auto
ColorSpace::DisplayP3());
预置色彩空间
TGFX 提供三个常用的预置色彩空间:
| 色彩空间 | API | 传输函数 | 色域 | 典型用途 |
|---|---|---|---|---|
| sRGB | ColorSpace::SRGB() | sRGB gamma (~2.2) | sRGB/Rec.709 | Web 内容、标准显示器 |
| sRGB Linear | ColorSpace::SRGBLinear() | Linear (1.0) | sRGB/Rec.709 | 物理光照计算、HDR 合成 |
| Display P3 | ColorSpace::DisplayP3() | sRGB gamma | DCI-P3 (D65) | Apple 设备、广色域内容 |
对于标准预置无法覆盖的场景,TGFX 提供了多种自定义创建方式:
// 方式 1:从传输函数和色域矩阵创建
// 例如创建 Adobe RGB 色彩空间
auto adobeRGB = ColorSpace::MakeRGB(NamedTransferFunction::TwoDotTwo,
NamedGamut::AdobeRGB);
// 方式 2:从 CICP 码点创建(常用于视频内容)
// Rec.709 色域 + sRGB 传输函数
auto rec709 = ColorSpace::MakeCICP(ColorSpacePrimariesID::Rec709,
TransferFunctionID::SRGB);
// 方式 3:从 ICC Profile 数据创建(用于嵌入了 ICC 配置的图像)
auto icc = ColorSpace::MakeFromICC(profileData, profileSize);
在 Surface 上启用色彩空间
创建 Surface 时通过最后一个参数指定目标色彩空间,之后所有绘制到该 Surface 的内容都会自动转换到该色彩空间:
// 创建一个 Display P3 色彩空间的 Surface
auto surface = Surface::Make(context, width, height,
false, // alphaOnly
1, // sampleCount
false, // mipmapped
0, // renderFlags
ColorSpace::DisplayP3());
// 也可以包裹已有的后端纹理或渲染目标
auto surface = Surface::MakeFrom(context, backendRenderTarget,
ImageOrigin::TopLeft,
0, // renderFlags
ColorSpace::DisplayP3());
在该 Surface 的 Canvas 上绘制时,TGFX 会根据需要自动完成色彩空间转换:
auto canvas = surface->getCanvas();
// Color 的值始终在 sRGB 色彩空间中表示
// 绘制时 TGFX 会自动将 sRGB 颜色转换到 Surface 的 Display P3 空间
canvas->drawRect(Rect::MakeXYWH(0, 0, 100, 100), paint);
// 绘制 sRGB 图像到 P3 Surface 时,也会自动转换
canvas->drawImage(srgbImage, 0, 0);
以下对比了在同一个 Display P3 Surface 上,分别使用 P3 颜色(左)和 sRGB 颜色(右)作为 DropShadow 阴影色的渲染效果:

上排为绿色阴影,下排为红色阴影。左侧使用 Display P3 色域的颜色(
Color::FromRGBA(0, 255, 0, 255, ColorSpace::DisplayP3())),右侧使用 sRGB 色域的颜色(Color::Green())。由于 P3 色域在红色和绿色方向比 sRGB 扩展了约 25%,在广色域显示器上观看时,左侧阴影的色彩明显更加鲜艳饱和。
Image 与色彩空间
Image 在创建时可以关联色彩空间。图像解码器(PNG、JPEG 等)会自动从文件元数据中读取嵌入的 ICC Profile 或色彩空间标记:
// 从文件解码——自动识别嵌入的色彩空间
auto image = Image::MakeFromFile("photo.png");
// image->colorSpace() 返回图片实际的色彩空间
// 从 HardwareBuffer 创建时指定色彩空间
auto image = Image::MakeFrom(hardwareBuffer,
ColorSpace::DisplayP3());
// 从 BackendTexture 创建时指定色彩空间
auto image = Image::MakeFrom(context, backendTexture,
ImageOrigin::TopLeft,
ColorSpace::DisplayP3());
当 Image 绘制到 Surface 时,如果两者的色彩空间不同,TGFX 会在 GPU 上自动执行色彩空间转换。这个过程完全透明——只需确保 Image 和 Surface 各自标注了正确的色彩空间即可。
Color 与广色域
TGFX 的 Color 类使用 4 个浮点数(RGBA)表示颜色,值始终定义在 sRGB 色彩空间中。浮点表示意味着分量值可以超出 [0, 1] 范围,这在广色域场景中非常有用:
// 标准 sRGB 红色
Color srgbRed = {1.0f, 0.0f, 0.0f, 1.0f};
// 从 8 位值创建(指定源色彩空间)
// 如果传入的 colorSpace 不是 sRGB,会自动转换到 sRGB
auto color = Color::FromRGBA(255, 0, 0, 255, ColorSpace::DisplayP3());
Color 和 PMColor(预乘颜色)的区别在编译期通过模板参数强制:
Color color = {1.0f, 0.5f, 0.0f, 0.8f};
// 编译期类型安全的预乘/反预乘转换
PMColor pm = color.premultiply(); // → {0.8, 0.4, 0.0, 0.8}
Color unpre = pm.unpremultiply(); // → {1.0, 0.5, 0.0, 0.8}
色彩空间转换流程
当源和目标色彩空间不同时,TGFX 执行以下转换管线(CPU 和 GPU 上步骤相同):
源像素值 (编码值)
│
▼
① Unpremultiply(解除预乘)
│
▼
② Linearize(应用源传输函数,编码值 → 线性值)
│
▼
③ Gamut Transform(色域变换,源 XYZ → 目标 XYZ 的 3×3 矩阵)
│
▼
④ Encode(应用目标传输函数的逆,线性值 → 编码值)
│
▼
⑤ Premultiply(重新预乘)
│
▼
目标像素值 (编码值)
TGFX 会自动优化这个管线——如果源和目标的传输函数或色域矩阵相同,对应的步骤会被跳过。例如 sRGB 和 Display P3 共用同一传输函数,因此只需要执行色域矩阵变换。
这个转换在 GPU 上通过 Fragment Shader 自动完成(ColorSpaceXformEffect),在 CPU 上通过 Pixmap::readPixels() 自动完成——开发者无需手动干预。
像素回读与色彩空间
从 Surface 回读像素时,可以通过 ImageInfo 指定目标色彩空间,TGFX 会自动完成转换:
// 将 P3 Surface 的内容回读为 sRGB 像素
auto dstInfo = ImageInfo::Make(width, height,
ColorType::RGBA_8888,
AlphaType::Premultiplied,
0,
ColorSpace::SRGB());
std::vector<uint8_t> pixels(dstInfo.byteSize());
surface->readPixels(dstInfo, pixels.data());
// 或者读取单个像素并获取指定色彩空间的颜色值
auto color = surface->getColor(x, y, ColorSpace::SRGB());
注意事项
- 不指定色彩空间时的默认行为:源(Image)未指定 ColorSpace 时,TGFX 将其视为 sRGB;目标(Surface)未指定 ColorSpace 时,TGFX 不会进行任何色彩空间转换,即"不关心色彩空间"的语义
- Alpha-only 图像不转换:
ColorType::ALPHA_8格式的图像(如文字遮罩)只包含透明度信息,没有色彩数据,因此不进行色彩空间转换 - 自定义 Shader 中的色彩空间:当使用 RuntimeEffect 编写自定义 Shader 时,TGFX 会自动将所有输入纹理转换到 Surface 的色彩空间后再传入
onDraw(),无需在 Shader 中手动处理色彩空间转换 - ICC Profile 支持:TGFX 通过
ColorSpace::MakeFromICC()支持从 ICC Profile 数据创建色彩空间,PNG 解码器会自动读取嵌入的 ICC Profile。也可以通过ColorSpace::toICCProfile()将色彩空间导出为 ICC Profile 数据
