Rework files and add test cases.

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1757193002

Review URL: https://codereview.chromium.org/1757193002
diff --git a/src/core/SkLinearBitmapPipeline.cpp b/src/core/SkLinearBitmapPipeline.cpp
index 91e54fb..4c21180 100644
--- a/src/core/SkLinearBitmapPipeline.cpp
+++ b/src/core/SkLinearBitmapPipeline.cpp
@@ -54,7 +54,6 @@
 };
 
 namespace  {
-
 // PointProcessor uses a strategy to help complete the work of the different stages. The strategy
 // must implement the following methods:
 // * processPoints(xs, ys) - must mutate the xs and ys for the stage.
diff --git a/src/core/SkLinearBitmapPipeline_core.h b/src/core/SkLinearBitmapPipeline_core.h
index 9a8ec0e..0541f3c 100644
--- a/src/core/SkLinearBitmapPipeline_core.h
+++ b/src/core/SkLinearBitmapPipeline_core.h
@@ -8,6 +8,8 @@
 #ifndef SkLinearBitmapPipeline_core_DEFINED
 #define SkLinearBitmapPipeline_core_DEFINED
 
+#include <cmath>
+
 // Tweak ABI of functions that pass Sk4f by value to pass them via registers.
 #if defined(_MSC_VER) && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2
     #define VECTORCALL __vectorcall
@@ -22,7 +24,7 @@
     explicit X(SkScalar val) : fVal{val} { }
     explicit X(SkPoint pt)   : fVal{pt.fX} { }
     explicit X(SkSize s)     : fVal{s.fWidth} { }
-    explicit X(SkISize s)    : fVal(s.fWidth) { }
+    explicit X(SkISize s)    : fVal((SkScalar)s.fWidth) { }
     operator SkScalar () const {return fVal;}
 private:
     SkScalar fVal;
@@ -32,7 +34,7 @@
     explicit Y(SkScalar val) : fVal{val} { }
     explicit Y(SkPoint pt)   : fVal{pt.fY} { }
     explicit Y(SkSize s)     : fVal{s.fHeight} { }
-    explicit Y(SkISize s)    : fVal(s.fHeight) { }
+    explicit Y(SkISize s)    : fVal((SkScalar)s.fHeight) { }
     operator SkScalar () const {return fVal;}
 private:
     SkScalar fVal;
@@ -73,7 +75,7 @@
     bool completelyWithin(SkScalar xMin, SkScalar xMax) const {
         SkScalar sMin, sMax;
         std::tie(sMin, sMax) = std::minmax(startX(), endX());
-        return xMin <= sMin && sMax <= xMax;
+        return xMin <= sMin && sMax < xMax;
     }
 
     void offset(SkScalar offsetX) {
@@ -90,19 +92,37 @@
         }
 
         int dxSteps = SkScalarFloorToInt((breakX - this->startX()) / dx);
+
+        // Calculate the values for the span to cleave off.
+        SkScalar newLength = dxSteps * dx;
+
         if (dxSteps < 0) {
             // The span is wholly after breakX.
             return Span{{0.0, 0.0}, 0.0f, 0};
-        } else if (dxSteps > fCount) {
+        } else if (dxSteps >= fCount) {
             // The span is wholly before breakX.
             Span answer = *this;
             this->clear();
             return answer;
         }
 
-        // Calculate the values for the span to cleave off.
+        // If the last (or first if count = 1) sample lands directly on the boundary. Include it
+        // when dx < 0 and exclude it when dx > 0.
+        // Reasoning:
+        //  dx > 0: The sample point on the boundary is part of the next span because the entire
+        // pixel is after the boundary.
+        //  dx < 0: The sample point on the boundary is part of the current span because the
+        // entire pixel is before the boundary.
+        if (startX() + newLength == breakX && dx > 0) {
+            if (dxSteps != 0) {
+                dxSteps -= 1;
+                newLength -= dx;
+            } else {
+                return Span{{0.0, 0.0}, 0.0f, 0};
+            }
+        }
+
         SkPoint newStart = fStart;
-        SkScalar newLength = dxSteps * dx;
         int newCount = dxSteps + 1;
         SkASSERT(newCount > 0);
 
diff --git a/src/core/SkLinearBitmapPipeline_tile.h b/src/core/SkLinearBitmapPipeline_tile.h
index 2f855a3..761e3c5 100644
--- a/src/core/SkLinearBitmapPipeline_tile.h
+++ b/src/core/SkLinearBitmapPipeline_tile.h
@@ -100,6 +100,7 @@
             }
         } else {
             Span rightClamped = span.breakAt(xMax, dx);
+
             if (!rightClamped.isEmpty()) {
                 rightClamped.clampToSinglePixel({xMax - 1, y});
                 next->pointSpan(rightClamped);
@@ -198,7 +199,7 @@
 
         Span span({x, y}, length, count);
         if (dx > 0) {
-            while (!span.isEmpty() && span.endX() > xMax) {
+            while (!span.isEmpty() && span.endX() >= xMax) {
                 Span toDraw = span.breakAt(xMax, dx);
                 next->pointSpan(toDraw);
                 span.offset(-xMax);
diff --git a/tests/SkLinearBitmapPipelineTest.cpp b/tests/SkLinearBitmapPipelineTest.cpp
index 04f2e27..e715b62 100644
--- a/tests/SkLinearBitmapPipelineTest.cpp
+++ b/tests/SkLinearBitmapPipelineTest.cpp
@@ -5,54 +5,237 @@
  * found in the LICENSE file.
  */
 
+#include <algorithm>
+#include <array>
+#include <tuple>
+#include <vector>
 #include "SkLinearBitmapPipeline.h"
 #include "SkColor.h"
+#include "SkNx.h"
+#include "SkPoint.h"
 #include "SkPM4f.h"
 #include "Test.h"
+#include "SkLinearBitmapPipeline_tile.h"
 
-using Pixel = float[4];
-DEF_TEST(SkBitmapFP, reporter) {
 
-    int width = 10;
-    int height = 10;
-    uint32_t* bitmap = new uint32_t[width * height];
-    for (int y = 0; y < height; y++) {
-        for (int x = 0; x < width; x++) {
-            bitmap[y * width + x] = (y << 8) + x + (128<<24);
+static SkString dump(SkScalar cut, Span prefix, Span remainder) {
+    SkPoint prefixStart; SkScalar prefixLen; int prefixCount;
+    std::tie(prefixStart, prefixLen, prefixCount) = prefix;
+    SkPoint remainderStart; SkScalar remainderLen; int remainderCount;
+    std::tie(remainderStart, remainderLen, remainderCount) = remainder;
+    return SkStringPrintf("cut: %f prefix: (%f, %f), %f, %d - remainder: (%f, %f), %f, %d",
+                          cut,
+                          prefixStart.fX, prefixStart.fY, prefixLen, prefixCount,
+                          remainderStart.fX, remainderStart.fY, remainderLen, remainderCount);
+}
+
+static void check_span_result(
+    skiatest::Reporter* reporter,
+    Span span, SkScalar dx, SkScalar cut, SkPoint start, SkScalar len, int count) {
+    SkPoint originalStart; SkScalar originalLen; int originalCount;
+    std::tie(originalStart, originalLen, originalCount) = span;
+
+    Span prefix = span.breakAt(cut, dx);
+
+    SkPoint prefixStart; SkScalar prefixLen; int prefixCount;
+    std::tie(prefixStart, prefixLen, prefixCount) = prefix;
+
+    REPORTER_ASSERT_MESSAGE(reporter, prefixStart == start, dump(cut, prefix, span));
+    REPORTER_ASSERT_MESSAGE(reporter, prefixLen == len, dump(cut, prefix, span));
+    REPORTER_ASSERT_MESSAGE(reporter, prefixCount == count, dump(cut, prefix, span));
+    SkPoint expectedRemainderStart;
+    SkScalar expectedRemainderLen;
+    int expectedRemainderCount;
+    if (prefix.isEmpty()) {
+        expectedRemainderStart = originalStart;
+        expectedRemainderLen = originalLen;
+        expectedRemainderCount = originalCount;
+    } else {
+        expectedRemainderStart = SkPoint::Make(originalStart.fX + prefixLen + dx, originalStart.fY);
+        expectedRemainderLen = originalLen - prefixLen - dx;
+        expectedRemainderCount = originalCount - prefixCount;
+    }
+
+    if (!span.isEmpty()) {
+        SkPoint remainderStart;
+        SkScalar remainderLen;
+        int remainderCount;
+        std::tie(remainderStart, remainderLen, remainderCount) = span;
+        // Remainder span
+        REPORTER_ASSERT_MESSAGE(reporter, expectedRemainderStart == remainderStart,
+                                dump(cut, prefix, span));
+        REPORTER_ASSERT_MESSAGE(reporter,
+                                expectedRemainderLen == remainderLen,
+                                dump(cut, prefix, span));
+        REPORTER_ASSERT_MESSAGE(reporter,
+                                expectedRemainderCount == remainderCount,
+                                dump(cut, prefix, span));
+    }
+}
+
+DEF_TEST(LBPSpanOps, reporter) {
+    {
+        SkScalar dx = 1.0f;
+        SkPoint start = SkPoint::Make(-5, -5);
+        Span span{start, 9.0f, 10};
+        check_span_result(reporter, span, dx,  0.0f, start, 4.0f, 5);
+        check_span_result(reporter, span, dx, -6.0f, SkPoint::Make(0, 0), 0.0f, 0);
+        check_span_result(reporter, span, dx, -5.0f, SkPoint::Make(0, 0), 0.0f, 0);
+        check_span_result(reporter, span, dx, -4.0f, SkPoint::Make(-5, -5), 0.0f, 1);
+        check_span_result(reporter, span, dx,  4.0f, SkPoint::Make(-5, -5), 8.0f, 9);
+        check_span_result(reporter, span, dx,  5.0f, SkPoint::Make(-5, -5), 9.0f, 10);
+        check_span_result(reporter, span, dx,  6.0f, SkPoint::Make(-5, -5), 9.0f, 10);
+    }
+    {
+        SkScalar dx = -1.0f;
+        SkPoint start = SkPoint::Make(5, 5);
+        Span span{start, -9.0f, 10};
+        check_span_result(reporter, span, dx,  0.0f, start, -5.0f, 6);
+        check_span_result(reporter, span, dx, -6.0f, SkPoint::Make(5, 5), -9.0f, 10);
+        check_span_result(reporter, span, dx, -5.0f, SkPoint::Make(5, 5), -9.0f, 10);
+        check_span_result(reporter, span, dx, -4.0f, SkPoint::Make(5, 5), -9.0f, 10);
+        check_span_result(reporter, span, dx,  4.0f, SkPoint::Make(5, 5), -1.0f, 2);
+        check_span_result(reporter, span, dx,  5.0f, SkPoint::Make(5, 5), 0.0f, 1);
+        check_span_result(reporter, span, dx,  6.0f, SkPoint::Make(0, 0), 0.0f, 0);
+    }
+}
+
+template <typename Tiler>
+static bool compare_tiler_case(Tiler& tiler, Span span, skiatest::Reporter* reporter) {
+    Span originalSpan = span;
+    std::vector<SkPoint> listPoints;
+    std::vector<SkPoint> spanPoints;
+    struct Sink {
+        void VECTORCALL pointListFew(int n, Sk4s xs, Sk4s ys) {
+            SkASSERT(0 < n && n < 4);
+            if (n >= 1) storePoint({xs[0], ys[0]});
+            if (n >= 2) storePoint({xs[1], ys[1]});
+            if (n >= 3) storePoint({xs[2], ys[2]});
+        }
+
+        void VECTORCALL pointList4(Sk4s xs, Sk4s ys) {
+            storePoint({xs[0], ys[0]});
+            storePoint({xs[1], ys[1]});
+            storePoint({xs[2], ys[2]});
+            storePoint({xs[3], ys[3]});
+        }
+
+        void pointSpan(Span span) {
+            span_fallback(span, this);
+        }
+
+        void storePoint(SkPoint pt) {
+            fPoints->push_back({SkScalarFloorToScalar(X(pt)), SkScalarFloorToScalar(Y(pt))});
+        }
+
+        std::vector<SkPoint>* fPoints;
+    };
+
+    Sink listSink = {&listPoints};
+    Sink spanSink = {&spanPoints};
+
+    SkPoint start; SkScalar length; int count;
+    std::tie(start, length, count) = span;
+
+    SkScalar dx = length / (count - 1);
+    Sk4f xs = Sk4f{X(start)} + Sk4f{0.0f, dx, 2 * dx, 3 * dx};
+    Sk4f ys = Sk4f{Y(start)};
+    while (count >= 4) {
+        Sk4f txs = xs;
+        Sk4f tys = ys;
+        tiler.processPoints(&txs, &tys);
+        listSink.pointList4(txs, tys);
+        xs = xs + 4.0f * dx;
+        count -= 4;
+    }
+    if (count > 0) {
+        tiler.processPoints(&xs, &ys);
+        listSink.pointListFew(count, xs, ys);
+    }
+
+    bool handledSpan = tiler.maybeProcessSpan(span, &spanSink);
+    if (handledSpan) {
+        auto firstNotTheSame = std::mismatch(
+            listPoints.begin(), listPoints.end(), spanPoints.begin());
+        if (firstNotTheSame.first != listSink.fPoints->end()) {
+            auto element = std::distance(listPoints.begin(), firstNotTheSame.first);
+            SkASSERT(element >= 0);
+            std::tie(start, length, count) = originalSpan;
+            ERRORF(reporter, "Span: {%f, %f}, %f, %d", start.fX, start.fY, length, count);
+            ERRORF(reporter, "Size points: %d, size span: %d",
+                   listPoints.size(), spanPoints.size());
+            if ((unsigned)element >= spanPoints.size()) {
+                ERRORF(reporter, "Size points: %d, size span: %d",
+                       listPoints.size(), spanPoints.size());
+                // Mismatch off the end
+                ERRORF(reporter,
+                       "The mismatch is at position %d and has value %f, %f - it is off the end "
+                           "of the other.",
+                       element, X(*firstNotTheSame.first), Y(*firstNotTheSame.first));
+            } else {
+                ERRORF(reporter,
+                       "Mismatch at %d - points: %f, %f - span: %f, %f",
+                       element, listPoints[element].fX, listPoints[element].fY,
+                       spanPoints[element].fX, spanPoints[element].fY);
+            }
+            SkFAIL("aha");
         }
     }
-
-    SkPM4f* FPbuffer = new SkPM4f[width * height];
-
-    SkMatrix m = SkMatrix::I();
-    //m.setRotate(30.0f, 1.0f, 1.0f);
-    SkMatrix invert;
-    bool trash = m.invert(&invert);
-    sk_ignore_unused_variable(trash);
-
-    const SkImageInfo info =
-        SkImageInfo::MakeN32Premul(width, height, kLinear_SkColorProfileType);
-
-    SkPixmap srcPixmap{info, bitmap, static_cast<size_t>(4 * width)};
-
-    SkLinearBitmapPipeline pipeline{invert, kNone_SkFilterQuality, SkShader::kClamp_TileMode,
-                                    SkShader::kClamp_TileMode, srcPixmap};
-
-    int count = 10;
-
-    pipeline.shadeSpan4f(3, 6, FPbuffer, count);
-#if 0
-    Pixel* pixelBuffer = (Pixel*)FPbuffer;
-    for (int i = 0; i < count; i++) {
-        printf("i: %d - (%g, %g, %g, %g)\n", i,
-               pixelBuffer[i][0] * 255.0f,
-               pixelBuffer[i][1] * 255.0f,
-               pixelBuffer[i][2] * 255.0f,
-               pixelBuffer[i][3] * 255.0f);
-    }
-#endif
-
-    delete [] bitmap;
-    delete [] FPbuffer;
+    return true;
 }
 
+template <typename Tiler>
+static bool compare_tiler_spans(int width, int height, skiatest::Reporter* reporter) {
+    Tiler tiler{SkSize::Make((SkScalar)width, (SkScalar)height)};
+    INFOF(reporter, "w: %d, h: %d \n", width, height);
+    std::array<int, 8> interestingX {{-5, -1, 0, 1, width - 1, width, width + 1, width + 5}};
+    std::array<int, 8> interestingY {{-5, -1, 0, 1, height - 1, height, height + 1, height + 5}};
+    std::array<int, 6> interestingCount {{1, 2, 3, 4, 5, 10}};
+    std::array<SkScalar, 7> interestingScale {{0.0f, 1.0f, 0.5f, 2.1f, -2.1f, -1.0f, -0.5f}};
+    for (auto scale : interestingScale) {
+        for (auto startX : interestingX) {
+            for (auto count : interestingCount) {
+                for (auto y : interestingY) {
+                    Span span{
+                        SkPoint::Make((SkScalar)startX, (SkScalar)y), (count-1.0f) * scale, count};
+                    if (!compare_tiler_case(tiler, span, reporter)) {
+                        return false;
+                    }
+                }
+            }
+        }
+    }
+    return true;
+}
+
+template <typename Tiler>
+static void test_tiler(skiatest::Reporter* reporter) {
+    std::array<int, 6> interestingSize {{1, 2, 3, 4, 5, 10}};
+    for (auto width : interestingSize) {
+        for (auto height : interestingSize) {
+            if (!compare_tiler_spans<Tiler>(width, height, reporter)) { return; }
+        }
+    }
+}
+
+DEF_TEST(LBPStrategyClampTile, reporter) {
+#if 0
+    ClampStrategy tiler{SkSize::Make(1, 1)};
+    Span span{SkPoint::Make(0, -5), 1.0f, 2};
+    compare_tiler_case<ClampStrategy>(tiler, span, reporter);
+#else
+    test_tiler<ClampStrategy>(reporter);
+#endif
+}
+
+DEF_TEST(LBPStrategyRepeatTile, reporter) {
+#if 0
+    RepeatStrategy tiler{SkSize::Make(3, 1)};
+    Span span{SkPoint::Make(-5, -5), 20 * 2.1f, 100};
+    compare_tiler_case<RepeatStrategy>(tiler, span, reporter);
+#else
+    test_tiler<RepeatStrategy>(reporter);
+#endif
+}
+
+