Add SkColorSpacePrimaries to help with making D50 matrices

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2304753002

Review-Url: https://codereview.chromium.org/2304753002
diff --git a/include/core/SkColorSpace.h b/include/core/SkColorSpace.h
index a96f622..9b990da 100644
--- a/include/core/SkColorSpace.h
+++ b/include/core/SkColorSpace.h
@@ -13,6 +13,22 @@
 
 class SkData;
 
+/**
+ *  Describes a color gamut with primaries and a white point.
+ */
+struct SK_API SkColorSpacePrimaries {
+    float fRX, fRY;
+    float fGX, fGY;
+    float fBX, fBY;
+    float fWX, fWY;
+
+    /**
+     *  Convert primaries and a white point to a toXYZD50 matrix, the preferred color gamut
+     *  representation of SkColorSpace.
+     */
+    bool toXYZD50(SkMatrix44* toXYZD50) const;
+};
+
 class SK_API SkColorSpace : public SkRefCnt {
 public:
 
@@ -50,7 +66,10 @@
     };
 
     /**
-     *  Create an SkColorSpace from a transfer function and a color gamut transform to D50 XYZ.
+     *  Create an SkColorSpace from a transfer function and a color gamut.
+     *
+     *  Transfer function is specified as linear or sRGB.
+     *  Gamut is specified using the matrix transformation to XYZ D50.
      */
     static sk_sp<SkColorSpace> NewRGB(RenderTargetGamma gamma, const SkMatrix44& toXYZD50);
 
diff --git a/src/codec/SkPngCodec.cpp b/src/codec/SkPngCodec.cpp
index dde49d0..55a2960 100644
--- a/src/codec/SkPngCodec.cpp
+++ b/src/codec/SkPngCodec.cpp
@@ -322,63 +322,6 @@
     0.0139f, 0.0971f, 0.7139f,    // Rz, Gz, Bz
 };
 
-static bool convert_to_D50(SkMatrix44* toXYZD50, float toXYZ[9], float whitePoint[2]) {
-    float wX = whitePoint[0];
-    float wY = whitePoint[1];
-    if (wX < 0.0f || wY < 0.0f || (wX + wY > 1.0f)) {
-        return false;
-    }
-
-    // Calculate the XYZ illuminant.  Call this the src illuminant.
-    float wZ = 1.0f - wX - wY;
-    float scale = 1.0f / wY;
-    // TODO (msarett):
-    // What are common src illuminants?  I'm guessing we will almost always see D65.  Should
-    // we go ahead and save a precomputed D65->D50 Bradford matrix?  Should we exit early if
-    // if the src illuminant is D50?
-    SkVector3 srcXYZ = SkVector3::Make(wX * scale, 1.0f, wZ * scale);
-
-    // The D50 illuminant.
-    SkVector3 dstXYZ = SkVector3::Make(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.
-    // http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
-    SkMatrix mA, mAInv;
-    mA.setAll(0.8951f, 0.2664f, -0.1614f, -0.7502f, 1.7135f, 0.0367f, 0.0389f, -0.0685f, 1.0296f);
-    mAInv.setAll(0.9869929f, -0.1470543f, 0.1599627f, 0.4323053f, 0.5183603f, 0.0492912f,
-                 -0.0085287f, 0.0400428f, 0.9684867f);
-
-    // Map illuminant into cone response domain.
-    SkVector3 srcCone;
-    srcCone.fX = mA[0] * srcXYZ.fX + mA[1] * srcXYZ.fY + mA[2] * srcXYZ.fZ;
-    srcCone.fY = mA[3] * srcXYZ.fX + mA[4] * srcXYZ.fY + mA[5] * srcXYZ.fZ;
-    srcCone.fZ = mA[6] * srcXYZ.fX + mA[7] * srcXYZ.fY + mA[8] * srcXYZ.fZ;
-    SkVector3 dstCone;
-    dstCone.fX = mA[0] * dstXYZ.fX + mA[1] * dstXYZ.fY + mA[2] * dstXYZ.fZ;
-    dstCone.fY = mA[3] * dstXYZ.fX + mA[4] * dstXYZ.fY + mA[5] * dstXYZ.fZ;
-    dstCone.fZ = mA[6] * dstXYZ.fX + mA[7] * dstXYZ.fY + mA[8] * dstXYZ.fZ;
-
-    SkMatrix DXToD50;
-    DXToD50.setIdentity();
-    DXToD50[0] = dstCone.fX / srcCone.fX;
-    DXToD50[4] = dstCone.fY / srcCone.fY;
-    DXToD50[8] = dstCone.fZ / srcCone.fZ;
-    DXToD50.postConcat(mAInv);
-    DXToD50.preConcat(mA);
-
-    SkMatrix toXYZ3x3;
-    toXYZ3x3.setAll(toXYZ[0], toXYZ[3], toXYZ[6], toXYZ[1], toXYZ[4], toXYZ[7], toXYZ[2], toXYZ[5],
-                    toXYZ[8]);
-    toXYZ3x3.postConcat(DXToD50);
-
-    toXYZD50->set3x3(toXYZ3x3[0], toXYZ3x3[3], toXYZ3x3[6],
-                     toXYZ3x3[1], toXYZ3x3[4], toXYZ3x3[7],
-                     toXYZ3x3[2], toXYZ3x3[5], toXYZ3x3[8]);
-    return true;
-}
-
 #endif // LIBPNG >= 1.6
 
 // Returns a colorSpace object that represents any color space information in
@@ -416,26 +359,24 @@
     }
 
     // Next, check for chromaticities.
