Add MakeUsableAsDestination to replace EnsureUsableAsDestination

If clients want fallback behavior, they can do that manually. This lets
clients make more explicit choices about profiles that aren't even
remotely suitable as destinations.

Change-Id: I2263d2cab9f3efd87ea7b0885349047d965443f4
Reviewed-on: https://skia-review.googlesource.com/125288
Reviewed-by: Mike Klein <mtklein@chromium.org>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/profiles/color.org/Upper_Left.icc.txt b/profiles/color.org/Upper_Left.icc.txt
index a90829b..e6115cb 100644
--- a/profiles/color.org/Upper_Left.icc.txt
+++ b/profiles/color.org/Upper_Left.icc.txt
@@ -17,7 +17,6 @@
  'B2A2' : 'mBA ' :   1792 : 3876
  'B2A1' : 'mBA ' :   1792 : 5668
 
-Best : 2.4, 0.9478673, 0.0521327, 0.07739938, 0.04045, 0, 0 (D-gap: -1.28057e-08)
  A2B : "M", Matrix, "B"
  "M" : 3 inputs
   M0 : 16-bit table with 256 entries
diff --git a/profiles/color.org/Upper_Right.icc.txt b/profiles/color.org/Upper_Right.icc.txt
index 8befd84..d12a71f 100644
--- a/profiles/color.org/Upper_Right.icc.txt
+++ b/profiles/color.org/Upper_Right.icc.txt
@@ -14,7 +14,6 @@
  'A2B2' : 'mAB ' :   1792 : 472
  'A2B1' : 'mAB ' :   1792 : 2264
 
-Best : 2.4, 0.9478673, 0.0521327, 0.07739938, 0.04045, 0, 0 (D-gap: -1.28057e-08)
  A2B : "A", CLUT, "M", Matrix, "B"
  "A" : 3 inputs
   A0 : 1, 1, 0, 0, 0, 0, 0 (Identity)
diff --git a/profiles/color.org/sRGB_ICC_v4_Appearance.icc.txt b/profiles/color.org/sRGB_ICC_v4_Appearance.icc.txt
index 11067a8..90b074c 100644
--- a/profiles/color.org/sRGB_ICC_v4_Appearance.icc.txt
+++ b/profiles/color.org/sRGB_ICC_v4_Appearance.icc.txt
@@ -15,7 +15,6 @@
  'B2A1' : 'mBA ' :    436 : 63420
  'rig0' : 'sig ' :     12 : 63856
 
-Best : 2.4, 0.9478673, 0.0521327, 0.07739938, 0.04045, 0, 0 (D-gap: -1.28057e-08)
  A2B : "A", CLUT, "M", Matrix, "B"
  "A" : 3 inputs
   A0 : 1, 1, 0, 0, 0, 0, 0 (Identity)
diff --git a/profiles/color.org/sRGB_v4_ICC_preference.icc.txt b/profiles/color.org/sRGB_v4_ICC_preference.icc.txt
index 7e2670a..fa6bf76 100644
--- a/profiles/color.org/sRGB_v4_ICC_preference.icc.txt
+++ b/profiles/color.org/sRGB_v4_ICC_preference.icc.txt
@@ -15,7 +15,6 @@
  'cprt' : 'mluc' :    118 : 60796
  'chad' : 'sf32' :     44 : 60916
 
-Best : 2.4, 0.9478673, 0.0521327, 0.07739938, 0.04045, 0, 0 (D-gap: -1.28057e-08)
  A2B : "A", CLUT, "M", Matrix, "B"
  "A" : 3 inputs
   A0 : 1, 1, 0, 0, 0, 0, 0 (Identity)
diff --git a/profiles/fuzz/infinite_roundtrip.icc.txt b/profiles/fuzz/infinite_roundtrip.icc.txt
index 08f9e4a..7140dea 100644
--- a/profiles/fuzz/infinite_roundtrip.icc.txt
+++ b/profiles/fuzz/infinite_roundtrip.icc.txt
@@ -19,7 +19,6 @@
 rTRC : 1.996582, 8224.125, 8224.125, 8224.125, 0, 8224.125, 0
 gTRC : 1.996582, 8224.125, 8224.125, 8224.125, 0, 8224.125, 0
 bTRC : 1.996582, 8224.125, 8224.125, 8224.125, 0, 8224.125, 0
-Best : 1.996582, 8224.125, 8224.125, 8224.125, 0, 8224.125, 0
  XYZ : | 8224.125000000 8224.125000000 8224.125000000 |
        | 8224.125000000 8224.125000000 8224.125000000 |
        | 8224.125000000 8224.125000000 8224.125000000 |
