Roll skcms from a58dfb3b4353 to 7852fc3f0085 (1 revision)
https://skia.googlesource.com/skcms.git/+log/a58dfb3b4353..7852fc3f0085
2025-08-28 ccameron@chromium.org skcms_Transform: Add HLG OOTF
If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/skcms-skia-autoroll
Please CC kjlubick@google.com,nicolettep@google.com on the revert to ensure that a human
is aware of the problem.
To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry
To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622
Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Cq-Include-Trybots: skia/skia.primary:Canary-Chromium
Tbr: kjlubick@google.com,nicolettep@google.com
Change-Id: I961ba5729a695c0b4a81267c6e3a1b5db548631e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1047838
Commit-Queue: skia-autoroll <skia-autoroll@skia-public.iam.gserviceaccount.com>
Bot-Commit: skia-autoroll <skia-autoroll@skia-public.iam.gserviceaccount.com>
diff --git a/modules/skcms/skcms.cc b/modules/skcms/skcms.cc
index bf1e61d..00b19ec 100644
--- a/modules/skcms/skcms.cc
+++ b/modules/skcms/skcms.cc
@@ -2645,11 +2645,13 @@
skcms_TransferFunction* invR,
skcms_TransferFunction* invG,
skcms_TransferFunction* invB,
- bool* dst_using_B2A) {
+ bool* dst_using_B2A,
+ bool* dst_using_hlg_ootf) {
const bool has_xyzd50 =
profile->has_toXYZD50 &&
skcms_Matrix3x3_invert(&profile->toXYZD50, fromXYZD50);
*dst_using_B2A = false;
+ *dst_using_hlg_ootf = false;
// CICP-specified PQ or HLG transfer functions take precedence.
// TODO: Add the ability to parse CICP primaries to not require
@@ -2668,6 +2670,7 @@
skcms_TransferFunction_invert(&trc_hlg, invR);
skcms_TransferFunction_invert(&trc_hlg, invG);
skcms_TransferFunction_invert(&trc_hlg, invB);
+ *dst_using_hlg_ootf = true;
return true;
}
@@ -2758,7 +2761,11 @@
dst_curves[1].table_entries =
dst_curves[2].table_entries = 0;
- skcms_Matrix3x3 from_xyz;
+ // This will store the XYZD50 to destination gamut conversion matrix, if it is needed.
+ skcms_Matrix3x3 dst_from_xyz;
+
+ // This will store the full source to destination gamut conversion matrix, if it is needed.
+ skcms_Matrix3x3 dst_from_src;
switch (srcFmt >> 1) {
default: return false;
@@ -2828,14 +2835,17 @@
// Track whether or not the A2B or B2A transforms are used. the CICP
// values take precedence over A2B and B2A.
bool src_using_A2B = false;
+ bool src_using_hlg_ootf = false;
bool dst_using_B2A = false;
+ bool dst_using_hlg_ootf = false;
if (!prep_for_destination(dstProfile,
- &from_xyz,
+ &dst_from_xyz,
&dst_curves[0].parametric,
&dst_curves[1].parametric,
&dst_curves[2].parametric,
- &dst_using_B2A)) {
+ &dst_using_B2A,
+ &dst_using_hlg_ootf)) {
return false;
}
@@ -2843,6 +2853,7 @@
set_reference_pq_ish_trc(&src_cicp_trc);
add_op_ctx(Op::pq_rgb, &src_cicp_trc);
} else if (has_cicp_hlg_trc(srcProfile) && srcProfile->has_toXYZD50) {
+ src_using_hlg_ootf = true;
set_sdr_hlg_ish_trc(&src_cicp_trc);
add_op_ctx(Op::hlg_rgb, &src_cicp_trc);
} else if (srcProfile->has_A2B) {
@@ -2888,6 +2899,10 @@
// B2A needs its input in XYZD50, so transform TRC sources now.
if (!src_using_A2B) {
add_op_ctx(Op::matrix_3x3, &srcProfile->toXYZD50);
+ // Apply the HLG OOTF in XYZD50 space, if needed.
+ if (src_using_hlg_ootf) {
+ add_op(Op::hlg_ootf_scale);
+ }
}
if (dstProfile->pcs == skcms_Signature_Lab) {
@@ -2920,22 +2935,34 @@
}
} else {
// This is a TRC destination.
- // We'll concat any src->xyz matrix with our xyz->dst matrix into one src->dst matrix.
- // (A2B sources are already in XYZD50, making that src->xyz matrix I.)
- static const skcms_Matrix3x3 I = {{
- { 1.0f, 0.0f, 0.0f },
- { 0.0f, 1.0f, 0.0f },
- { 0.0f, 0.0f, 1.0f },
- }};
- const skcms_Matrix3x3* to_xyz = src_using_A2B ? &I : &srcProfile->toXYZD50;
- // There's a chance the source and destination gamuts are identical,
- // in which case we can skip the gamut transform.
- if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
- // Concat the entire gamut transform into from_xyz,
- // now slightly misnamed but it's a handy spot to stash the result.
- from_xyz = skcms_Matrix3x3_concat(&from_xyz, to_xyz);
- add_op_ctx(Op::matrix_3x3, &from_xyz);
+ // Transform to the destination gamut.
+ if (src_using_hlg_ootf != dst_using_hlg_ootf) {
+ // If just the src or the dst has an HLG OOTF then we will apply the OOTF in XYZD50
+ // space. If both the src and dst has an HLG OOTF then they will cancel.
+ if (!src_using_A2B) {
+ add_op_ctx(Op::matrix_3x3, &srcProfile->toXYZD50);
+ }
+ if (src_using_hlg_ootf) {
+ add_op(Op::hlg_ootf_scale);
+ }
+ if (dst_using_hlg_ootf) {
+ add_op(Op::hlginv_ootf_scale);
+ }
+ add_op_ctx(Op::matrix_3x3, &dst_from_xyz);
+ } else if (src_using_A2B) {
+ // If the source is A2B then we are already in XYZD50. Just apply the xyz->dst
+ // matrix.
+ add_op_ctx(Op::matrix_3x3, &dst_from_xyz);
+ } else {
+ const skcms_Matrix3x3* to_xyz = &srcProfile->toXYZD50;
+ // There's a chance the source and destination gamuts are identical,
+ // in which case we can skip the gamut transform.
+ if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
+ // Concat the entire gamut transform into dst_from_src.
+ dst_from_src = skcms_Matrix3x3_concat(&dst_from_xyz, to_xyz);
+ add_op_ctx(Op::matrix_3x3, &dst_from_src);
+ }
}
// Encode back to dst RGB using its parametric transfer functions.
@@ -3038,7 +3065,8 @@
skcms_Matrix3x3 fromXYZD50;
skcms_TransferFunction invR, invG, invB;
bool useB2A = false;
- assert(prep_for_destination(profile, &fromXYZD50, &invR, &invG, &invB, &useB2A));
+ bool useHlgOotf = false;
+ assert(prep_for_destination(profile, &fromXYZD50, &invR, &invG, &invB, &useB2A, &useHlgOotf));
#endif
}
diff --git a/modules/skcms/src/Transform_inl.h b/modules/skcms/src/Transform_inl.h
index c0b2765..0d48337 100644
--- a/modules/skcms/src/Transform_inl.h
+++ b/modules/skcms/src/Transform_inl.h
@@ -359,6 +359,14 @@
return bit_pun<F>(sign | bit_pun<U32>(v));
}
+// Compute the luminance Y used in the HLG OOTF. This is equivalent to computing the dot product
+// with the vector [0.2627 0.678 0.0593] in Rec2020 primaries, but is performed in the XYZD50
+// space to simplify the pipeline.
+SI F compute_Y_in_xyzd50(F x, F y, F z) {
+ return -0.02831655f * x +
+ 1.00995452f * y +
+ 0.02102382f * z;
+}
// Strided loads and stores of N values, starting from p.
template <typename T, typename P>
@@ -1216,6 +1224,27 @@
b = apply_hlg(tf, b);
}
+// Apply the HLG Reference OOTF, as described in ITU-R BT.2100-3 Table 5.
+STAGE(hlg_ootf_scale, const void*) {
+ // Compute Y in the XYZD50 primaries.
+ F Y = compute_Y_in_xyzd50(r, g, b);
+
+ // Apply the gamma of 1.2.
+ const float gamma_minus_1 = 0.2f;
+ U32 sign;
+ Y = strip_sign(Y, &sign);
+ F Y_to_gamma_minus1 = apply_sign(approx_pow(Y, gamma_minus_1), sign);
+ r = r * Y_to_gamma_minus1;
+ g = g * Y_to_gamma_minus1;
+ b = b * Y_to_gamma_minus1;
+
+ // Scale to the reference peak white (1000 nits) to get display luminance. Then divide by the
+ // HDR reference white (203 nits), to get a value in relative linear color space.
+ r *= 1000.0f / 203.0f;
+ g *= 1000.0f / 203.0f;
+ b *= 1000.0f / 203.0f;
+}
+
STAGE(hlginv_r, const skcms_TransferFunction* tf) { r = apply_hlginv(tf, r); }
STAGE(hlginv_g, const skcms_TransferFunction* tf) { g = apply_hlginv(tf, g); }
STAGE(hlginv_b, const skcms_TransferFunction* tf) { b = apply_hlginv(tf, b); }
@@ -1227,6 +1256,23 @@
b = apply_hlginv(tf, b);
}
+// Perform the inverse of the operation in hlg_ootf_scale.
+STAGE(hlginv_ootf_scale, const void*) {
+ r *= (203.f / 1000.0f);
+ g *= (203.f / 1000.0f);
+ b *= (203.f / 1000.0f);
+
+ const float gamma_inv_minus_1 = 1.0f / 1.2f - 1.0f;
+ F Y = compute_Y_in_xyzd50(r, g, b);
+ U32 sign;
+ Y = strip_sign(Y, &sign);
+ F Y_to_gamma_minus1 = apply_sign(approx_pow(Y, gamma_inv_minus_1), sign);
+
+ r = r * Y_to_gamma_minus1;
+ g = g * Y_to_gamma_minus1;
+ b = b * Y_to_gamma_minus1;
+}
+
STAGE(table_r, const skcms_Curve* curve) { r = table(curve, r); }
STAGE(table_g, const skcms_Curve* curve) { g = table(curve, g); }
STAGE(table_b, const skcms_Curve* curve) { b = table(curve, b); }
diff --git a/modules/skcms/src/skcms_Transform.h b/modules/skcms/src/skcms_Transform.h
index b2c14e1..868d71a 100644
--- a/modules/skcms/src/skcms_Transform.h
+++ b/modules/skcms/src/skcms_Transform.h
@@ -72,12 +72,14 @@
M(hlg_b) \
M(hlg_a) \
M(hlg_rgb) \
+ M(hlg_ootf_scale) \
\
M(hlginv_r) \
M(hlginv_g) \
M(hlginv_b) \
M(hlginv_a) \
M(hlginv_rgb) \
+ M(hlginv_ootf_scale) \
\
M(table_r) \
M(table_g) \
diff --git a/modules/skcms/version.sha1 b/modules/skcms/version.sha1
index 8391f8e..0110163 100755
--- a/modules/skcms/version.sha1
+++ b/modules/skcms/version.sha1
@@ -1 +1 @@
-a58dfb3b43532dbda18030f7cf24d79d2e49e634
+7852fc3f0085c5439e78c2b60fda6fc3776dfb2e