-    png_fixed_point toXYZFixed[9];
-    float toXYZ[9];
-    png_fixed_point whitePointFixed[2];
-    float whitePoint[2];
+    png_fixed_point chrm[8];
     png_fixed_point gamma;
     float gammas[3];
-    if (png_get_cHRM_XYZ_fixed(png_ptr, info_ptr, &toXYZFixed[0], &toXYZFixed[1], &toXYZFixed[2],
-                               &toXYZFixed[3], &toXYZFixed[4], &toXYZFixed[5], &toXYZFixed[6],
-                               &toXYZFixed[7], &toXYZFixed[8]) &&
-        png_get_cHRM_fixed(png_ptr, info_ptr, &whitePointFixed[0], &whitePointFixed[1], nullptr,
-                           nullptr, nullptr, nullptr, nullptr, nullptr))
+    if (png_get_cHRM_fixed(png_ptr, info_ptr, &chrm[0], &chrm[1], &chrm[2], &chrm[3], &chrm[4],
+                           &chrm[5], &chrm[6], &chrm[7]))
     {
-        for (int i = 0; i < 9; i++) {
-            toXYZ[i] = png_fixed_point_to_float(toXYZFixed[i]);
-        }
-        whitePoint[0] = png_fixed_point_to_float(whitePointFixed[0]);
-        whitePoint[1] = png_fixed_point_to_float(whitePointFixed[1]);
+        SkColorSpacePrimaries primaries;
+        primaries.fRX = png_fixed_point_to_float(chrm[2]);
+        primaries.fRY = png_fixed_point_to_float(chrm[3]);
+        primaries.fGX = png_fixed_point_to_float(chrm[4]);
+        primaries.fGY = png_fixed_point_to_float(chrm[5]);
+        primaries.fBX = png_fixed_point_to_float(chrm[6]);
+        primaries.fBY = png_fixed_point_to_float(chrm[7]);
+        primaries.fWX = png_fixed_point_to_float(chrm[0]);
+        primaries.fWY = png_fixed_point_to_float(chrm[1]);
 
         SkMatrix44 toXYZD50(SkMatrix44::kUninitialized_Constructor);
-        if (!convert_to_D50(&toXYZD50, toXYZ, whitePoint)) {
+        if (!primaries.toXYZD50(&toXYZD50)) {
             toXYZD50.set3x3RowMajorf(gSRGB_toXYZD50);
         }
 
diff --git a/src/core/SkColorSpace.cpp b/src/core/SkColorSpace.cpp
index c6bf4b9..66b980b 100644
--- a/src/core/SkColorSpace.cpp
+++ b/src/core/SkColorSpace.cpp
@@ -9,6 +9,82 @@
 #include "SkColorSpace_Base.h"
 #include "SkColorSpacePriv.h"
 #include "SkOnce.h"
+#include "SkPoint3.h"
+
+static inline bool is_zero_to_one(float v) {
+    return (0.0f <= v) && (v <= 1.0f);
+}
+
+bool SkColorSpacePrimaries::toXYZD50(SkMatrix44* toXYZ_D50) const {
+    if (!is_zero_to_one(fRX) || !is_zero_to_one(fRY) ||
+        !is_zero_to_one(fGX) || !is_zero_to_one(fGY) ||
+        !is_zero_to_one(fBX) || !is_zero_to_one(fBY) ||
+        !is_zero_to_one(fWX) || !is_zero_to_one(fWY))
+    {
+        return false;
+    }
+
+    // First, we need to convert xy values (primaries) to XYZ.
+    SkMatrix primaries;
+    primaries.setAll(             fRX,              fGX,              fBX,
+                                  fRY,              fGY,              fBY,
+                     1.0f - fRX - fRY, 1.0f - fGX - fGY, 1.0f - fBX - fBY);
+    SkMatrix primariesInv;
+    if (!primaries.invert(&primariesInv)) {
+        return false;
+    }
+
+    // Assumes that Y is 1.0f.
+    SkVector3 wXYZ = SkVector3::Make(fWX / fWY, 1.0f, (1.0f - fWX - fWY) / fWY);
+    SkVector3 XYZ;
+    XYZ.fX = primariesInv[0] * wXYZ.fX + primariesInv[1] * wXYZ.fY + primariesInv[2] * wXYZ.fZ;
+    XYZ.fY = primariesInv[3] * wXYZ.fX + primariesInv[4] * wXYZ.fY + primariesInv[5] * wXYZ.fZ;
+    XYZ.fZ = primariesInv[6] * wXYZ.fX + primariesInv[7] * wXYZ.fY + primariesInv[8] * wXYZ.fZ;
+    SkMatrix toXYZ;
+    toXYZ.setAll(XYZ.fX,   0.0f,   0.0f,
+                   0.0f, XYZ.fY,   0.0f,
+                   0.0f,   0.0f, XYZ.fZ);
+    toXYZ.postConcat(primaries);
+
+    // Now convert toXYZ matrix to toXYZD50.
+    SkVector3 wXYZD50 = SkVector3::Make(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.
+    SkMatrix mA, mAInv;
+    mA.setAll(+0.8951f, +0.2664f, -0.1614f,
+              -0.7502f, +1.7135f, +0.0367f,
+              +0.0389f, -0.0685f, +1.0296f);
+    mAInv.setAll(+0.9869929f, -0.1470543f, +0.1599627f,
+                 +0.4323053f, +0.5183603f, +0.0492912f,
+                 -0.0085287f, +0.0400428f, +0.9684867f);
+
+    SkVector3 srcCone;
+    srcCone.fX = mA[0] * wXYZ.fX + mA[1] * wXYZ.fY + mA[2] * wXYZ.fZ;
+    srcCone.fY = mA[3] * wXYZ.fX + mA[4] * wXYZ.fY + mA[5] * wXYZ.fZ;
+    srcCone.fZ = mA[6] * wXYZ.fX + mA[7] * wXYZ.fY + mA[8] * wXYZ.fZ;
+    SkVector3 dstCone;
+    dstCone.fX = mA[0] * wXYZD50.fX + mA[1] * wXYZD50.fY + mA[2] * wXYZD50.fZ;
+    dstCone.fY = mA[3] * wXYZD50.fX + mA[4] * wXYZD50.fY + mA[5] * wXYZD50.fZ;
+    dstCone.fZ = mA[6] * wXYZD50.fX + mA[7] * wXYZD50.fY + mA[8] * wXYZD50.fZ;
+
+    SkMatrix DXToD50;
+    DXToD50.setIdentity();
+    DXToD50[0] = dstCone.fX / srcCone.fX;
+    DXToD50[4] = dstCone.fY / srcCone.fY;
+    DXToD50[8] = dstCone.fZ / srcCone.fZ;
+    DXToD50.postConcat(mAInv);
+    DXToD50.preConcat(mA);
+
+    toXYZ.postConcat(DXToD50);
+    toXYZ_D50->set3x3(toXYZ[0], toXYZ[3], toXYZ[6],
+                      toXYZ[1], toXYZ[4], toXYZ[7],
+                      toXYZ[2], toXYZ[5], toXYZ[8]);
+    return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
 
 SkColorSpace_Base::SkColorSpace_Base(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50)
     : fGammaNamed(gammaNamed)
diff --git a/tests/ColorSpaceTest.cpp b/tests/ColorSpaceTest.cpp
index a6ed9e3..a4eab92 100644
--- a/tests/ColorSpaceTest.cpp
+++ b/tests/ColorSpaceTest.cpp
@@ -271,3 +271,23 @@
     REPORTER_ASSERT(r, !SkColorSpace::Equals(upperRight.get(), adobe.get()));
     REPORTER_ASSERT(r, !SkColorSpace::Equals(rgb1.get(), rgb2.get()));
 }
+
+DEF_TEST(ColorSpace_Primaries, r) {
+    // sRGB primaries
+    SkColorSpacePrimaries primaries;
+    primaries.fRX = 0.64f;
+    primaries.fRY = 0.33f;
+    primaries.fGX = 0.30f;
+    primaries.fGY = 0.60f;
+    primaries.fBX = 0.15f;
+    primaries.fBY = 0.06f;
+    primaries.fWX = 0.3127f;
+    primaries.fWY = 0.3290f;
+
+    SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor);
+    bool result = primaries.toXYZD50(&toXYZ);
+    REPORTER_ASSERT(r, result);
+
+    sk_sp<SkColorSpace> space = SkColorSpace::NewRGB(SkColorSpace::kSRGB_RenderTargetGamma, toXYZ);
+    REPORTER_ASSERT(r, SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named) == space);
+}