Sk4fLinearGradient fuzzer fixes

1) update in_range() to actually follow the documented rules regarding interval
   open/close endpoints

2) detect cases where the intervals provide negligible advance and fall back
   to using a color average

BUG=skia:5647
R=reed@google.com
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2472483002

Review-Url: https://codereview.chromium.org/2472483002
diff --git a/gm/gradients.cpp b/gm/gradients.cpp
index 45b6564..8ae1dd7 100644
--- a/gm/gradients.cpp
+++ b/gm/gradients.cpp
@@ -919,3 +919,20 @@
 DEF_SIMPLE_GM(gradient_many_stops_4f, canvas, 500, 500) {
     draw_many_stops(canvas, SkLinearGradient::kForce4fContext_PrivateFlag);
 }
+
+static void draw_subpixel_gradient(SkCanvas* canvas, uint32_t flags) {
+    const SkPoint pts[] = { {50, 50}, {50.1f, 50.1f}};
+    SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE };
+    SkPaint p;
+    p.setShader(SkGradientShader::MakeLinear(
+        pts, colors, nullptr, SK_ARRAY_COUNT(colors), SkShader::kRepeat_TileMode, flags, nullptr));
+    canvas->drawRect(SkRect::MakeXYWH(0, 0, 500, 500), p);
+}
+
+DEF_SIMPLE_GM(gradient_subpixel, canvas, 500, 500) {
+    draw_subpixel_gradient(canvas, 0);
+}
+
+DEF_SIMPLE_GM(gradient_subpixel_4f, canvas, 500, 500) {
+    draw_subpixel_gradient(canvas, SkLinearGradient::kForce4fContext_PrivateFlag);
+}
diff --git a/src/effects/gradients/Sk4fLinearGradient.cpp b/src/effects/gradients/Sk4fLinearGradient.cpp
index ebfd138..1100493 100644
--- a/src/effects/gradients/Sk4fLinearGradient.cpp
+++ b/src/effects/gradients/Sk4fLinearGradient.cpp
@@ -105,8 +105,8 @@
 bool in_range(SkScalar x, SkScalar k1, SkScalar k2) {
     SkASSERT(k1 != k2);
     return (k1 < k2)
-        ? (x >= k1 && x <  k2)
-        : (x >  k2 && x <= k1);
+        ? (x >= k1 && x < k2)
+        : (x >= k2 && x < k1);
 }
 
 } // anonymous namespace
@@ -294,12 +294,28 @@
         SkASSERT(fAdvX >= 0);
         SkASSERT(firstInterval <= lastInterval);
         SkASSERT(in_range(fx, i->fP0, i->fP1));
+
+        if (tileMode != kClamp_TileMode && !is_vertical) {
+            const auto spanX = (lastInterval->fP1 - firstInterval->fP0) / dx;
+            SkASSERT(spanX >= 0);
+
+            // If we're in a repeating tile mode and the whole gradient is compressed into a
+            // fraction of a pixel, we just use the average color in zero-ramp mode.
+            // This also avoids cases where we make no progress due to interval advances being
+            // close to zero.
+            static constexpr SkScalar kMinSpanX = .25f;
+            if (spanX < kMinSpanX) {
+                this->init_average_props();
+                return;
+            }
+        }
+
         this->compute_interval_props(fx - i->fP0);
     }
 
     SkScalar currentAdvance() const {
         SkASSERT(fAdvX >= 0);
-        SkASSERT(fAdvX <= (fInterval->fP1 - fInterval->fP0) / fDx);
+        SkASSERT(fAdvX <= (fInterval->fP1 - fInterval->fP0) / fDx || !isfinite(fAdvX));
         return fAdvX;
     }
 
@@ -334,6 +350,29 @@
         }
     }
 
+    void init_average_props() {
+        fAdvX     = SK_ScalarInfinity;
+        fZeroRamp = true;
+        fDcDx     = 0;
+        fCc       = Sk4f(0);
+
+        // TODO: precompute the average at interval setup time?
+        for (const auto* i = fFirstInterval; i <= fLastInterval; ++i) {
+            // Each interval contributes its average color to the total/weighted average:
+            //
+            //   C = (c0 + c1) / 2 = (c0 + c0 + dc * (p1 - p0)) / 2
+            //
+            //   Avg += C * (p1 - p0)
+            //
+            const auto dp = i->fP1 - i->fP0;
+            auto c = DstTraits<dstType, premul>::load(i->fC0);
+            if (!i->fZeroRamp) {
+                c = c + DstTraits<dstType, premul>::load(i->fDc) * dp * 0.5f;
+            }
+            fCc = fCc + c * dp;
+        }
+    }
+
     const Interval* next_interval(const Interval* i) const {
         SkASSERT(i >= fFirstInterval);
         SkASSERT(i <= fLastInterval);