parse profile following user-supplied A2B priority

This let skcms users indicate which A2B profile to prefer,
preserving skcms_Parse()'s hard-coded priorities.

Change-Id: Idd5e9c04e2c9eb5c8fc628365282c6884df42d24
Reviewed-on: https://skia-review.googlesource.com/c/skcms/+/388202
Commit-Queue: Mike Klein <mtklein@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/skcms.cc b/skcms.cc
index 4f98343..58deef8 100644
--- a/skcms.cc
+++ b/skcms.cc
@@ -1027,7 +1027,9 @@
        || (profile->has_trc && profile->has_toXYZD50);
 }
 
-bool skcms_Parse(const void* buf, size_t len, skcms_ICCProfile* profile) {
+bool skcms_ParseWithA2BPriority(const void* buf, size_t len,
+                                const int priority[], const int priorities,
+                                skcms_ICCProfile* profile) {
     assert(SAFE_SIZEOF(header_Layout) == 132);
 
     if (!profile) {
@@ -1132,13 +1134,13 @@
 
     skcms_ICCTag a2b_tag;
 
-    // For now, we're preferring A2B0, like Skia does and the ICC spec tells us to.
-    // TODO: prefer A2B1 (relative colormetric) over A2B0 (perceptual)?
-    // This breaks with the ICC spec, but we think it's a good idea, given that TRC curves
-    // and all our known users are thinking exclusively in terms of relative colormetric.
-    const uint32_t sigs[] = { skcms_Signature_A2B0, skcms_Signature_A2B1 };
-    for (int i = 0; i < ARRAY_COUNT(sigs); i++) {
-        if (skcms_GetTagBySignature(profile, sigs[i], &a2b_tag)) {
+    for (int i = 0; i < priorities; i++) {
+        // enum { perceptual, relative_colormetric, saturation }
+        if (priority[i] < 0 || priority[i] > 2) {
+            return false;
+        }
+        uint32_t sig = skcms_Signature_A2B0 + static_cast<uint32_t>(priority[i]);
+        if (skcms_GetTagBySignature(profile, sig, &a2b_tag)) {
             if (!read_a2b(&a2b_tag, &profile->A2B, pcs_is_xyz)) {
                 // Malformed A2B tag
                 return false;
diff --git a/skcms.h b/skcms.h
index edaa5d5..cfc2596 100644
--- a/skcms.h
+++ b/skcms.h
@@ -146,8 +146,9 @@
     bool                   has_toXYZD50;
     skcms_Matrix3x3        toXYZD50;
 
-    // If the profile has a valid A2B0 tag, skcms_Parse() sets A2B to that data,
-    // and has_A2B to true.
+    // If the profile has a valid A2B0 or A2B1 tag, skcms_Parse() sets A2B to
+    // that data, and has_A2B to true.  skcms_ParseWithA2BPriority() does the
+    // same following any user-provided prioritization of A2B0, A2B1, or A2B2.
     bool                   has_A2B;
     skcms_A2B              A2B;
 } skcms_ICCProfile;
@@ -180,9 +181,20 @@
                                                 const skcms_TransferFunction* inv_tf);
 
 // Parse an ICC profile and return true if possible, otherwise return false.
-// The buffer is not copied, it must remain valid as long as the skcms_ICCProfile
-// will be used.
-SKCMS_API bool skcms_Parse(const void*, size_t, skcms_ICCProfile*);
+// Selects an A2B profile (if present) according to priority list (each entry 0-2).
+// The buffer is not copied; it must remain valid as long as the skcms_ICCProfile will be used.
+SKCMS_API bool skcms_ParseWithA2BPriority(const void*, size_t,
+                                          const int priority[], int priorities,
+                                          skcms_ICCProfile*);
+
+static inline bool skcms_Parse(const void* buf, size_t len, skcms_ICCProfile* profile) {
+    // For continuity of existing user expectations,
+    // prefer A2B0 (perceptual) over A2B1 (relative colormetric), and ignore A2B2 (saturation).
+    const int priority[] = {0,1};
+    return skcms_ParseWithA2BPriority(buf, len,
+                                      priority, sizeof(priority)/sizeof(*priority),
+                                      profile);
+}
 
 SKCMS_API bool skcms_ApproximateCurve(const skcms_Curve* curve,
                                       skcms_TransferFunction* approx,
diff --git a/tests.c b/tests.c
index 86ef3e1..b4f41fe 100644
--- a/tests.c
+++ b/tests.c
@@ -1769,6 +1769,36 @@
     free(ptr);
 }
 
+static void test_ParseWithA2BPriority() {
+    void*  ptr;
+    size_t len;
+    expect(load_file("profiles/misc/US_Web_Coated_SWOP_CMYK.icc", &ptr,&len));
+
+    skcms_ICCProfile simple;
+    expect(skcms_Parse(ptr, len, &simple));  // This will pick up A2B0.
+    expect(simple.has_A2B);
+
+    for (int priority = -1; priority < 4; priority++) {
+        skcms_ICCProfile profile;
+
+        bool ok = skcms_ParseWithA2BPriority(ptr, len, &priority, 1, &profile);
+        if (priority < 0 || priority > 2) {
+            expect(!ok);
+            continue;
+        }
+        expect(ok);
+        if (priority == 0 || priority == 2) {
+            // A2B0 and A2B2 are the same in this profile.
+            expect(0 == memcmp(&profile, &simple, sizeof(profile)));
+        } else {
+            // A2B1 is different.
+            expect(0 != memcmp(&profile, &simple, sizeof(profile)));
+        }
+    }
+
+    free(ptr);
+}
+
 int main(int argc, char** argv) {
     bool regenTestData = false;
     for (int i = 1; i < argc; ++i) {
@@ -1811,6 +1841,7 @@
     test_PQ_invert();
     test_HLG_invert();
     test_RGBA_8888_sRGB();
+    test_ParseWithA2BPriority();
 
     // Temporarily disable some tests while getting FP16 compute working.
     if (!kFP16) {