Add color space support to 4f gradients

Similar to the raster pipeline stage, transform the stops into the dest
color space before interpolation.

Change-Id: I626b6ef18606fd2308d7da166ce70d05f3951e21
Reviewed-on: https://skia-review.googlesource.com/18767
Reviewed-by: Florin Malita <fmalita@chromium.org>
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Florin Malita <fmalita@chromium.org>
diff --git a/src/shaders/gradients/Sk4fGradientBase.cpp b/src/shaders/gradients/Sk4fGradientBase.cpp
index e20f5f4..bf884ac 100644
--- a/src/shaders/gradients/Sk4fGradientBase.cpp
+++ b/src/shaders/gradients/Sk4fGradientBase.cpp
@@ -11,8 +11,7 @@
 
 namespace {
 
-Sk4f pack_color(SkColor c, bool premul, const Sk4f& component_scale) {
-    const SkColor4f c4f = SkColor4f::FromColor(c);
+Sk4f pack_color(const SkColor4f& c4f, bool premul, const Sk4f& component_scale) {
     const Sk4f pm4f = premul
         ? c4f.premul().to4f()
         : Sk4f{c4f.fR, c4f.fG, c4f.fB, c4f.fA};
@@ -22,40 +21,40 @@
 
 class IntervalIterator {
 public:
-    IntervalIterator(const SkColor* colors, const SkScalar* pos, int count, bool reverse)
-        : fColors(colors)
-        , fPos(pos)
-        , fCount(count)
+    IntervalIterator(const SkGradientShaderBase& shader, SkColorSpace* dstCS, bool reverse)
+        : fShader(shader)
+        , fDstCS(dstCS)
         , fFirstPos(reverse ? SK_Scalar1 : 0)
-        , fBegin(reverse ? count - 1 : 0)
+        , fBegin(reverse ? shader.fColorCount - 1 : 0)
         , fAdvance(reverse ? -1 : 1) {
-        SkASSERT(colors);
-        SkASSERT(count > 0);
+        SkASSERT(shader.fColorCount > 0);
     }
 
-    void iterate(std::function<void(SkColor, SkColor, SkScalar, SkScalar)> func) const {
-        if (!fPos) {
+    void iterate(std::function<void(const SkColor4f&, const SkColor4f&,
+                                    SkScalar, SkScalar)> func) const {
+        if (!fShader.fOrigPos) {
             this->iterateImplicitPos(func);
             return;
         }
 
-        const int end = fBegin + fAdvance * (fCount - 1);
+        const int end = fBegin + fAdvance * (fShader.fColorCount - 1);
         const SkScalar lastPos = 1 - fFirstPos;
         int prev = fBegin;
         SkScalar prevPos = fFirstPos;
 
         do {
             const int curr = prev + fAdvance;
-            SkASSERT(curr >= 0 && curr < fCount);
+            SkASSERT(curr >= 0 && curr < fShader.fColorCount);
 
             // TODO: this sanitization should be done in SkGradientShaderBase
             const SkScalar currPos = (fAdvance > 0)
-                ? SkTPin(fPos[curr], prevPos, lastPos)
-                : SkTPin(fPos[curr], lastPos, prevPos);
+                ? SkTPin(fShader.fOrigPos[curr], prevPos, lastPos)
+                : SkTPin(fShader.fOrigPos[curr], lastPos, prevPos);
 
             if (currPos != prevPos) {
                 SkASSERT((currPos - prevPos > 0) == (fAdvance > 0));
-                func(fColors[prev], fColors[curr], prevPos, currPos);
+                func(fShader.getXformedColor(prev, fDstCS), fShader.getXformedColor(curr, fDstCS),
+                     prevPos, currPos);
             }
 
             prev = curr;
@@ -64,44 +63,48 @@
     }
 
 private:
-    void iterateImplicitPos(std::function<void(SkColor, SkColor, SkScalar, SkScalar)> func) const {
+    void iterateImplicitPos(std::function<void(const SkColor4f&, const SkColor4f&,
+                                               SkScalar, SkScalar)> func) const {
         // When clients don't provide explicit color stop positions (fPos == nullptr),
         // the color stops are distributed evenly across the unit interval
         // (implicit positioning).
-        const SkScalar dt = fAdvance * SK_Scalar1 / (fCount - 1);
-        const int end = fBegin + fAdvance * (fCount - 2);
+        const SkScalar dt = fAdvance * SK_Scalar1 / (fShader.fColorCount - 1);
+        const int end = fBegin + fAdvance * (fShader.fColorCount - 2);
         int prev = fBegin;
         SkScalar prevPos = fFirstPos;
 
         while (prev != end) {
             const int curr = prev + fAdvance;
-            SkASSERT(curr >= 0 && curr < fCount);
+            SkASSERT(curr >= 0 && curr < fShader.fColorCount);
 
             const SkScalar currPos = prevPos + dt;
-            func(fColors[prev], fColors[curr], prevPos, currPos);
+            func(fShader.getXformedColor(prev, fDstCS),
+                 fShader.getXformedColor(curr, fDstCS),
+                 prevPos, currPos);
             prev = curr;
             prevPos = currPos;
         }
 
         // emit the last interval with a pinned end position, to avoid precision issues
-        func(fColors[prev], fColors[prev + fAdvance], prevPos, 1 - fFirstPos);
+        func(fShader.getXformedColor(prev, fDstCS),
+             fShader.getXformedColor(prev + fAdvance, fDstCS),
+             prevPos, 1 - fFirstPos);
     }
 
-    const SkColor*  fColors;
-    const SkScalar* fPos;
-    const int       fCount;
-    const SkScalar  fFirstPos;
-    const int       fBegin;
-    const int       fAdvance;
+    const SkGradientShaderBase& fShader;
+    SkColorSpace*               fDstCS;
+    const SkScalar              fFirstPos;
+    const int                   fBegin;
+    const int                   fAdvance;
 };
 
-void addMirrorIntervals(const SkColor colors[],
-                        const SkScalar pos[], int count,
+void addMirrorIntervals(const SkGradientShaderBase& shader,
+                        SkColorSpace* dstCS,
                         const Sk4f& componentScale,
                         bool premulColors, bool reverse,
                         Sk4fGradientIntervalBuffer::BufferType* buffer) {
-    const IntervalIterator iter(colors, pos, count, reverse);
-    iter.iterate([&] (SkColor c0, SkColor c1, SkScalar t0, SkScalar t1) {
+    const IntervalIterator iter(shader, dstCS, reverse);
+    iter.iterate([&] (const SkColor4f& c0, const SkColor4f& c1, SkScalar t0, SkScalar t1) {
         SkASSERT(buffer->empty() || buffer->back().fT1 == 2 - t0);
 
         const auto mirror_t0 = 2 - t0;
@@ -137,7 +140,7 @@
     dc.store(&fCg.fVec);
 }
 
-void Sk4fGradientIntervalBuffer::init(const SkColor colors[], const SkScalar pos[], int count,
+void Sk4fGradientIntervalBuffer::init(const SkGradientShaderBase& shader, SkColorSpace* dstCS,
                                       SkShader::TileMode tileMode, bool premulColors,
                                       SkScalar alpha, bool reverse) {
     // The main job here is to build a specialized interval list: a different
@@ -181,8 +184,9 @@
     //
     // TODO: investigate collapsing intervals << 1px.
 
+    const auto count = shader.fColorCount;
+
     SkASSERT(count > 0);
-    SkASSERT(colors);
 
     fIntervals.reset();
 
@@ -196,18 +200,18 @@
 
     if (tileMode == SkShader::kClamp_TileMode) {
         // synthetic edge interval: -/+inf .. P0
-        const Sk4f clamp_color = pack_color(colors[first_index],
+        const Sk4f clamp_color = pack_color(shader.getXformedColor(first_index, dstCS),
                                             premulColors, componentScale);
         const SkScalar clamp_pos = reverse ? SK_ScalarInfinity : SK_ScalarNegativeInfinity;
         fIntervals.emplace_back(clamp_color, clamp_pos,
                                 clamp_color, first_pos);
     } else if (tileMode == SkShader::kMirror_TileMode && reverse) {
         // synthetic mirror intervals injected before main intervals: (2 .. 1]
-        addMirrorIntervals(colors, pos, count, componentScale, premulColors, false, &fIntervals);
+        addMirrorIntervals(shader, dstCS, componentScale, premulColors, false, &fIntervals);
     }
 
-    const IntervalIterator iter(colors, pos, count, reverse);
-    iter.iterate([&] (SkColor c0, SkColor c1, SkScalar t0, SkScalar t1) {
+    const IntervalIterator iter(shader, dstCS, reverse);
+    iter.iterate([&] (const SkColor4f& c0, const SkColor4f& c1, SkScalar t0, SkScalar t1) {
         SkASSERT(fIntervals.empty() || fIntervals.back().fT1 == t0);
 
         fIntervals.emplace_back(pack_color(c0, premulColors, componentScale), t0,
@@ -216,13 +220,14 @@
 
     if (tileMode == SkShader::kClamp_TileMode) {
         // synthetic edge interval: Pn .. +/-inf
-        const Sk4f clamp_color = pack_color(colors[last_index], premulColors, componentScale);
+        const Sk4f clamp_color = pack_color(shader.getXformedColor(last_index, dstCS),
+                                            premulColors, componentScale);
         const SkScalar clamp_pos = reverse ? SK_ScalarNegativeInfinity : SK_ScalarInfinity;
         fIntervals.emplace_back(clamp_color, last_pos,
                                 clamp_color, clamp_pos);
     } else if (tileMode == SkShader::kMirror_TileMode && !reverse) {
         // synthetic mirror intervals injected after main intervals: [1 .. 2)
-        addMirrorIntervals(colors, pos, count, componentScale, premulColors, true, &fIntervals);
+        addMirrorIntervals(shader, dstCS, componentScale, premulColors, true, &fIntervals);
     }
 }
 
diff --git a/src/shaders/gradients/Sk4fGradientBase.h b/src/shaders/gradients/Sk4fGradientBase.h
index a660d6b..bd0aab4 100644
--- a/src/shaders/gradients/Sk4fGradientBase.h
+++ b/src/shaders/gradients/Sk4fGradientBase.h
@@ -37,8 +37,8 @@
 
 class Sk4fGradientIntervalBuffer {
 public:
-    void init(const SkColor colors[], const SkScalar pos[], int count,
-              SkShader::TileMode tileMode, bool premulColors, SkScalar alpha, bool reverse);
+    void init(const SkGradientShaderBase&, SkColorSpace* dstCS, SkShader::TileMode tileMode,
+              bool premulColors, SkScalar alpha, bool reverse);
 
     const Sk4fGradientInterval* find(SkScalar t) const;
     const Sk4fGradientInterval* findNext(SkScalar t, const Sk4fGradientInterval* prev,
diff --git a/src/shaders/gradients/Sk4fLinearGradient.cpp b/src/shaders/gradients/Sk4fLinearGradient.cpp
index 7b7498e..202461c 100644
--- a/src/shaders/gradients/Sk4fLinearGradient.cpp
+++ b/src/shaders/gradients/Sk4fLinearGradient.cpp
@@ -92,7 +92,7 @@
 
     // Our fast path expects interval points to be monotonically increasing in x.
     const bool reverseIntervals = this->isFast() && std::signbit(fDstToPos.getScaleX());
-    fIntervals.init(shader.fOrigColors, shader.fOrigPos, shader.fColorCount, shader.fTileMode,
+    fIntervals.init(shader, rec.fDstColorSpace, shader.fTileMode,
                     fColorsArePremul, rec.fPaint->getAlpha() * (1.0f / 255), reverseIntervals);
 
     SkASSERT(fIntervals->count() > 0);
diff --git a/src/shaders/gradients/SkGradientShader.cpp b/src/shaders/gradients/SkGradientShader.cpp
index a34a35a..ab774f6 100644
--- a/src/shaders/gradients/SkGradientShader.cpp
+++ b/src/shaders/gradients/SkGradientShader.cpp
@@ -449,8 +449,7 @@
 
     const bool premulGrad = fGradFlags & SkGradientShader::kInterpolateColorsInPremul_Flag;
     auto prepareColor = [premulGrad, dstCS, this](int i) {
-        SkColor4f c = dstCS ? to_colorspace(fOrigColors4f[i], fColorSpace.get(), dstCS)
-                            : SkColor4f_from_SkColor(fOrigColors[i], nullptr);
+        SkColor4f c = this->getXformedColor(i, dstCS);
         return premulGrad ? c.premul()
                           : SkPM4f::From4f(Sk4f::Load(&c));
     };
@@ -870,6 +869,11 @@
     return fCache;
 }
 
+SkColor4f SkGradientShaderBase::getXformedColor(size_t i, SkColorSpace* dstCS) const {
+    return dstCS ? to_colorspace(fOrigColors4f[i], fColorSpace.get(), dstCS)
+                 : SkColor4f_from_SkColor(fOrigColors[i], nullptr);
+}
+
 SK_DECLARE_STATIC_MUTEX(gGradientCacheMutex);
 /*
  *  Because our caller might rebuild the same (logically the same) gradient
diff --git a/src/shaders/gradients/SkGradientShaderPriv.h b/src/shaders/gradients/SkGradientShaderPriv.h
index 7a66eda..c7c7761 100644
--- a/src/shaders/gradients/SkGradientShaderPriv.h
+++ b/src/shaders/gradients/SkGradientShaderPriv.h
@@ -202,6 +202,8 @@
 
     uint32_t getGradFlags() const { return fGradFlags; }
 
+    SkColor4f getXformedColor(size_t index, SkColorSpace*) const;
+
 protected:
     struct Rec {
         SkFixed     fPos;   // 0...1