Add skcms_AdaptToXYZD50 to the API

This splits of the part inside skcms_PrimariesToXYZD50 which computes
the chromatic adaptation matrix into a new function
skcms_AdaptToXYZD50.

This is useful when creating an ICC profile, to convert values to D50
which ICC uses.

Change-Id: I2a453ee9f17f7173868bd8341ef793614dde01bb
Reviewed-on: https://skia-review.googlesource.com/c/skcms/+/259136
Reviewed-by: Mike Klein <mtklein@google.com>
Commit-Queue: Mike Klein <mtklein@google.com>
diff --git a/skcms.cc b/skcms.cc
index cc5738d..2765567 100644
--- a/skcms.cc
+++ b/skcms.cc
@@ -1364,6 +1364,47 @@
     return dst;
 }
 
+bool skcms_AdaptToXYZD50(float wx, float wy,
+                         skcms_Matrix3x3* toXYZD50) {
+    if (!is_zero_to_one(wx) || !is_zero_to_one(wy) ||
+        !toXYZD50) {
+        return false;
+    }
+
+    // Assumes that Y is 1.0f.
+    skcms_Vector3 wXYZ = { { wx / wy, 1, (1 - wx - wy) / wy } };
+
+    // Now convert toXYZ matrix to toXYZD50.
+    skcms_Vector3 wXYZD50 = { { 0.96422f, 1.0f, 0.82521f } };
+
+    // Calculate the chromatic adaptation matrix.  We will use the Bradford method, thus
+    // the matrices below.  The Bradford method is used by Adobe and is widely considered
+    // to be the best.
+    skcms_Matrix3x3 xyz_to_lms = {{
+        {  0.8951f,  0.2664f, -0.1614f },
+        { -0.7502f,  1.7135f,  0.0367f },
+        {  0.0389f, -0.0685f,  1.0296f },
+    }};
+    skcms_Matrix3x3 lms_to_xyz = {{
+        {  0.9869929f, -0.1470543f, 0.1599627f },
+        {  0.4323053f,  0.5183603f, 0.0492912f },
+        { -0.0085287f,  0.0400428f, 0.9684867f },
+    }};
+
+    skcms_Vector3 srcCone = mv_mul(&xyz_to_lms, &wXYZ);
+    skcms_Vector3 dstCone = mv_mul(&xyz_to_lms, &wXYZD50);
+
+    *toXYZD50 = {{
+        { dstCone.vals[0] / srcCone.vals[0], 0, 0 },
+        { 0, dstCone.vals[1] / srcCone.vals[1], 0 },
+        { 0, 0, dstCone.vals[2] / srcCone.vals[2] },
+    }};
+    *toXYZD50 = skcms_Matrix3x3_concat(toXYZD50, &xyz_to_lms);
+    *toXYZD50 = skcms_Matrix3x3_concat(&lms_to_xyz, toXYZD50);
+
+    return true;
+}
+
 bool skcms_PrimariesToXYZD50(float rx, float ry,
                              float gx, float gy,
                              float bx, float by,
@@ -1399,33 +1440,10 @@
     }};
     toXYZ = skcms_Matrix3x3_concat(&primaries, &toXYZ);
 
-    // Now convert toXYZ matrix to toXYZD50.
-    skcms_Vector3 wXYZD50 = { { 0.96422f, 1.0f, 0.82521f } };
-
-    // Calculate the chromatic adaptation matrix.  We will use the Bradford method, thus
-    // the matrices below.  The Bradford method is used by Adobe and is widely considered
-    // to be the best.
-    skcms_Matrix3x3 xyz_to_lms = {{
-        {  0.8951f,  0.2664f, -0.1614f },
-        { -0.7502f,  1.7135f,  0.0367f },
-        {  0.0389f, -0.0685f,  1.0296f },
-    }};
-    skcms_Matrix3x3 lms_to_xyz = {{
-        {  0.9869929f, -0.1470543f, 0.1599627f },
-        {  0.4323053f,  0.5183603f, 0.0492912f },
-        { -0.0085287f,  0.0400428f, 0.9684867f },
-    }};
-
-    skcms_Vector3 srcCone = mv_mul(&xyz_to_lms, &wXYZ);
-    skcms_Vector3 dstCone = mv_mul(&xyz_to_lms, &wXYZD50);
-
-    skcms_Matrix3x3 DXtoD50 = {{
-        { dstCone.vals[0] / srcCone.vals[0], 0, 0 },
-        { 0, dstCone.vals[1] / srcCone.vals[1], 0 },
-        { 0, 0, dstCone.vals[2] / srcCone.vals[2] },
-    }};
-    DXtoD50 = skcms_Matrix3x3_concat(&DXtoD50, &xyz_to_lms);
-    DXtoD50 = skcms_Matrix3x3_concat(&lms_to_xyz, &DXtoD50);
+    skcms_Matrix3x3 DXtoD50;
+    if (!skcms_AdaptToXYZD50(wx, wy, &DXtoD50)) {
+        return false;
+    }
 
     *toXYZD50 = skcms_Matrix3x3_concat(&DXtoD50, &toXYZ);
     return true;
diff --git a/skcms.h b/skcms.h
index 3df6b44..165264c 100644
--- a/skcms.h
+++ b/skcms.h
@@ -293,6 +293,12 @@
 // profile unchanged and return false.
 SKCMS_API bool skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile);
 
+// Returns a matrix to adapt XYZ color from given the whitepoint to D50.
+SKCMS_API bool skcms_AdaptToXYZD50(float wx, float wy,
+                                   skcms_Matrix3x3* toXYZD50);
+
+// Returns a matrix to convert RGB color into XYZ adapted to D50, given the
+// primaries and whitepoint of the RGB model.
 SKCMS_API bool skcms_PrimariesToXYZD50(float rx, float ry,
                                        float gx, float gy,
                                        float bx, float by,
diff --git a/tests.c b/tests.c
index 7a69a31..522cb35 100644
--- a/tests.c
+++ b/tests.c
@@ -1216,6 +1216,24 @@
     free(ptr);
 }
 
+static void test_AdaptToD50() {
+    skcms_Matrix3x3 xyz_to_xyzD50;
+    float x_D65 = 0.3127f;
+    float y_D65 = 0.3290f;
+    expect(skcms_AdaptToXYZD50(x_D65, y_D65, &xyz_to_xyzD50));
+    skcms_Matrix3x3 sRGB_D65 = {{
+        { 0.4124564f, 0.3575761f, 0.1804375f },
+        { 0.2126729f, 0.7151522f, 0.0721750f },
+        { 0.0193339f, 0.1191920f, 0.9503041f }
+    }};
+    skcms_Matrix3x3 sRGB_D50 = skcms_Matrix3x3_concat(&xyz_to_xyzD50, &sRGB_D65);
+    skcms_ICCProfile p = *skcms_sRGB_profile();
+    for (int r = 0; r < 3; ++r)
+        for (int c = 0; c < 3; ++c) {
+            expect(fabsf_(sRGB_D50.vals[r][c] - p.toXYZD50.vals[r][c]) < 0.0001f);
+        }
+}
+
 static void test_PrimariesToXYZ() {
     skcms_Matrix3x3 srgb_to_xyz;
     expect(skcms_PrimariesToXYZD50(0.64f, 0.33f,
@@ -1613,6 +1631,7 @@
     test_ByteToLinearFloat();
     test_MakeUsableAsDestination();
     test_MakeUsableAsDestinationAdobe();
+    test_AdaptToD50();
     test_PrimariesToXYZ();
     test_Programmatic_sRGB();
     test_ExactlyEqual();