diff --git a/profiles/misc/Apple_Wide_Color.icc.txt b/profiles/misc/Apple_Wide_Color.icc.txt
index 1347a41..276f52a 100644
--- a/profiles/misc/Apple_Wide_Color.icc.txt
+++ b/profiles/misc/Apple_Wide_Color.icc.txt
@@ -14,7 +14,6 @@
  'A2B0' : 'mAB ' :  29772 : 408
  'A2B1' : 'mAB ' :  29772 : 408
 
-Best : 2.4, 0.9478673, 0.0521327, 0.07739938, 0.04045, 0, 0 (D-gap: -1.28057e-08)
  A2B : "A", CLUT, "M", Matrix, "B"
  "A" : 3 inputs
   A0 : 1, 1, 0, 0, 0, 0, 0 (Identity)
diff --git a/profiles/misc/Coated_FOGRA39_CMYK.icc.txt b/profiles/misc/Coated_FOGRA39_CMYK.icc.txt
index 38da40d..e5dc0b8 100644
--- a/profiles/misc/Coated_FOGRA39_CMYK.icc.txt
+++ b/profiles/misc/Coated_FOGRA39_CMYK.icc.txt
@@ -20,7 +20,6 @@
  'B2A2' : 'mft1' : 145588 : 471752
  'gamt' : 'mft1' :  37009 : 617340
 
-Best : 2.4, 0.9478673, 0.0521327, 0.07739938, 0.04045, 0, 0 (D-gap: -1.28057e-08)
  A2B : "A", CLUT, "B"
  "A" : 4 inputs
   A0 : 16-bit table with 256 entries
diff --git a/profiles/misc/ColorLogic_ISO_Coated_CMYK.icc.txt b/profiles/misc/ColorLogic_ISO_Coated_CMYK.icc.txt
index 9b8e984..f20257a 100644
--- a/profiles/misc/ColorLogic_ISO_Coated_CMYK.icc.txt
+++ b/profiles/misc/ColorLogic_ISO_Coated_CMYK.icc.txt
@@ -20,7 +20,6 @@
  'targ' : 'text' :  73446 : 1751172
  'Info' : 'text' :    902 : 1824620
 
-Best : 2.4, 0.9478673, 0.0521327, 0.07739938, 0.04045, 0, 0 (D-gap: -1.28057e-08)
  A2B : "A", CLUT, "B"
  "A" : 4 inputs
   A0 : 16-bit table with 256 entries
diff --git a/profiles/misc/Japan_Color_2001_Coated.icc.txt b/profiles/misc/Japan_Color_2001_Coated.icc.txt
index 417cf0f..e11cbec 100644
--- a/profiles/misc/Japan_Color_2001_Coated.icc.txt
+++ b/profiles/misc/Japan_Color_2001_Coated.icc.txt
@@ -16,7 +16,6 @@
  'B2A2' : 'mft1' : 145588 : 374568
  'gamt' : 'mft1' :  37009 : 520156
 
-Best : 2.4, 0.9478673, 0.0521327, 0.07739938, 0.04045, 0, 0 (D-gap: -1.28057e-08)
  A2B : "A", CLUT, "B"
  "A" : 4 inputs
   A0 : 16-bit table with 256 entries
diff --git a/profiles/misc/Lexmark_X110.icc.txt b/profiles/misc/Lexmark_X110.icc.txt
index 2db4ba2..d69061e 100644
--- a/profiles/misc/Lexmark_X110.icc.txt
+++ b/profiles/misc/Lexmark_X110.icc.txt
@@ -12,7 +12,6 @@
  'wtpt' : 'XYZ ' :     20 : 688
  'A2B0' : 'mft1' :  16323 : 708
 
-Best : 2.4, 0.9478673, 0.0521327, 0.07739938, 0.04045, 0, 0 (D-gap: -1.28057e-08)
  A2B : "A", CLUT, "B"
  "A" : 3 inputs
   A0 : 1, 1, 0, 0, 0, 0, 0 (Identity)
diff --git a/profiles/misc/MartiMaria_browsertest_A2B.icc.txt b/profiles/misc/MartiMaria_browsertest_A2B.icc.txt
index 1b9f746..f1e231c 100644
--- a/profiles/misc/MartiMaria_browsertest_A2B.icc.txt
+++ b/profiles/misc/MartiMaria_browsertest_A2B.icc.txt
@@ -12,7 +12,6 @@
  'A2B1' : 'mft2' :  29554 : 480
  'A2B2' : 'mft2' :  29554 : 480
 
