Fix Morphology effects sourcing outside of the crop rect.

BUG=skia:1766

Committed: https://skia.googlesource.com/skia/+/f6be925b5615f07039ce95c3433039694a8d1679

Review URL: https://codereview.chromium.org/781153002
diff --git a/src/effects/SkMorphologyImageFilter.cpp b/src/effects/SkMorphologyImageFilter.cpp
index e895cac..5e20418 100644
--- a/src/effects/SkMorphologyImageFilter.cpp
+++ b/src/effects/SkMorphologyImageFilter.cpp
@@ -303,9 +303,16 @@
         return SkNEW_ARGS(GrMorphologyEffect, (tex, dir, radius, type));
     }
 
+    static GrFragmentProcessor* Create(GrTexture* tex, Direction dir, int radius,
+                                       MorphologyType type, float bounds[2]) {
+        return SkNEW_ARGS(GrMorphologyEffect, (tex, dir, radius, type, bounds));
+    }
+
     virtual ~GrMorphologyEffect();
 
     MorphologyType type() const { return fType; }
+    bool useRange() const { return fUseRange; }
+    const float* range() const { return fRange; }
 
     const char* name() const SK_OVERRIDE { return "Morphology"; }
 
@@ -316,6 +323,8 @@
 protected:
 
     MorphologyType fType;
+    bool fUseRange;
+    float fRange[2];
 
 private:
     bool onIsEqual(const GrFragmentProcessor&) const SK_OVERRIDE;
@@ -323,6 +332,7 @@
     void onComputeInvariantOutput(GrInvariantOutput* inout) const SK_OVERRIDE;
 
     GrMorphologyEffect(GrTexture*, Direction, int radius, MorphologyType);
+    GrMorphologyEffect(GrTexture*, Direction, int radius, MorphologyType, float bounds[2]);
 
     GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
 
@@ -350,8 +360,11 @@
     int width() const { return GrMorphologyEffect::WidthFromRadius(fRadius); }
 
     int                                   fRadius;
+    Gr1DKernelEffect::Direction           fDirection;
+    bool                                  fUseRange;
     GrMorphologyEffect::MorphologyType    fType;
-    GrGLProgramDataManager::UniformHandle fImageIncrementUni;
+    GrGLProgramDataManager::UniformHandle fPixelSizeUni;
+    GrGLProgramDataManager::UniformHandle fRangeUni;
 
     typedef GrGLFragmentProcessor INHERITED;
 };
@@ -359,6 +372,8 @@
 GrGLMorphologyEffect::GrGLMorphologyEffect(const GrProcessor& proc) {
     const GrMorphologyEffect& m = proc.cast<GrMorphologyEffect>();
     fRadius = m.radius();
+    fDirection = m.direction();
+    fUseRange = m.useRange();
     fType = m.type();
 }
 
@@ -368,9 +383,14 @@
                                     const char* inputColor,
                                     const TransformedCoordsArray& coords,
                                     const TextureSamplerArray& samplers) {
-    fImageIncrementUni = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
-                                             kVec2f_GrSLType, kDefault_GrSLPrecision,
-                                             "ImageIncrement");
+    fPixelSizeUni = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
+                                            kFloat_GrSLType, kDefault_GrSLPrecision,
+                                            "PixelSize");
+    const char* pixelSizeInc = builder->getUniformCStr(fPixelSizeUni);
+    fRangeUni = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
+                                            kVec2f_GrSLType, kDefault_GrSLPrecision,
+                                            "Range");
+    const char* range = builder->getUniformCStr(fRangeUni);
 
     GrGLFPFragmentBuilder* fsBuilder = builder->getFragmentShaderBuilder();
     SkString coords2D = fsBuilder->ensureFSCoords2D(coords, 0);
@@ -389,14 +409,41 @@
             func = ""; // suppress warning
             break;
     }
-    const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
 
