skcms_EnsureUsableAsDestinationWithSingleCurve

Like skcms_EnsureUsableAsDestination, but also guarantees that the
resulting profile has a single parametric transfer function. Updated the
error metric (from skcms_BestSingleCurve) to optimize for round-trip
error, like other places in skcms.

Change-Id: Iedb2a72da398f464010547d314fe099b8da6b920
Reviewed-on: https://skia-review.googlesource.com/123424
Auto-Submit: Brian Osman <brianosman@google.com>
Commit-Queue: Mike Klein <mtklein@chromium.org>
Reviewed-by: Mike Klein <mtklein@chromium.org>
diff --git a/profiles/misc/DisplayCal_ASUS_NonMonotonic.icc.txt b/profiles/misc/DisplayCal_ASUS_NonMonotonic.icc.txt
index b29fa23..8ba5ca0 100644
--- a/profiles/misc/DisplayCal_ASUS_NonMonotonic.icc.txt
+++ b/profiles/misc/DisplayCal_ASUS_NonMonotonic.icc.txt
@@ -36,7 +36,7 @@
   ~= : 2.27403, 0.980813, 0.0314167, 0.0311284, 0.192157, -0.0259432, 0 (Max error: 0.0558824) (D-gap: 0)
 bTRC : 16-bit table with 256 entries
   ~= : 2.19997, 1.0228, -0.0143504, 0.00389105, 0.160784, -0.0147935, 0 (Max error: 0.152941) (D-gap: -2.32831e-10)
-Best : 2.27403, 0.980813, 0.0314167, 0.0311284, 0.192157, -0.0259432, 0 (D-gap: 0)
+Best : 2.19997, 1.0228, -0.0143504, 0.00389105, 0.160784, -0.0147935, 0 (D-gap: -2.32831e-10)
  XYZ : | 0.436737061 0.380325317 0.147140503 |
        | 0.217636108 0.729843140 0.052520752 |
        | 0.002655029 0.064407349 0.757827759 |
diff --git a/skcms.h b/skcms.h
index 54c9e8e..108eb58 100644
--- a/skcms.h
+++ b/skcms.h
@@ -203,6 +203,12 @@
 // (e.g. skcms_sRGB_profile) where not.
 void skcms_EnsureUsableAsDestination(skcms_ICCProfile* profile, const skcms_ICCProfile* fallback);
 
+// If profile cannot be used as a destination profile with a single parametric transfer function,
+// (ie for rasterization), rewrite it with approximations where reasonable or by pulling from
+// fallback (e.g. skcms_sRGB_profile) where not.
+void skcms_EnsureUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile,
+                                                    const skcms_ICCProfile* fallback);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/Transform.c b/src/Transform.c
index b3a6b55..16cfaec 100644
--- a/src/Transform.c
+++ b/src/Transform.c
@@ -6,6 +6,7 @@
  */
 
 #include "../skcms.h"
+#include "GaussNewton.h"
 #include "LinearAlgebra.h"
 #include "Macros.h"
 #include "PortableMath.h"
@@ -643,3 +644,46 @@
     *profile = ok;
     assert_usable_as_destination(profile);
 }
+
+static float max_roundtrip_error(const skcms_TransferFunction* inv_tf, const skcms_Curve* curve) {
+    int N = curve->table_entries ? (int)curve->table_entries : 256;
+    const float x_scale = 1.0f / (N - 1);
+    float err = 0;
+    for (int i = 0; i < N; i++) {
+        float x = i * x_scale,
+              y = skcms_eval_curve(x, curve);
+        err = fmaxf_(err, fabsf_(x - skcms_TransferFunction_eval(inv_tf, y)));
+    }
+    return err;
+}
+
+void skcms_EnsureUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile,
+                                                    const skcms_ICCProfile* fallback) {
+    // Operate on a copy of profile, so we can choose the best TF for the original curves
+    skcms_ICCProfile result = *profile;
+    skcms_EnsureUsableAsDestination(&result, fallback);
+
+    int best_tf = 0;
+    float min_max_error = INFINITY_;
+    const skcms_ICCProfile* ref = profile->has_trc ? profile : fallback;
+    for (int i = 0; i < 3; i++) {
+        skcms_TransferFunction inv;
+        skcms_TransferFunction_invert(&result.trc[i].parametric, &inv);
+
+        float err = 0;
+        for (int j = 0; j < 3; ++j) {
+            err = fmaxf_(err, max_roundtrip_error(&inv, &ref->trc[j]));
+        }
+        if (min_max_error > err) {
+            min_max_error = err;
+            best_tf = i;
+        }
+    }
+
+    for (int i = 0; i < 3; i++) {
+        result.trc[i].parametric = result.trc[best_tf].parametric;
+    }
+
+    *profile = result;
+    assert_usable_as_destination(profile);
+}
diff --git a/test_only.c b/test_only.c
index 43b72d9..6b7ebae 100644
--- a/test_only.c
+++ b/test_only.c
@@ -155,8 +155,9 @@
         }
     }
 
-    skcms_TransferFunction best_tf = skcms_BestSingleCurve(profile);
-    dump_transfer_function(fp, "Best", &best_tf, 0.0f);
+    skcms_ICCProfile best_single_curve = *profile;
+    skcms_EnsureUsableAsDestinationWithSingleCurve(&best_single_curve, &skcms_sRGB_profile);
+    dump_transfer_function(fp, "Best", &best_single_curve.trc[0].parametric, 0.0f);
 
     if (profile->has_toXYZD50) {
         skcms_Matrix3x3 toXYZ = profile->toXYZD50;