-Best : 2.4, 0.9478673, 0.0521327, 0.07739938, 0.04045, 0, 0 (D-gap: -1.28057e-08)
  A2B : "A", CLUT, "B"
  "A" : 3 inputs
   A0 : 1, 1, 0, 0, 0, 0, 0 (Identity)
diff --git a/profiles/misc/MartiMaria_browsertest_HARD.icc.txt b/profiles/misc/MartiMaria_browsertest_HARD.icc.txt
index 615749a..d1a5cc8 100644
--- a/profiles/misc/MartiMaria_browsertest_HARD.icc.txt
+++ b/profiles/misc/MartiMaria_browsertest_HARD.icc.txt
@@ -22,7 +22,6 @@
 gTRC : 16-bit table with 255 entries
   ~= : 1, 0.003875792, 0, 0, 0, 0, 0 (Max error: 5.96046e-08)
 bTRC : 16-bit table with 255 entries
-Best : 2.4, 0.9478673, 0.0521327, 0.07739938, 0.04045, 0, 0 (D-gap: -1.28057e-08)
  XYZ : | 0.964202881 0.000000000 0.964202881 |
        | 1.000000000 0.000000000 1.000000000 |
        | 0.824905396 0.000000000 0.824905396 |
diff --git a/profiles/misc/Phase_One_P25.icc.txt b/profiles/misc/Phase_One_P25.icc.txt
index 6374605..cc85893 100644
--- a/profiles/misc/Phase_One_P25.icc.txt
+++ b/profiles/misc/Phase_One_P25.icc.txt
@@ -23,7 +23,6 @@
 gTRC : 16-bit table with 256 entries
 bTRC : 16-bit table with 256 entries
   ~= : 0.7391114, 0.8479661, -0.009976073, 2.721141, 0.01176471, 0.03201343, 0 (Max error: 0.140139) (D-gap: 0)
-Best : 0.3803044, 1.742777, 0.01527609, 3.844358, 0.007843138, -0.2298338, 0 (D-gap: -2.93106e-05)
  XYZ : | 0.647903442 0.357360840 0.156417847 |
        | 0.382919312 1.109725952 0.000000000 |
        | 0.083267212 0.679275513 0.523422241 |
diff --git a/profiles/misc/PrintOpen_ISO_Coated_CMYK.icc.txt b/profiles/misc/PrintOpen_ISO_Coated_CMYK.icc.txt
index 3ef93bd..b079732 100644
--- a/profiles/misc/PrintOpen_ISO_Coated_CMYK.icc.txt
+++ b/profiles/misc/PrintOpen_ISO_Coated_CMYK.icc.txt
@@ -19,7 +19,6 @@
  'desc' : 'desc' :    152 : 1702240
  'targ' : 'text' : 126685 : 1702392
 
-Best : 2.4, 0.9478673, 0.0521327, 0.07739938, 0.04045, 0, 0 (D-gap: -1.28057e-08)
  A2B : "A", CLUT, "B"
  "A" : 4 inputs
   A0 : 1, 1, 0, 0, 0, 0, 0 (Identity)
diff --git a/profiles/misc/SWOP_Coated_20_GCR_CMYK.icc.txt b/profiles/misc/SWOP_Coated_20_GCR_CMYK.icc.txt
index 46e95a6..4228868 100644
--- a/profiles/misc/SWOP_Coated_20_GCR_CMYK.icc.txt
+++ b/profiles/misc/SWOP_Coated_20_GCR_CMYK.icc.txt
@@ -17,7 +17,6 @@
  'wtpt' : 'XYZ ' :     20 : 492
  'AS00' : 'data' :    144 : 723996
 
-Best : 2.4, 0.9478673, 0.0521327, 0.07739938, 0.04045, 0, 0 (D-gap: -1.28057e-08)
  A2B : "A", CLUT, "B"
  "A" : 4 inputs
   A0 : 16-bit table with 256 entries
diff --git a/profiles/misc/US_Web_Coated_SWOP_CMYK.icc.txt b/profiles/misc/US_Web_Coated_SWOP_CMYK.icc.txt
index 1445a2e..d4c8e7a 100644
--- a/profiles/misc/US_Web_Coated_SWOP_CMYK.icc.txt
+++ b/profiles/misc/US_Web_Coated_SWOP_CMYK.icc.txt
@@ -16,7 +16,6 @@
  'B2A2' : 'mft1' : 145588 : 374568
  'gamt' : 'mft1' :  37009 : 520156
 