-    fsBuilder->codeAppendf("\t\tvec2 coord = %s - %d.0 * %s;\n", coords2D.c_str(), fRadius, imgInc);
-    fsBuilder->codeAppendf("\t\tfor (int i = 0; i < %d; i++) {\n", this->width());
+    const char* dir;
+    switch (fDirection) {
+        case Gr1DKernelEffect::kX_Direction:
+            dir = "x";
+            break;
+        case Gr1DKernelEffect::kY_Direction:
+            dir = "y";
+            break;
+        default:
+            SkFAIL("Unknown filter direction.");
+            dir = ""; // suppress warning
+    }
+
+    // vec2 coord = coord2D;
+    fsBuilder->codeAppendf("\t\tvec2 coord = %s;\n", coords2D.c_str());
+    // coord.x -= radius * pixelSize;
+    fsBuilder->codeAppendf("\t\tcoord.%s -= %d.0 * %s; \n", dir, fRadius, pixelSizeInc);
+    if (fUseRange) {
+        // highBound = min(highBound, coord.x + (width-1) * pixelSize);
+        fsBuilder->codeAppendf("\t\tfloat highBound = min(%s.y, coord.%s + %f * %s);",
+                               range, dir, float(width() - 1), pixelSizeInc);
+        // coord.x = max(lowBound, coord.x);
+        fsBuilder->codeAppendf("\t\tcoord.%s = max(%s.x, coord.%s);", dir, range, dir);
+    }
+    fsBuilder->codeAppendf("\t\tfor (int i = 0; i < %d; i++) {\n", width());
     fsBuilder->codeAppendf("\t\t\t%s = %s(%s, ", outputColor, func, outputColor);
     fsBuilder->appendTextureLookup(samplers[0], "coord");
     fsBuilder->codeAppend(");\n");
-    fsBuilder->codeAppendf("\t\t\tcoord += %s;\n", imgInc);
+    // coord.x += pixelSize;
+    fsBuilder->codeAppendf("\t\t\tcoord.%s += %s;\n", dir, pixelSizeInc);
+    if (fUseRange) {
+        // coord.x = min(highBound, coord.x);
+        fsBuilder->codeAppendf("\t\t\tcoord.%s = min(highBound, coord.%s);", dir, dir);
+    }
     fsBuilder->codeAppend("\t\t}\n");
     SkString modulate;
     GrGLSLMulVarBy4f(&modulate, outputColor, inputColor);
@@ -408,27 +455,41 @@
     const GrMorphologyEffect& m = proc.cast<GrMorphologyEffect>();
     uint32_t key = static_cast<uint32_t>(m.radius());
     key |= (m.type() << 8);
