Implement correct clipping for image filters.

Image filters in Skia currently clip the size of the the offscreen
bitmap used for filtering to the device clip bounds. This means that
any pixel-moving filter (e.g., blur) has edge artifacts at the clip
boundaries. This is problematic for tiling, where a single SkPicture
is played back with a clip set to the tile boundaries.

By implementing the onFilterBounds() traversal, and using it in
saveLayer() when a filter is present, we can clip the layer to the
expanded clip rect. Note that this requires that the traversal be
performed in reverse as compared to computeFastBounds().  (It's also
done in device space, unlike computeFastBounds()).

New test imagefiltersclipped tests pixel-moving filters when clipped
by various clip rects.
New test imageblurtiled tests tiled (compositor-style) rendering of
blurred text. There should be no artifacts at the tile boundaries.

BUG=337831
R=reed@google.com

Review URL: https://codereview.chromium.org/23011012

git-svn-id: http://skia.googlecode.com/svn/trunk@13323 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/effects/SkBitmapSource.cpp b/src/effects/SkBitmapSource.cpp
index 67567c5..f318c91 100644
--- a/src/effects/SkBitmapSource.cpp
+++ b/src/effects/SkBitmapSource.cpp
@@ -83,3 +83,9 @@
 void SkBitmapSource::computeFastBounds(const SkRect&, SkRect* dst) const {
     *dst = fDstRect;
 }
+
+bool SkBitmapSource::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
+                                    SkIRect* dst) const {
+    *dst = src;
+    return true;
+}
diff --git a/src/effects/SkBlurImageFilter.cpp b/src/effects/SkBlurImageFilter.cpp
index ccc7ec3..67b0511 100644
--- a/src/effects/SkBlurImageFilter.cpp
+++ b/src/effects/SkBlurImageFilter.cpp
@@ -236,6 +236,21 @@
     dst->outset(SkScalarMul(fSigma.width(), SkIntToScalar(3)),
                 SkScalarMul(fSigma.height(), SkIntToScalar(3)));
 }
+
+bool SkBlurImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
+                                       SkIRect* dst) const {
+    SkIRect bounds = src;
+    if (getInput(0) && !getInput(0)->filterBounds(src, ctm, &bounds)) {
+        return false;
+    }
+    SkVector sigma, localSigma = SkVector::Make(fSigma.width(), fSigma.height());
+    ctm.mapVectors(&sigma, &localSigma, 1);
+    bounds.outset(SkScalarCeilToInt(SkScalarMul(sigma.x(), SkIntToScalar(3))),
+                  SkScalarCeilToInt(SkScalarMul(sigma.y(), SkIntToScalar(3))));
+    *dst = bounds;
+    return true;
+}
+
 bool SkBlurImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const SkMatrix& ctm,
                                        SkBitmap* result, SkIPoint* offset) {
 #if SK_SUPPORT_GPU
diff --git a/src/effects/SkComposeImageFilter.cpp b/src/effects/SkComposeImageFilter.cpp
index c794b0d..cb51376 100644
--- a/src/effects/SkComposeImageFilter.cpp
+++ b/src/effects/SkComposeImageFilter.cpp
@@ -36,7 +36,7 @@
 
 bool SkComposeImageFilter::onFilterBounds(const SkIRect& src,
                                           const SkMatrix& ctm,
-                                          SkIRect* dst) {
+                                          SkIRect* dst) const {
     SkImageFilter* outer = getInput(0);
     SkImageFilter* inner = getInput(1);
 
diff --git a/src/effects/SkDisplacementMapEffect.cpp b/src/effects/SkDisplacementMapEffect.cpp
index e56cba1..555f795 100644
--- a/src/effects/SkDisplacementMapEffect.cpp
+++ b/src/effects/SkDisplacementMapEffect.cpp
@@ -255,6 +255,15 @@
     }
 }
 
+bool SkDisplacementMapEffect::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
+                                   SkIRect* dst) const {
+    if (getColorInput()) {
+        return getColorInput()->filterBounds(src, ctm, dst);
+    }
+    *dst = src;
+    return true;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 #if SK_SUPPORT_GPU
diff --git a/src/effects/SkDropShadowImageFilter.cpp b/src/effects/SkDropShadowImageFilter.cpp
index 0bc7e12..7cb5152 100644
--- a/src/effects/SkDropShadowImageFilter.cpp
+++ b/src/effects/SkDropShadowImageFilter.cpp
@@ -112,3 +112,22 @@
                         SkScalarMul(fSigmaY, SkIntToScalar(3)));
     dst->join(shadowBounds);
 }
