skcms_TransferFunction_invert() for PQ/HLG

Absolutely nothing tricky.

This is enough to clean up tf_is_valid().

Bug: chromium:960620
Change-Id: I56c5017fddf167f576879a7d1041455023108c0c
Reviewed-on: https://skia-review.googlesource.com/c/skcms/+/247729
Commit-Queue: Mike Klein <mtklein@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/skcms.cc b/skcms.cc
index 4a4fc47..85282c0 100644
--- a/skcms.cc
+++ b/skcms.cc
@@ -160,12 +160,6 @@
     return Bad;
 }
 
-// TODO: temporary shim for old call sites
-static bool tf_is_valid(const skcms_TransferFunction* tf) {
-    return classify(*tf) == sRGBish;
-}
-
-
 bool skcms_TransferFunction_makePQish(skcms_TransferFunction* tf,
                                       float A, float B, float C,
                                       float D, float E, float F) {
@@ -489,7 +483,7 @@
             curve->parametric.f = read_big_fixed(paraTag->variable + 24);
             break;
     }
-    return tf_is_valid(&curve->parametric);
+    return classify(curve->parametric) == sRGBish;
 }
 
 typedef struct {
@@ -1511,13 +1505,33 @@
 }
 
 #if defined(__clang__)
-    [[clang::no_sanitize("float-divide-by-zero")]]  // Checked for by tf_is_valid() on the way out.
+    [[clang::no_sanitize("float-divide-by-zero")]]  // Checked for by classify() on the way out.
 #endif
 bool skcms_TransferFunction_invert(const skcms_TransferFunction* src, skcms_TransferFunction* dst) {
-    if (!tf_is_valid(src)) {
-        return false;
+    TF_PQish  pq;
+    TF_HLGish hlg;
+    switch (classify(*src, &pq, &hlg)) {
+        case Bad: return false;
+        case sRGBish: break;  // handled below
+
+        case PQish:
+            *dst = { TFKind_marker(PQish), -pq.A,  pq.D, 1.0f/pq.F
+                                         ,  pq.B, -pq.E, 1.0f/pq.C};
+            return true;
+
+        case HLGish:
+            *dst = { TFKind_marker(HLGinvish), 1.0f/hlg.R, 1.0f/hlg.G
+                                             , 1.0f/hlg.a, hlg.b, hlg.c, 0 };
+            return true;
+
+        case HLGinvish:
+            *dst = { TFKind_marker(HLGish), 1.0f/hlg.R, 1.0f/hlg.G
+                                          , 1.0f/hlg.a, hlg.b, hlg.c, 0 };
+            return true;
     }
 
+    assert (classify(*src) == sRGBish);
+
     // We're inverting this function, solving for x in terms of y.
     //   y = (cx + f)         x < d
     //       (ax + b)^g + e   x ≥ d
@@ -1563,7 +1577,7 @@
     inv.e = -src->b / src->a;
 
     // We need to enforce the same constraints here that we do when fitting a curve,
-    // a >= 0 and ad+b >= 0.  These constraints are checked by tf_is_valid(), so they're true
+    // a >= 0 and ad+b >= 0.  These constraints are checked by classify(), so they're true
     // of the source function if we're here.
 
     // Just like when fitting the curve, there's really no way to rescue a < 0.
@@ -1575,9 +1589,9 @@
         inv.b = -inv.a * inv.d;
     }
 
-    // That should usually make tf_is_valid(&inv) true, but there are a couple situations
+    // That should usually make classify(inv) == sRGBish true, but there are a couple situations
     // where we might still fail here, like non-finite parameter values.
-    if (!tf_is_valid(&inv)) {
+    if (classify(inv) != sRGBish) {
         return false;
     }
 
@@ -1601,7 +1615,7 @@
     }
 
     *dst = inv;
-    return tf_is_valid(dst);
+    return classify(*dst) == sRGBish;
 }
 
 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
diff --git a/tests.c b/tests.c
index b7370a0..026f669 100644
--- a/tests.c
+++ b/tests.c
@@ -1458,6 +1458,79 @@
     expect(12.00001f > skcms_TransferFunction_eval(&dec, 1.0f));
 }
 
+static void test_PQ_invert() {
+    skcms_TransferFunction pqA, invA, invB;
+
+    expect(skcms_TransferFunction_makePQ   (&pqA)  );
+    expect(skcms_TransferFunction_makePQinv(&invA));
+    expect(skcms_TransferFunction_invert(&pqA, &invB));
+
+    // a,b,d,e really just negate and swap around,
+    // so those should be exact.  c and f will 1.0f/x
+    // each other, so they might not be exactly perfect,
+    // but it turns out we do get lucky here.
+
+    expect(invA.g == invB.g);  // I.e. are we still PQ?
+    expect(invA.a == invB.a);
+    expect(invA.b == invB.b);
+    expect(invA.c == invB.c);  // We got lucky here.
+    expect(invA.d == invB.d);
+    expect(invA.e == invB.e);
+    expect(invA.f == invB.f);  // And here.
+
+    // Just for fun, invert back to PQ.
+    // This just tests the same code path twice.
+    skcms_TransferFunction pqB;
+    expect(skcms_TransferFunction_invert(&invA, &pqB));
+
+    expect(pqA.g == pqB.g);
+    expect(pqA.a == pqB.a);
+    expect(pqA.b == pqB.b);
+    expect(pqA.c == pqB.c);
+    expect(pqA.d == pqB.d);
+    expect(pqA.e == pqB.e);
+    expect(pqA.f == pqB.f);
+
+    // PQ functions invert to the same form.
+    expect(pqA.g == invA.g);
+}
+
+static void test_HLG_invert() {
+    skcms_TransferFunction hlgA, invA, invB;
+
+    expect(skcms_TransferFunction_makeHLG   (&hlgA) );
+    expect(skcms_TransferFunction_makeHLGinv(&invA));
+    expect(skcms_TransferFunction_invert(&hlgA, &invB));
+
+    // Like PQ above, some of these values are expected
+    // to be exact, and some of them we're just getting
+    // lucky with that they happen to round trip exactly.
+
+    expect(invA.g == invB.g);  // Is this still HLGinvish?
+    expect(invA.a == invB.a);  // Lucky.
+    expect(invA.b == invB.b);  // Lucky.
+    expect(invA.c == invB.c);  // Lucky.
+    expect(invA.d == invB.d);  // Exact.
+    expect(invA.e == invB.e);  // Exact.
+    expect(invA.f == invB.f);  // Exact (zero).
+
+    // ... and invert back to HLG.
+    // This tests a slightly different code path (really very similar).
+    skcms_TransferFunction hlgB;
+    expect(skcms_TransferFunction_invert(&invB, &hlgB));
+
+    expect(hlgA.g == hlgB.g);
+    expect(hlgA.a == hlgB.a);
+    expect(hlgA.b == hlgB.b);
+    expect(hlgA.c == hlgB.c);
+    expect(hlgA.d == hlgB.d);
+    expect(hlgA.e == hlgB.e);
+    expect(hlgA.f == hlgB.f);
+
+    // HLG functions invert between two different forms.
+    expect(hlgA.g != invA.g);
+}
+
 int main(int argc, char** argv) {
     bool regenTestData = false;
     for (int i = 1; i < argc; ++i) {
@@ -1494,6 +1567,8 @@
     test_Premul();
     test_PQ();
     test_HLG();
+    test_PQ_invert();
+    test_HLG_invert();
 
     // Temporarily disable some tests while getting FP16 compute working.
     if (!kFP16) {