+    key |= (m.direction() << 9);
+    if (m.useRange()) key |= 1 << 10;
     b->add32(key);
 }
 
 void GrGLMorphologyEffect::setData(const GrGLProgramDataManager& pdman,
                                    const GrProcessor& proc) {
-    const Gr1DKernelEffect& kern = proc.cast<Gr1DKernelEffect>();
-    GrTexture& texture = *kern.texture(0);
-    // the code we generated was for a specific kernel radius
-    SkASSERT(kern.radius() == fRadius);
-    float imageIncrement[2] = { 0 };
-    switch (kern.direction()) {
+    const GrMorphologyEffect& m = proc.cast<GrMorphologyEffect>();
+    GrTexture& texture = *m.texture(0);
+    // the code we generated was for a specific kernel radius, direction and bound usage
+    SkASSERT(m.radius() == fRadius);
+    SkASSERT(m.direction() == fDirection);
+    SkASSERT(m.useRange() == fUseRange);
+
+    float pixelSize = 0.0f;
+    switch (fDirection) {
         case Gr1DKernelEffect::kX_Direction:
-            imageIncrement[0] = 1.0f / texture.width();
+            pixelSize = 1.0f / texture.width();
             break;
         case Gr1DKernelEffect::kY_Direction:
-            imageIncrement[1] = 1.0f / texture.height();
+            pixelSize = 1.0f / texture.height();
             break;
         default:
             SkFAIL("Unknown filter direction.");
     }
-    pdman.set2fv(fImageIncrementUni, 1, imageIncrement);
+    pdman.set1f(fPixelSizeUni, pixelSize);
+
+    if (fUseRange) {
+        const float* range = m.range();
+        if (fDirection && texture.origin() == kBottomLeft_GrSurfaceOrigin) {
+            pdman.set2f(fRangeUni, 1.0f - range[1], 1.0f - range[0]);
+        } else {
+            pdman.set2f(fRangeUni, range[0], range[1]);
+        }
+    }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -438,10 +499,22 @@
                                        int radius,
                                        MorphologyType type)
     : Gr1DKernelEffect(texture, direction, radius)
-    , fType(type) {
+    , fType(type), fUseRange(false) {
     this->initClassID<GrMorphologyEffect>();
 }
 
+GrMorphologyEffect::GrMorphologyEffect(GrTexture* texture,
+                                       Direction direction,
+                                       int radius,
+                                       MorphologyType type,
+                                       float range[2])
+    : Gr1DKernelEffect(texture, direction, radius)
+    , fType(type), fUseRange(true) {
+    this->initClassID<GrMorphologyEffect>();
+    fRange[0] = range[0];
+    fRange[1] = range[1];
+}
+
 GrMorphologyEffect::~GrMorphologyEffect() {
 }
 
@@ -456,6 +529,7 @@
     const GrMorphologyEffect& s = sBase.cast<GrMorphologyEffect>();
     return (this->radius() == s.radius() &&
             this->direction() == s.direction() &&
+            this->useRange() == s.useRange() &&
             this->type() == s.type());
 }
 
@@ -486,7 +560,26 @@
 
 namespace {
 
-void apply_morphology_pass(GrContext* context,
+
+void apply_morphology_rect(GrContext* context,
+                           GrTexture* texture,
+                           const SkIRect& srcRect,
+                           const SkIRect& dstRect,
+                           int radius,
+                           GrMorphologyEffect::MorphologyType morphType,
+                           float bounds[2],
+                           Gr1DKernelEffect::Direction direction) {
+    GrPaint paint;
+    paint.addColorProcessor(GrMorphologyEffect::Create(texture,
+                                                       direction,
+                                                       radius,
+                                                       morphType,
+                                                       bounds))->unref();
+    context->drawNonAARectToRect(paint, SkMatrix::I(), SkRect::Make(dstRect),
+                            SkRect::Make(srcRect));
+}
+
+void apply_morphology_rect_no_bounds(GrContext* context,
                            GrTexture* texture,
                            const SkIRect& srcRect,
                            const SkIRect& dstRect,
@@ -502,6 +595,51 @@
                             SkRect::Make(srcRect));
 }
 
+void apply_morphology_pass(GrContext* context,
+                           GrTexture* texture,
+                           const SkIRect& srcRect,
+                           const SkIRect& dstRect,
+                           int radius,
+                           GrMorphologyEffect::MorphologyType morphType,
+                           Gr1DKernelEffect::Direction direction) {
+    float bounds[2] = { 0.0f, 1.0f };
+    SkIRect lowerSrcRect = srcRect, lowerDstRect = dstRect;
+    SkIRect middleSrcRect = srcRect, middleDstRect = dstRect;
+    SkIRect upperSrcRect = srcRect, upperDstRect = dstRect;
+    if (direction == Gr1DKernelEffect::kX_Direction) {
+        bounds[0] = (SkIntToScalar(srcRect.left()) + 0.5f) / texture->width();
+        bounds[1] = (SkIntToScalar(srcRect.right()) - 0.5f) / texture->width();
+        lowerSrcRect.fRight = srcRect.left() + radius;
+        lowerDstRect.fRight = dstRect.left() + radius;
+        upperSrcRect.fLeft = srcRect.right() - radius;
+        upperDstRect.fLeft = dstRect.right() - radius;
+        middleSrcRect.inset(radius, 0);
+        middleDstRect.inset(radius, 0);
+    } else {
+        bounds[0] = (SkIntToScalar(srcRect.top()) + 0.5f) / texture->height();
+        bounds[1] = (SkIntToScalar(srcRect.bottom()) - 0.5f) / texture->height();
+        lowerSrcRect.fBottom = srcRect.top() + radius;
+        lowerDstRect.fBottom = dstRect.top() + radius;
+        upperSrcRect.fTop = srcRect.bottom() - radius;
+        upperDstRect.fTop = dstRect.bottom() - radius;
+        middleSrcRect.inset(0, radius);
+        middleDstRect.inset(0, radius);
+    }
+    if (middleSrcRect.fLeft - middleSrcRect.fRight >= 0) {
+        // radius covers srcRect; use bounds over entire draw
+        apply_morphology_rect(context, texture, srcRect, dstRect, radius,
+                              morphType, bounds, direction);
+    } else {
+        // Draw upper and lower margins with bounds; middle without.
+        apply_morphology_rect(context, texture, lowerSrcRect, lowerDstRect, radius,
+                              morphType, bounds, direction);
+        apply_morphology_rect(context, texture, upperSrcRect, upperDstRect, radius,
+                              morphType, bounds, direction);
+        apply_morphology_rect_no_bounds(context, texture, middleSrcRect, middleDstRect, radius,
+                              morphType, direction);
+    }
+}
+
 bool apply_morphology(const SkBitmap& input,
                       const SkIRect& rect,
                       GrMorphologyEffect::MorphologyType morphType,