Add SkColorSpaceXform_skcms

Currently only enabled in Skia dev builds. Has some diffs
in GMs and images, but (hopefully) nothing major.

Bug: skia:
Change-Id: Ifdf5d2804e59f555a3dc84f657e438dd589a2751
Reviewed-on: https://skia-review.googlesource.com/116520
Commit-Queue: Mike Klein <mtklein@google.com>
Reviewed-by: Mike Klein <mtklein@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index ff99776..7786024 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -722,11 +722,12 @@
 optional("skcms") {
   enabled = skia_use_skcms
 
+  public_defines = [ "SK_USE_SKCMS" ]
   deps = [
     "//third_party/skcms",
   ]
   sources = [
-    # TODO
+    "src/core/SkColorSpaceXform_skcms.cpp",
   ]
 }
 
diff --git a/include/core/SkColorSpace.h b/include/core/SkColorSpace.h
index 2908268..35466fd 100644
--- a/include/core/SkColorSpace.h
+++ b/include/core/SkColorSpace.h
@@ -241,6 +241,12 @@
      */
     static bool Equals(const SkColorSpace* src, const SkColorSpace* dst);
 
+    /**
+     *  If this color space was constructed from an ICC profile, return that profile data.
+     *  Otherise, return nullptr.
+     */
+    const SkData* profileData() const { return this->onProfileData(); }
+
 private:
     virtual const SkMatrix44* onToXYZD50() const = 0;
     virtual uint32_t onToXYZD50Hash() const = 0;