-Best : 2.4, 0.9478673, 0.0521327, 0.07739938, 0.04045, 0, 0 (D-gap: -1.28057e-08)
  A2B : "A", CLUT, "B"
  "A" : 4 inputs
   A0 : 16-bit table with 256 entries
diff --git a/profiles/misc/XRite_GRACol7_340_CMYK.icc.txt b/profiles/misc/XRite_GRACol7_340_CMYK.icc.txt
index 8858d24..9aafa4e 100644
--- a/profiles/misc/XRite_GRACol7_340_CMYK.icc.txt
+++ b/profiles/misc/XRite_GRACol7_340_CMYK.icc.txt
@@ -20,7 +20,6 @@
  'DEVS' : 'MSBN' : 692224 : 2779892
  'desc' : 'desc' :    138 : 3472116
 
-Best : 2.4, 0.9478673, 0.0521327, 0.07739938, 0.04045, 0, 0 (D-gap: -1.28057e-08)
  A2B : "A", CLUT, "B"
  "A" : 4 inputs
   A0 : 1, 1, 0, 0, 0, 0, 0 (Identity)
diff --git a/profiles/misc/sRGB_ICC_v4_beta.icc.txt b/profiles/misc/sRGB_ICC_v4_beta.icc.txt
index e0734cd..00f6e21 100644
--- a/profiles/misc/sRGB_ICC_v4_beta.icc.txt
+++ b/profiles/misc/sRGB_ICC_v4_beta.icc.txt
@@ -15,7 +15,6 @@
  'B2A1' : 'mBA ' :    508 : 63408
  'rig0' : 'sig ' :     12 : 63916
 
-Best : 2.4, 0.9478673, 0.0521327, 0.07739938, 0.04045, 0, 0 (D-gap: -1.28057e-08)
  A2B : "A", CLUT, "M", Matrix, "B"
  "A" : 3 inputs
   A0 : 1, 1, 0, 0, 0, 0, 0 (Identity)
diff --git a/skcms.h b/skcms.h
index 2053fe6..36ac4a0 100644
--- a/skcms.h
+++ b/skcms.h
@@ -221,6 +221,17 @@
 SKCMS_API void skcms_EnsureUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile,
                                                               const skcms_ICCProfile* fallback);
 
+// If profile can be used as a destination in skcms_Transform, return true. Otherwise, attempt to
+// rewrite it with approximations where reasonable. If successful, return true. If no reasonable
+// approximation exists, leave the profile unchanged and return false.
+SKCMS_API bool skcms_MakeUsableAsDestination(skcms_ICCProfile* profile);
+
+// If profile can be used as a destination with a single parametric transfer function (ie for
+// rasterization), return true. Otherwise, attempt to rewrite it with approximations where
+// reasonable. If successful, return true. If not reasonable approximation exists, leave the
+// profile unchanged and return false.
+SKCMS_API bool skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/Transform.c b/src/Transform.c
index 7aaed63..ae26e71 100644
--- a/src/Transform.c
+++ b/src/Transform.c
@@ -697,3 +697,67 @@
     *profile = result;
     assert_usable_as_destination(profile);
 }
+
+bool skcms_MakeUsableAsDestination(skcms_ICCProfile* profile) {
+    skcms_Matrix3x3 fromXYZD50;
+    if (!profile->has_trc || !profile->has_toXYZD50
+        || !skcms_Matrix3x3_invert(&profile->toXYZD50, &fromXYZD50)) {
+        return false;
+    }
+
+    skcms_TransferFunction tf[3];
+    for (int i = 0; i < 3; i++) {
+        skcms_TransferFunction inv;
+        if (profile->trc[i].table_entries == 0
+            && skcms_TransferFunction_invert(&profile->trc[i].parametric, &inv)) {
+            tf[i] = profile->trc[i].parametric;
+            continue;
+        }
+
+        float max_error;
+        // Parametric curves from skcms_ApproximateCurve() are guaranteed to be invertible.
+        if (!skcms_ApproximateCurve(&profile->trc[i], &tf[i], &max_error)) {
+            return false;
+        }
+    }
+
+    for (int i = 0; i < 3; ++i) {
+        profile->trc[i].table_entries = 0;
+        profile->trc[i].parametric = tf[i];
+    }
+
+    assert_usable_as_destination(profile);
+    return true;
+}
+
+bool skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile) {
+    // Operate on a copy of profile, so we can choose the best TF for the original curves
+    skcms_ICCProfile result = *profile;
+    if (!skcms_MakeUsableAsDestination(&result)) {
+        return false;
+    }
+
+    int best_tf = 0;
+    float min_max_error = INFINITY_;
+    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, &profile->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);
+    return true;
+}
diff --git a/test_only.c b/test_only.c
index 7a37dd3..55c7dae 100644
--- a/test_only.c
+++ b/test_only.c
@@ -184,8 +184,9 @@
     }
 
     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 (skcms_MakeUsableAsDestinationWithSingleCurve(&best_single_curve)) {
+        dump_transfer_function(fp, "Best", &best_single_curve.trc[0].parametric, 0.0f);
+    }
 
     if (profile->has_toXYZD50) {
         skcms_Matrix3x3 toXYZ = profile->toXYZD50;
diff --git a/tests.c b/tests.c
index 9d88f33..a6154f9 100644
--- a/tests.c
+++ b/tests.c
@@ -534,6 +534,17 @@
             fprintf(dump, "Unable to parse ICC profile\n");
         }
 