+
+bool SkDropShadowImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
+                                             SkIRect* dst) const {
+    SkIRect bounds = src;
+    if (getInput(0) && !getInput(0)->filterBounds(src, ctm, &bounds)) {
+        return false;
+    }
+    SkVector offsetVec, localOffsetVec = SkVector::Make(fDx, fDy);
+    ctm.mapVectors(&offsetVec, &localOffsetVec, 1);
+    bounds.offset(-SkScalarCeilToInt(offsetVec.x()),
+                  -SkScalarCeilToInt(offsetVec.y()));
+    SkVector sigma, localSigma = SkVector::Make(fSigmaX, fSigmaY);
+    ctm.mapVectors(&sigma, &localSigma, 1);
+    bounds.outset(SkScalarCeilToInt(SkScalarMul(sigma.x(), SkIntToScalar(3))),
+                  SkScalarCeilToInt(SkScalarMul(sigma.y(), SkIntToScalar(3))));
+    bounds.join(src);
+    *dst = bounds;
+    return true;
+}
diff --git a/src/effects/SkMergeImageFilter.cpp b/src/effects/SkMergeImageFilter.cpp
index 086d8e6..d0a153f 100755
--- a/src/effects/SkMergeImageFilter.cpp
+++ b/src/effects/SkMergeImageFilter.cpp
@@ -65,38 +65,6 @@
     }
 }
 
-bool SkMergeImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
-                                        SkIRect* dst) {
-    if (countInputs() < 1) {
-        return false;
-    }
-
-    SkIRect totalBounds;
-
-    int inputCount = countInputs();
-    for (int i = 0; i < inputCount; ++i) {
-        SkImageFilter* filter = getInput(i);
-        SkIRect r;
-        if (filter) {
-            if (!filter->filterBounds(src, ctm, &r)) {
-                return false;
-            }
-        } else {
-            r = src;
-        }
-        if (0 == i) {
-            totalBounds = r;
-        } else {
-            totalBounds.join(r);
-        }
-    }
-
-    // don't modify dst until now, so we don't accidentally change it in the
-    // loop, but then return false on the next filter.
-    *dst = totalBounds;
-    return true;
-}
-
 bool SkMergeImageFilter::onFilterImage(Proxy* proxy, const SkBitmap& src,
                                        const SkMatrix& ctm,
                                        SkBitmap* result, SkIPoint* offset) {
diff --git a/src/effects/SkMorphologyImageFilter.cpp b/src/effects/SkMorphologyImageFilter.cpp
index 7321eb2..46302ad 100644
--- a/src/effects/SkMorphologyImageFilter.cpp
+++ b/src/effects/SkMorphologyImageFilter.cpp
@@ -249,6 +249,20 @@
     dst->outset(SkIntToScalar(fRadius.width()), SkIntToScalar(fRadius.height()));
 }
 
+bool SkMorphologyImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
+                                             SkIRect* dst) const {
+    SkIRect bounds = src;
+    if (getInput(0) && !getInput(0)->filterBounds(src, ctm, &bounds)) {
+        return false;
+    }
+    SkVector radius = SkVector::Make(SkIntToScalar(this->radius().width()),
+                                     SkIntToScalar(this->radius().height()));
+    ctm.mapVectors(&radius, 1);
+    bounds.outset(SkScalarCeilToInt(radius.x()), SkScalarCeilToInt(radius.y()));
+    *dst = bounds;
+    return true;
+}
+
 #if SK_SUPPORT_GPU
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/src/effects/SkOffsetImageFilter.cpp b/src/effects/SkOffsetImageFilter.cpp
index 16ce36f..e19a327 100644
--- a/src/effects/SkOffsetImageFilter.cpp
+++ b/src/effects/SkOffsetImageFilter.cpp
@@ -72,16 +72,19 @@
     } else {
         *dst = src;
     }
+    SkRect copy = *dst;
     dst->offset(fOffset.fX, fOffset.fY);
+    dst->join(copy);
 }
 
 bool SkOffsetImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
-                                         SkIRect* dst) {
+                                         SkIRect* dst) const {
     SkVector vec;
     ctm.mapVectors(&vec, &fOffset, 1);
 
     *dst = src;
-    dst->offset(SkScalarRoundToInt(vec.fX), SkScalarRoundToInt(vec.fY));
+    dst->offset(-SkScalarCeilToInt(vec.fX), -SkScalarCeilToInt(vec.fY));
+    dst->join(src);
     return true;
 }