本文件是本仓 ColorMatrix 工程的详细文档;增益图容器层那份见
GainMapKit.md。总览见README.md。
纯托管的色彩数学内核 —— 色彩空间转换矩阵、传递函数、HDR 算子与逐像素执行器。 零外部依赖、零进程调用、无 ffmpeg 绑定;可直接用于任何 .NET 图像/视频色彩管线。
本库是从 ffmpegPictureUI 的
Services/ColorMapping/中抽取出的可独立编译子集。抽取原则见 §5 本库刻意不包含什么。
一套自研的色彩数学实现,用 C# 写成,不链接、不依赖 lcms2 / LittleCMS / zimg / ffmpeg / libjxl (零 NuGet 依赖)。覆盖「原色 ↔ 原色」的线性矩阵推导、色适应、标准传递函数(SDR 与 HDR)、 HDR→SDR 色调映射算子,以及逐像素的矩阵/曲线/量化执行。
⚠ 原创范围的诚实边界:除
ColorKernels.GamutMapScalar(逐行移植自 libjxl,BSD-3-Clause) 与ToneMapping的 Hable/Mobius 常数(公式形式对齐 ffmpegvf_tonemap.c,未复制其源码)外, 本库其余代码为作者原创。完整出处登记见THIRD-PARTY-NOTICES.md—— 它对下游再分发有实际约束(BSD-3 第 2 条),不要跳过。
using ColorMatrix;
// 例:求 bt2020 → bt709 的线性原色转换矩阵
var m = ColorSpaceRegistry.LinearMatrixBetweenPrimaries("bt2020", "bt709");
// → 3×3 行主序矩阵(行主序 × 列向量,全链同形)
// 例:BT.2100 PQ 的 EOTF
double nits = TransferCurve.PqEotf(0.58); // → ≈ 203.0 nits
// 例:线性 float → sRGB 8bit(可选 AVX2)
SimdPixelOps.FloatToSrgb8(linearSpan, byteSpan);| 文件 | 职责 |
|---|---|
ColorSpaceRegistry.cs |
原色表(CICP/H.273)、白点、矩阵推导、Bradford 色适应、具名空间注册 |
ColorSpaceDescriptor.cs |
色彩空间描述符与等价判定(消费 EquivalenceTolerances) |
TransferCurve.cs |
标准传递函数(SDR 曲线归一为 7 参数规范形;PQ/HLG 为闭式) |
Bt2100Ootf.cs |
BT.2100 的 OOTF(正/逆) |
Bt2446MethodA.cs |
BT.2446 Method A 的 HDR→SDR 映射 |
ToneMapping.cs |
色调映射算子(Reinhard / Hable / Mobius 等) |
ColorKernels.cs |
逐像素内核:矩阵、曲线、位深量化与有序抖动 |
SimdPixelOps.cs |
SIMD 加速路径(AVX2)与标量回退 |
DescriptorConfidence.cs / EquivalenceTolerances.cs |
从宿主抽取的最小依赖(枚举 / 阈值常量) |
原色(CICP/H.273):bt709 · bt470m · bt470bg · smpte170m · smpte240m ·
bt2020 · smpte431 · smpte432 · jedec-p22;具名空间含 sRGB / Display P3 /
Adobe RGB / ProPhoto RGB / Rec.2100 系列。
传递函数:CICP 1/4/5/6/7/8/13/14/15 · 16 (PQ) · 18 (HLG); BT.2100 OOTF;BT.2446 Method A。
设计约束(有意为之):
- 矩阵布局与乘序全链一致:行主序 × 列向量,构造端 / 执行端 / ICC 读写端同形。
- fail-closed:未收录或无法表达的标准返回
null/ 拒绝,绝不猜测一个矩阵去乘像素。 - 色适应复合为
M⁻¹·L·M(Bradford),已修对历史错序。
下表全部来自本仓自带自检的输出:dotnet run --project tests/ColorMatrix.SelfTest -c Release
⇒ 当前读数 43 PASS / 0 FAIL / exit 0。凡"外部参照"都是别人的实现算出来的数,
不是本库自己声称的期望值。
| 对撞对象(组) | 本仓实测读数 |
|---|---|
原色表逐 token 对撞 zimg(ffmpeg 的 zscale 实际下发的矩阵,第 8 组,8 个 token,容差 5e-4) |
逐元素最大实差:bt470m 1.96e-4 · bt470bg 4.32e-5 · smpte170m 4.21e-5 · smpte240m 4.21e-5 · bt2020 5.06e-5 · smpte431 2.56e-4 · smpte432 4.30e-5 · jedec-p22 5.25e-5 |
| 反退化:8 个 token 不得共用一组常数(第 8 组) | 算出 7 组不同矩阵(判据 ≥7)⇒ 原色表没有退化成"所有标准一条式" |
| 传递函数锚点(第 9 组) | PQ ToLinear(0.580694)×10000 = 203.010 nits(容差 0.6);ToLinear(0.8) 对应 1555.2 nits(反控:远高于 SDR 白点) |
| PQ / HLG 往返互逆 + 三条曲线 100 点单调(第 9 组) | 互逆残差 0.400000000000 / 0.600000000000;PQ·HLG·BT.709 均单调不减,黑电平未被抬高 |
bt2020→bt709 对撞 ITU 发布值(第 1 组)+ 往返互逆(第 3 组) |
通过;互逆 M(a→b)·M(b→a) ≈ I 实测 maxErr 6.7e-16 |
⚠ 口径边界:宿主项目另有一组更宽的对撞门禁(verify-colorfmt-matrix 等),
它跑在宿主的完整管线上、不在本仓,所以本表不引用它任何数字 —— 这里每一格都能用上面那条命令打出来。
已知缺口(诚实登记,不是"还没填表"就是"数学上表达不了"):
film(CICP 8):精度不足,有意不收录,返回null;smpte428(CICP 10 / ST 428-1):原色是 CIE-XYZ 三角(蓝原色 y=0), xy 参数化在数学上无法表达 ⇒ 永久 fail-closed,需新的参数化才能支持;log100/log316/xvYCC/BT.1361/smpte428(γ2.6):未收录,fail-closed;- BT.2390 EETF、BT.2446 Method B/C:未实现。
抽取时刻意剔除了以下部分——它们属于应用层,不是色彩数学内核:
- 规划层(
ColorTransformPlan/ColorStrategyPlanning/ColorIntent等): 依赖宿主的FfmpegOptions(Models)与FfmpegCommandBuilder,且与宿主构成双向循环依赖。 - 执行层(
RawColorPipeline):本质是 ffmpeg / cjxl / jxr 的命令行编排器, 还依赖宿主的设置单例与临时文件。 SimdKernelRouting:读宿主的AppSettingsService(应用设置单例)。 本库改以SimdPixelOps.UseAvx2ForFloatToSrgb8公开属性表达同一开关,由调用方设置。
⇒ 因此本库不含任何 ffmpeg / cjxl / jxr 的进程调用,也不链接任何第三方库。
唯一的第三方代码来源是一处:ColorKernels.GamutMapScalar(libjxl 逐行移植,BSD-3-Clause)
—— 见 THIRD-PARTY-NOTICES.md §1。除此之外均为原创。
dotnet build src/ColorMatrix/ColorMatrix.csproj -c Release- 目标框架
net11.0。当前状态:0 警告 0 错误。 - 文档生成是开着的(
GenerateDocumentationFile=true,只豁免CS1591缺注释)⇒ 悬空的<see cref>、非法 XML 注释都会变成可见的CS1570/CS1574警告。 本轮公开前据此修掉了 15 处既有 markup 缺陷(两处悬空 cref 指向未抽入本库的规划层类型、 两处<summary>未闭合、未转义的<、TransformRgba8缺 7 个<param>)。 - 换 TFM 复测(2026-10-02 复测:
net8.0同样 0 警告 0 错误,本库不使用任何 .NET 11 专有 API):⚠ 少了dotnet restore src/ColorMatrix/ColorMatrix.csproj -p:TargetFramework=net8.0 dotnet build src/ColorMatrix/ColorMatrix.csproj -c Release -p:TargetFramework=net8.0 --no-restore
restore那一步会直接error NETSDK1005(资产文件里没有该 TFM 的目标)—— 这是还原态问题,不是代码问题;别把它误读成"库在 net8 上编不过"。测完记得再dotnet restore回net11.0。
dotnet run --project tests/ColorMatrix.SelfTest -c Release刻意不用 xunit / NUnit —— 那会引入 NuGet 依赖,破坏本库「零外部依赖」的定位。 自检是控制台程序,全过 exit 0 / 有失败 exit 非 0,可直接接 CI。
当前读数:43 PASS / 0 FAIL,覆盖 9 组:
| 组 | 内容 |
|---|---|
| 1 | bt2020→bt709 对撞 ITU 发布值(含反控:不得退化成单位阵) |
| 2 | 契约:同原色返回 null、film/smpte428/未知 token 一律 fail-closed |
| 3 | 往返互逆 M(a→b)·M(b→a) ≈ I(实测 maxErr 6.7e-16) |
| 4 | 白点守恒:原色矩阵三行行和 ≈ 1 |
| 5 | 传递函数收录面:PQ/HLG/BT.709 收录,log100/log316/未知 fail-closed |
| 6 | HDR 算子闭式常数(含反控:非恒返回常量) |
| 7 | 逐像素内核:alpha 不受矩阵影响、矩阵真的改变颜色、null 为无操作 |
| 8 | 原色表逐 token 对撞 zimg(8 个 token 的入库常量,容差 5e-4)+ 反控:8 个 token 须算出 ≥7 组不同矩阵(防原色表退化成"所有标准共用一组常数"—— 历史上出过这个 P1) |
| 9 | 传递函数锚点 / 互逆 / 单调:PQ 锚点 ToLinear(0.580694)×10000 ≈ 203 nits(实测 203.010)、PQ/HLG 往返互逆、三条曲线 100 点单调扫描、黑电平不被抬高 |
⚠
ToLinear的归一化约定(实测澄清,勿再假设):它归一化到该曲线自身的峰值, 不是归一化到 SDR 白点 203nits —— PQ 的1.0= 10000 nits、HLG 的1.0= 其峰值。 ⇒ 跨曲线的锚点断言必须写成「乘回峰值换算成 nits」才与约定无关。 本组第一版我按"归一化到 203nits"写,实测 3 条转红 —— 那是我的假设错,不是库错; 这本身也是该组"有牙"的证据:断言确实绑在真实行为上,不是恒真式。
⚠ 自检的变异验牙(2026-10-02 本机复跑,计数以当前 43 条为准;还原后两次都回到 43 PASS / 0 FAIL, 且被改文件 SHA256 与基线一致):
- 把
Bt2446MethodA.LumaPrime代码行的系数0.2627 * rp改成0.2628 * rp⇒ 41 PASS / 2 FAIL(红的正是第 6 组那两条LumaPrime锚点:白(1,1,1)与灰阶(0.5,0.5,0.5), 白那条件打印出实 1.000100000000)。- 把
bt470m的 R 色度{ 0.670, 0.330 }改成{ 0.680, 0.330 }⇒ 42 PASS / 1 FAIL(bt470m→bt709对撞 zimg:maxErr=8.64e-3vs 容差 5e-4)。- 空变异教训(保留,别删):第一次改
LumaPrime时用的是裸常数0.2627,replace(...,1)命中的是注释里的同名常数(Bt2446MethodA.cs:33),代码行(:34)没动 ⇒ 测试照绿, 差点被误读成「断言没牙」。⇒ 变异模式必须唯一到代码行(本轮因此改用0.2627 * rp), 并用"转红"本身证明它生效;命中数不等于 1 就直接判"这条变异不算"。
- 来源:从 ffmpegPictureUI 抽取
(该仓库为 GPL-3.0)。本库是该仓库中不含 ffmpeg 依赖、可独立编译的数学内核子集;
其中除
ColorKernels.GamutMapScalar(libjxl 逐行移植)与ToneMapping的 Hable/Mobius 常数(对齐 ffmpegvf_tonemap.c的公式形式)外,均为作者原创 —— 逐条登记见THIRD-PARTY-NOTICES.md。 - 许可:MIT(见
LICENSE),署名口径与源仓库一致(Copyright (c) 2025-2026 luoye-cpu)。 ⚠ MIT 是本库原创部分的授权;libjxl 派生件自带的 BSD-3-Clause 署名义务不被 MIT 豁免, 再分发源码须保留THIRD-PARTY-NOTICES.md,再分发二进制须按该文件 §1 第 2 条复述声明。 - 「唯一版权人」前提 —— 已验证(不是假设):
git log全量作者去重 = 1 人(luoye-cpu,68 个提交全部);且触及被抽取文件 (Services/ColorMapping/、ColorSpaceRegistry.cs、SimdPixelOps.cs)的提交也全部出自同一作者。 ⇒ 不存在需要他人授权的第三方贡献者,GPL-3.0 → MIT 的重授权在作者原创部分上成立。 (⚠ 若日后引入外部贡献者,新增部分的重授权需另行取得其同意。)
本库提供色彩数学实现,不构成对任何标准符合性的担保。 接入关键产线前请用你自己的参照(如 zimg / libavif / 真实素材)做交叉验证。