+        // MakeUsable functions should leave input unchanged when returning false
+        skcms_ICCProfile as_dst = profile;
+        if (!skcms_MakeUsableAsDestination(&as_dst)) {
+            expect(memcmp(&as_dst, &profile, sizeof(profile)) == 0);
+        }
+
+        as_dst = profile;
+        if (!skcms_MakeUsableAsDestinationWithSingleCurve(&as_dst)) {
+            expect(memcmp(&as_dst, &profile, sizeof(profile)) == 0);
+        }
+
         void* dump_buf = NULL;
         size_t dump_len = 0;
         expect(load_file_fp(dump, &dump_buf, &dump_len));
@@ -994,7 +1005,7 @@
 }
 #endif
 
-static void test_EnsureUsableAsDestination() {
+static void test_MakeUsableAsDestination() {
     void*  ptr;
     size_t len;
     expect(load_file("profiles/mobile/sRGB_LUT.icc", &ptr, &len));
@@ -1010,8 +1021,8 @@
                 &dst, skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul, &profile,
                 1));
 
-    // We take care to use a fallback profile that is not sRGB. :)
-    skcms_EnsureUsableAsDestination(&profile, skcms_XYZD50_profile());
+    // We should be able to approximate this profile
+    expect(skcms_MakeUsableAsDestination(&profile));
 
     // Now the transform should work.
     expect(skcms_Transform(
@@ -1025,7 +1036,7 @@
     free(ptr);
 }
 
-static void test_EnsureUsableAsDestinationAdobe() {
+static void test_MakeUsableAsDestinationAdobe() {
     void*  ptr;
     size_t len;
     expect(load_file("profiles/misc/AdobeRGB.icc", &ptr, &len));
@@ -1034,16 +1045,15 @@
     expect(skcms_Parse(ptr, len, &profile));
 
     skcms_ICCProfile usable_as_dst = profile;
-    skcms_EnsureUsableAsDestination(&usable_as_dst, skcms_sRGB_profile());
+    expect(skcms_MakeUsableAsDestination(&usable_as_dst));
 
-    // These profiles should behave nearly (or exactly) identical.
-    // If we let any part of sRGB (eg PolyTF) leak into usable_as_dst, this will fail.
-    expect(skcms_ApproximatelyEqualProfiles(&profile, &usable_as_dst));
+    // This profile was already parametric, so it should remain unchanged
+    expect(memcmp(&usable_as_dst, &profile, sizeof(profile)) == 0);
 
     // Same sequence as above, using the more aggressive SingleCurve version.
     skcms_ICCProfile single_curve = profile;
-    skcms_EnsureUsableAsDestinationWithSingleCurve(&single_curve, skcms_sRGB_profile());
-    expect(skcms_ApproximatelyEqualProfiles(&profile, &single_curve));
+    expect(skcms_MakeUsableAsDestinationWithSingleCurve(&single_curve));
+    expect(memcmp(&single_curve, &profile, sizeof(profile)) == 0);
 
     free(ptr);
 }
@@ -1139,8 +1149,8 @@
     test_ByteToLinearFloat();
     test_TRC_Table16();
     test_Premul();
-    test_EnsureUsableAsDestination();
-    test_EnsureUsableAsDestinationAdobe();
+    test_MakeUsableAsDestination();
+    test_MakeUsableAsDestinationAdobe();
     test_sRGB_profile_has_poly_tf();
     test_AlmostLinear2();
     test_AlmostLinear3();