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