diff --git a/src/core/SkColorSpacePriv.h b/src/core/SkColorSpacePriv.h
index 80046bb..7b1b97d 100644
--- a/src/core/SkColorSpacePriv.h
+++ b/src/core/SkColorSpacePriv.h
@@ -48,10 +48,8 @@
 static constexpr SkColorSpaceTransferFn g2Dot2_TransferFn =
         { 2.2f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
 
-// gLinear_TransferFn.fD > 1.0f: Make sure that we use the linear segment of
-// the transfer function even when the x-value is 1.0f.
 static constexpr SkColorSpaceTransferFn gLinear_TransferFn =
-        { 0.0f, 0.0f, 0.0f, 1.0f, 1.0000001f, 0.0f, 0.0f };
+        { 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
 
 static constexpr SkColorSpaceTransferFn gDCIP3_TransferFn =
     { 2.399994f, 0.947998047f, 0.0520019531f, 0.0769958496f, 0.0390014648f, 0.0f, 0.0f };
diff --git a/src/core/SkColorSpaceXform.cpp b/src/core/SkColorSpaceXform.cpp
index 7c53ed5..70a5beb 100644
--- a/src/core/SkColorSpaceXform.cpp
+++ b/src/core/SkColorSpaceXform.cpp
@@ -316,6 +316,9 @@
         return nullptr;
     }
 
+#if defined(SK_USE_SKCMS)
+    return MakeSkcmsXform(src, dst, premulBehavior);
+#else
     if (src->toXYZD50()) {
         return skstd::make_unique<SkColorSpaceXform_XYZ>(static_cast<SkColorSpace_XYZ*>(src),
                                                          static_cast<SkColorSpace_XYZ*>(dst),
@@ -323,6 +326,7 @@
     }
     return skstd::make_unique<SkColorSpaceXform_A2B>(static_cast<SkColorSpace_A2B*>(src),
                                                      static_cast<SkColorSpace_XYZ*>(dst));
+#endif
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/SkColorSpaceXform_Base.h b/src/core/SkColorSpaceXform_Base.h
index f931cde..2e02023 100644
--- a/src/core/SkColorSpaceXform_Base.h
+++ b/src/core/SkColorSpaceXform_Base.h
@@ -71,4 +71,9 @@
 // For testing.  Bypasses opts for when src and dst color spaces are equal.
 std::unique_ptr<SkColorSpaceXform> SlowIdentityXform(SkColorSpace_XYZ* space);
 
+#if defined(SK_USE_SKCMS)
+std::unique_ptr<SkColorSpaceXform> MakeSkcmsXform(SkColorSpace* src, SkColorSpace* dst,
+                                                  SkTransferFunctionBehavior premulBehavior);
+#endif
+
 #endif
diff --git a/src/core/SkColorSpaceXform_skcms.cpp b/src/core/SkColorSpaceXform_skcms.cpp
new file mode 100644
index 0000000..34afaaa
--- /dev/null
+++ b/src/core/SkColorSpaceXform_skcms.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkColorSpaceXform.h"
+#include "SkData.h"
+#include "SkMakeUnique.h"
+#include "skcms.h"
+
+class SkColorSpaceXform_skcms : public SkColorSpaceXform {
+public:
+    SkColorSpaceXform_skcms(const skcms_ICCProfile& srcProfile,
+                            const skcms_ICCProfile& dstProfile,
+                            skcms_AlphaFormat premulFormat)
+        : fSrcProfile(srcProfile)
+        , fDstProfile(dstProfile)
+        , fPremulFormat(premulFormat)
+    {}
+
+    bool apply(ColorFormat, void*, ColorFormat, const void*, int, SkAlphaType) const override;
+
+private:
+    skcms_ICCProfile  fSrcProfile;
+    skcms_ICCProfile  fDstProfile;
+    skcms_AlphaFormat fPremulFormat;
+};
+
+static skcms_PixelFormat get_skcms_format(SkColorSpaceXform::ColorFormat fmt) {
+    switch (fmt) {
+        case SkColorSpaceXform::kRGBA_8888_ColorFormat:
+            return skcms_PixelFormat_RGBA_8888;
+        case SkColorSpaceXform::kBGRA_8888_ColorFormat:
+            return skcms_PixelFormat_BGRA_8888;
+        case SkColorSpaceXform::kRGB_U16_BE_ColorFormat:
+            return skcms_PixelFormat_RGB_161616;
+        case SkColorSpaceXform::kRGBA_U16_BE_ColorFormat:
+            return skcms_PixelFormat_RGBA_16161616;
+        case SkColorSpaceXform::kRGBA_F16_ColorFormat:
+            return skcms_PixelFormat_RGBA_hhhh;
+        case SkColorSpaceXform::kRGBA_F32_ColorFormat:
+            return skcms_PixelFormat_RGBA_ffff;
+        case SkColorSpaceXform::kBGR_565_ColorFormat:
+            return skcms_PixelFormat_BGR_565;
+        default:
+            SkDEBUGFAIL("Invalid ColorFormat");
+            return skcms_PixelFormat_RGBA_8888;
+    }
+}
+
+bool SkColorSpaceXform_skcms::apply(ColorFormat dstFormat, void* dst,
+                                    ColorFormat srcFormat, const void* src,
+                                    int count, SkAlphaType alphaType) const {
+    skcms_AlphaFormat srcAlpha = skcms_AlphaFormat_Unpremul;
+    skcms_AlphaFormat dstAlpha = kPremul_SkAlphaType == alphaType ? fPremulFormat
+                                                                  : skcms_AlphaFormat_Unpremul;
+
+    return skcms_Transform(src, get_skcms_format(srcFormat), srcAlpha, &fSrcProfile,
+                           dst, get_skcms_format(dstFormat), dstAlpha, &fDstProfile, count);
+}
+
+static bool cs_to_profile(const SkColorSpace* cs, skcms_ICCProfile* profile) {
+    if (cs->profileData()) {
+        bool result = skcms_Parse(cs->profileData()->data(), cs->profileData()->size(), profile);
+        // We shouldn't encounter color spaces that were constructed from invalid profiles!
+        SkASSERT(result);
+        return result;
+    }
+
+    SkMatrix44 toXYZ;
+    SkColorSpaceTransferFn tf;
+    if (cs->toXYZD50(&toXYZ) && cs->isNumericalTransferFn(&tf)) {
+        memset(profile, 0, sizeof(*profile));
+
+        profile->has_trc = true;
+        profile->trc[0].parametric.g = tf.fG;
+        profile->trc[0].parametric.a = tf.fA;
+        profile->trc[0].parametric.b = tf.fB;
+        profile->trc[0].parametric.c = tf.fC;
+        profile->trc[0].parametric.d = tf.fD;
+        profile->trc[0].parametric.e = tf.fE;
+        profile->trc[0].parametric.f = tf.fF;
+        profile->trc[1].parametric = profile->trc[0].parametric;
+        profile->trc[2].parametric = profile->trc[0].parametric;
+
+        profile->has_toXYZD50 = true;
+        for (int r = 0; r < 3; ++r) {
+            for (int c = 0; c < 3; ++c) {
+                profile->toXYZD50.vals[r][c] = toXYZ.get(r, c);
+            }
+        }
+
+        return true;
+    }
+
+    // It should be impossible to make a color space that gets here with our available factories.
+    // All ICC-based profiles have profileData. All remaining factories produce XYZ spaces with
+    // a single (numerical) transfer function.
+    SkDEBUGFAIL("How did we get here?");
+    return false;
+}
+
+std::unique_ptr<SkColorSpaceXform> MakeSkcmsXform(SkColorSpace* src, SkColorSpace* dst,
+                                                  SkTransferFunctionBehavior premulBehavior) {
+    // Construct skcms_ICCProfiles from each color space. For now, support A2B and XYZ.
+    // Eventually, only need to support XYZ. Map premulBehavior to one of the two premul formats
+    // in skcms.
+    skcms_ICCProfile srcProfile, dstProfile;
+
+    if (!cs_to_profile(src, &srcProfile) || !cs_to_profile(dst, &dstProfile)) {
+        return nullptr;
+    }
+
+    skcms_AlphaFormat premulFormat = SkTransferFunctionBehavior::kRespect == premulBehavior
+            ? skcms_AlphaFormat_PremulLinear : skcms_AlphaFormat_PremulAsEncoded;
+    return skstd::make_unique<SkColorSpaceXform_skcms>(srcProfile, dstProfile, premulFormat);
+}
diff --git a/tests/ColorSpaceXformTest.cpp b/tests/ColorSpaceXformTest.cpp
index 0efd993..bd24f24 100644
--- a/tests/ColorSpaceXformTest.cpp
+++ b/tests/ColorSpaceXformTest.cpp
@@ -107,6 +107,12 @@
 
 static void test_identity_xform_A2B(skiatest::Reporter* r, SkGammaNamed gammaNamed,
                                     const sk_sp<SkGammas>& gammas, int tol=1) {
+#if defined(SK_USE_SKCMS)
+    (void)r;
+    (void)gammaNamed;
+    (void)gammas;
+    (void)tol;
+#else
     // Arbitrary set of 10 pixels
     constexpr int width = 10;
     constexpr uint32_t srcPixels[width] = {
@@ -133,6 +139,7 @@
         REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 24) & 0xFF),
                                         SkGetPackedA32(dstPixels[i]), tol));
     }
+#endif
 }
 
 DEF_TEST(ColorSpaceXform_TableGamma, r) {
@@ -264,6 +271,7 @@
     test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, tolerance);
 }
 
+#if !defined(SK_USE_SKCMS)
 DEF_TEST(ColorSpaceXform_A2BCLUT, r) {
     constexpr int inputChannels = 3;
     constexpr int gp            = 4; // # grid points
@@ -325,6 +333,7 @@
                                         SkColorGetR(dstPixels[i])));
     }
 }
+#endif
 
 DEF_TEST(SkColorSpaceXform_LoadTail, r) {
     std::unique_ptr<uint64_t[]> srcPixel(new uint64_t[1]);