Fix filter primitive bounds computations.

Make each filter responsible for expanding its destination
bounds. Previously, we were using a union of all
intermediate bounds sizes via join() calls in many image
filters' computeFastBounds(), due to the fact that those
filters could only produce bitmaps the same size as their
inputs. Now, we compute optimal bounds for each filter as
follows:

1) Pass the (unmodified) clip bounds to the root node
of the DAG in the first recursive call to onFilterImage()
as the Context's fClipBounds.

2) Reverse-map the clip: when recursing up the DAG in
filterInput[GPU](), apply filter-specific expansion to the
clip by calling calling onFilterNodeBounds(... kReverse).
This allows upstream nodes to have a clip that respects the
current node's requirements. This is done via helper
function mapContext().

3) Forward-map the source bitmap: just prior to applying
the crop rect in applyCropRect(), we determine the filter's
preferred bounds by mapping the source bitmap bounds
forwards via onFilterNodeBounds(..., kForward).

NOTE: GMs affected by this change:
fast_slow_blurimagefilter: fast and slow paths now produce the same result
spritebitmap: drawSprite() and drawBitmap() paths now produce the same result
filterfastbounds: fast bounds are optimized; all drop-shadow results now appear
apply-filter: snug and not-snug cases give same results
dropshadowimagefilter: drawSprite() results now show shadows
draw-with-filter: no artifacts on erode edges; blur edges no longer clipped
displacement, imagefiltersbase, imagefiltersclipped, imagefilterscropexpand, imagefiltersscaled, matriximagefilter,
resizeimagefilter, localmatriximagefilter, testimagefilters: fixed incorrect clipping
imagefilterstransformed, morphology: no artifacts on erode edges

BUG=skia:1062,skia:3194,skia:3939,skia:4337,skia:4526

Review URL: https://codereview.chromium.org/1308703007
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index 3880fa9..0c9c20d 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -40,8 +40,6 @@
 #include "GrRenderTarget.h"
 #endif
 
-#define SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
-
 /*
  *  Return true if the drawing this rect would hit every pixels in the canvas.
  *
@@ -1084,6 +1082,10 @@
         } else {
             bounds = nullptr;
         }
+#else
+        if (bounds && !imageFilter->canComputeFastBounds()) {
+            bounds = nullptr;
+        }
 #endif
     }
     SkIRect ir;
@@ -1370,7 +1372,11 @@
             const SkBitmap& src = srcDev->accessBitmap(false);
             SkMatrix matrix = *iter.fMatrix;
             matrix.postTranslate(SkIntToScalar(-pos.x()), SkIntToScalar(-pos.y()));
+#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
             SkIRect clipBounds = SkIRect::MakeWH(srcDev->width(), srcDev->height());
+#else
+            SkIRect clipBounds = iter.fClip->getBounds().makeOffset(-pos.x(), -pos.y());
+#endif
             SkAutoTUnref<SkImageFilter::Cache> cache(dstDev->getImageFilterCache());
             SkImageFilter::Context ctx(matrix, clipBounds, cache.get(),
                                        SkImageFilter::kApprox_SizeConstraint);
diff --git a/src/core/SkDevice.cpp b/src/core/SkDevice.cpp
index cf4a279..cf57c2d 100644
--- a/src/core/SkDevice.cpp
+++ b/src/core/SkDevice.cpp
@@ -411,7 +411,11 @@
         SkIPoint offset = SkIPoint::Make(0, 0);
         SkMatrix matrix = *draw.fMatrix;
         matrix.postTranslate(SkIntToScalar(-x), SkIntToScalar(-y));
+#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
         const SkIRect clipBounds = bitmap.bounds();
+#else
+        const SkIRect clipBounds = draw.fClip->getBounds().makeOffset(-x, -y);
+#endif
         SkAutoTUnref<SkImageFilter::Cache> cache(this->getImageFilterCache());
         SkImageFilter::Context ctx(matrix, clipBounds, cache.get(),
                                    SkImageFilter::kApprox_SizeConstraint);
diff --git a/src/core/SkImageFilter.cpp b/src/core/SkImageFilter.cpp
index 3b2b277..2bfd142 100644
--- a/src/core/SkImageFilter.cpp
+++ b/src/core/SkImageFilter.cpp
@@ -277,7 +277,7 @@
     }
     Context ctx(origCtx.ctm(), origCtx.clipBounds(), origCtx.cache(), constraint);
 
-    return input->filterImage(proxy, src, ctx, result, offset);
+    return input->filterImage(proxy, src, this->mapContext(ctx), result, offset);
 }
 
 bool SkImageFilter::filterBounds(const SkIRect& src, const SkMatrix& ctm,
@@ -405,7 +405,12 @@
     }
     src.getBounds(srcBounds);
     srcBounds->offset(srcOffset);
-    return fCropRect.applyTo(*srcBounds, ctx, dstBounds) && srcBounds->intersect(*dstBounds);
+#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
+    return fCropRect.applyTo(*srcBounds, ctx, dstBounds);
+#else
+    this->onFilterNodeBounds(*srcBounds, ctx.ctm(), dstBounds, kForward_MapDirection);
+    return fCropRect.applyTo(*dstBounds, ctx, dstBounds);
+#endif
 }
 
 bool SkImageFilter::applyCropRect(const Context& ctx, Proxy* proxy, const SkBitmap& src,
@@ -413,7 +418,13 @@
     SkIRect srcBounds;
     src.getBounds(&srcBounds);
     srcBounds.offset(*srcOffset);
+#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
     if (!fCropRect.applyTo(srcBounds, ctx, bounds)) {
+#else
+    SkIRect dstBounds;
+    this->onFilterNodeBounds(srcBounds, ctx.ctm(), &dstBounds, kForward_MapDirection);
+    if (!fCropRect.applyTo(dstBounds, ctx, bounds)) {
+#endif
         return false;
     }
 
@@ -441,26 +452,44 @@
         return true;
     }
 
-    SkIRect bounds;
+    SkIRect bounds, totalBounds;
+    this->onFilterNodeBounds(src, ctm, &bounds, kReverse_MapDirection);
     for (int i = 0; i < fInputCount; ++i) {
         SkImageFilter* filter = this->getInput(i);
-        SkIRect rect = src;
-        if (filter && !filter->filterBounds(src, ctm, &rect)) {
+        SkIRect rect = bounds;
+        if (filter && !filter->filterBounds(bounds, ctm, &rect)) {
             return false;
         }
         if (0 == i) {
-            bounds = rect;
+            totalBounds = rect;
         } else {
-            bounds.join(rect);
+            totalBounds.join(rect);
         }
     }
 
     // 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 = bounds;
+    *dst = totalBounds;
     return true;
 }
 
+void SkImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix&,
+                                       SkIRect* dst, MapDirection) const {
+    *dst = src;
+}
+
+
+SkImageFilter::Context SkImageFilter::mapContext(const Context& ctx) const {
+#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
+    return ctx;
+#else
+    SkIRect clipBounds;
+    this->onFilterNodeBounds(ctx.clipBounds(), ctx.ctm(), &clipBounds,
+                             MapDirection::kReverse_MapDirection);
+    return Context(ctx.ctm(), clipBounds, ctx.cache(), ctx.sizeConstraint());
+#endif
+}
+
 bool SkImageFilter::asFragmentProcessor(GrFragmentProcessor**, GrTexture*,
                                         const SkMatrix&, const SkIRect&) const {
     return false;
@@ -506,7 +535,7 @@
     }
     Context ctx(origCtx.ctm(), origCtx.clipBounds(), origCtx.cache(), constraint);
 
-    if (input->filterImage(proxy, src, ctx, result, offset)) {
+    if (input->filterImage(proxy, src, this->mapContext(ctx), result, offset)) {
         if (!result->getTexture()) {
             const SkImageInfo info = result->info();
             if (kUnknown_SkColorType == info.colorType()) {
diff --git a/src/core/SkMatrixImageFilter.cpp b/src/core/SkMatrixImageFilter.cpp
index 4370dda..4138ccf 100644
--- a/src/core/SkMatrixImageFilter.cpp
+++ b/src/core/SkMatrixImageFilter.cpp
@@ -97,30 +97,33 @@
         getInput(0)->computeFastBounds(src, &bounds);
     }
     fTransform.mapRect(dst, bounds);
+#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
     dst->join(bounds);   // Work around for skia:3194
+#endif
 }
 
-bool SkMatrixImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
-                                         SkIRect* dst) const {
-    SkMatrix transformInverse;
-    if (!fTransform.invert(&transformInverse)) {
-        return false;
-    }
+void SkMatrixImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
+                                             SkIRect* dst, MapDirection direction) const {
     SkMatrix matrix;
     if (!ctm.invert(&matrix)) {
-        return false;
+        *dst = src;
+        return;
     }
-    matrix.postConcat(transformInverse);
+    if (kForward_MapDirection == direction) {
+        matrix.postConcat(fTransform);
+    } else {
+        SkMatrix transformInverse;
+        if (!fTransform.invert(&transformInverse)) {
+            *dst = src;
+            return;
+        }
+        matrix.postConcat(transformInverse);
+    }
     matrix.postConcat(ctm);
     SkRect floatBounds;
     matrix.mapRect(&floatBounds, SkRect::Make(src));
     SkIRect bounds = floatBounds.roundOut();
-    if (getInput(0) && !getInput(0)->filterBounds(bounds, ctm, &bounds)) {
-        return false;
-    }
-
     *dst = bounds;
-    return true;
 }
 
 #ifndef SK_IGNORE_TO_STRING
diff --git a/src/core/SkMatrixImageFilter.h b/src/core/SkMatrixImageFilter.h
index 86734b6..09bfca9 100644
--- a/src/core/SkMatrixImageFilter.h
+++ b/src/core/SkMatrixImageFilter.h
@@ -46,8 +46,8 @@
 
     virtual bool onFilterImage(Proxy*, const SkBitmap& src, const Context&,
                                SkBitmap* result, SkIPoint* loc) const override;
-    virtual bool onFilterBounds(const SkIRect& src, const SkMatrix&,
-                                SkIRect* dst) const override;
+    virtual void onFilterNodeBounds(const SkIRect& src, const SkMatrix&,
+                                    SkIRect* dst, MapDirection) const override;
 
 private:
     SkMatrix              fTransform;
diff --git a/src/core/SkRecordDraw.cpp b/src/core/SkRecordDraw.cpp
index b9bf92c..849e8f9 100644
--- a/src/core/SkRecordDraw.cpp
+++ b/src/core/SkRecordDraw.cpp
@@ -221,6 +221,7 @@
         int controlOps;        // Number of control ops in this Save block, including the Save.
         Bounds bounds;         // Bounds of everything in the block.
         const SkPaint* paint;  // Unowned.  If set, adjusts the bounds of all ops in this block.
+        SkMatrix ctm;
     };
 
     // Only Restore, SetMatrix, and Concat change the CTM.
@@ -301,6 +302,7 @@
         sb.bounds =
             PaintMayAffectTransparentBlack(paint) ? fCurrentClipBounds : Bounds::MakeEmpty();
         sb.paint = paint;
+        sb.ctm = this->fCTM;
 
         fSaveStack.push(sb);
         this->pushControl();
@@ -563,9 +565,15 @@
 
     bool adjustForSaveLayerPaints(SkRect* rect, int savesToIgnore = 0) const {
         for (int i = fSaveStack.count() - 1 - savesToIgnore; i >= 0; i--) {
+            SkMatrix inverse;
+            if (!fSaveStack[i].ctm.invert(&inverse)) {
+                return false;
+            }
+            inverse.mapRect(rect);
             if (!AdjustForPaint(fSaveStack[i].paint, rect)) {
                 return false;
             }
+            fSaveStack[i].ctm.mapRect(rect);
         }
         return true;
     }
diff --git a/src/effects/SkBlurImageFilter.cpp b/src/effects/SkBlurImageFilter.cpp
index 928793d..752749b 100644
--- a/src/effects/SkBlurImageFilter.cpp
+++ b/src/effects/SkBlurImageFilter.cpp
@@ -82,7 +82,10 @@
     }
 
     SkIRect srcBounds, dstBounds;
-    if (!this->applyCropRect(ctx, src, srcOffset, &dstBounds, &srcBounds)) {
+    if (!this->applyCropRect(this->mapContext(ctx), src, srcOffset, &dstBounds, &srcBounds)) {
+        return false;
+    }
+    if (!srcBounds.intersect(dstBounds)) {
         return false;
     }
 
@@ -184,17 +187,12 @@
                 SkScalarMul(fSigma.height(), SkIntToScalar(3)));
 }
 
-bool SkBlurImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
-                                       SkIRect* dst) const {
-    SkIRect bounds = src;
+void SkBlurImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
+                                           SkIRect* dst, MapDirection) const {
+    *dst = src;
     SkVector sigma = mapSigma(fSigma, ctm);
-    bounds.outset(SkScalarCeilToInt(SkScalarMul(sigma.x(), SkIntToScalar(3))),
-                  SkScalarCeilToInt(SkScalarMul(sigma.y(), SkIntToScalar(3))));
-    if (this->getInput(0) && !this->getInput(0)->filterBounds(bounds, ctm, &bounds)) {
-        return false;
-    }
-    *dst = bounds;
-    return true;
+    dst->outset(SkScalarCeilToInt(SkScalarMul(sigma.x(), SkIntToScalar(3))),
+                SkScalarCeilToInt(SkScalarMul(sigma.y(), SkIntToScalar(3))));
 }
 
 bool SkBlurImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const Context& ctx,
@@ -206,7 +204,10 @@
         return false;
     }
     SkIRect srcBounds, dstBounds;
-    if (!this->applyCropRect(ctx, input, srcOffset, &dstBounds, &srcBounds)) {
+    if (!this->applyCropRect(this->mapContext(ctx), input, srcOffset, &dstBounds, &srcBounds)) {
+        return false;
+    }
+    if (!srcBounds.intersect(dstBounds)) {
         return false;
     }
     GrTexture* source = input.getTexture();
diff --git a/src/effects/SkComposeImageFilter.cpp b/src/effects/SkComposeImageFilter.cpp
index 1be03a3..5915955 100644
--- a/src/effects/SkComposeImageFilter.cpp
+++ b/src/effects/SkComposeImageFilter.cpp
@@ -35,7 +35,9 @@
 
     SkMatrix outerMatrix(ctx.ctm());
     outerMatrix.postTranslate(SkIntToScalar(-innerOffset.x()), SkIntToScalar(-innerOffset.y()));
-    Context outerContext(outerMatrix, ctx.clipBounds(), ctx.cache(), ctx.sizeConstraint());
+    SkIRect clipBounds = ctx.clipBounds();
+    clipBounds.offset(-innerOffset.x(), -innerOffset.y());
+    Context outerContext(outerMatrix, clipBounds, ctx.cache(), ctx.sizeConstraint());
     if (!this->filterInput(0, proxy, tmp, outerContext, result, &outerOffset, false)) {
         return false;
     }
diff --git a/src/effects/SkDisplacementMapEffect.cpp b/src/effects/SkDisplacementMapEffect.cpp
index 3370a76..a3fff39 100644
--- a/src/effects/SkDisplacementMapEffect.cpp
+++ b/src/effects/SkDisplacementMapEffect.cpp
@@ -271,13 +271,19 @@
     dst->outset(fScale * SK_ScalarHalf, fScale * SK_ScalarHalf);
 }
 
-bool SkDisplacementMapEffect::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
-                                   SkIRect* dst) const {
-    SkIRect bounds = src;
+void SkDisplacementMapEffect::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
+                                   SkIRect* dst, MapDirection) const {
+    *dst = src;
     SkVector scale = SkVector::Make(fScale, fScale);
     ctm.mapVectors(&scale, 1);
-    bounds.outset(SkScalarCeilToInt(scale.fX * SK_ScalarHalf),
-                  SkScalarCeilToInt(scale.fY * SK_ScalarHalf));
+    dst->outset(SkScalarCeilToInt(scale.fX * SK_ScalarHalf),
+                SkScalarCeilToInt(scale.fY * SK_ScalarHalf));
+}
+
+bool SkDisplacementMapEffect::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
+                                   SkIRect* dst) const {
+    SkIRect bounds;
+    this->onFilterNodeBounds(src, ctm, &bounds, kReverse_MapDirection);
     if (this->getColorInput()) {
         return this->getColorInput()->filterBounds(bounds, ctm, dst);
     }
diff --git a/src/effects/SkDropShadowImageFilter.cpp b/src/effects/SkDropShadowImageFilter.cpp
index 7519d53..eb05cf0 100644
--- a/src/effects/SkDropShadowImageFilter.cpp
+++ b/src/effects/SkDropShadowImageFilter.cpp
@@ -116,25 +116,23 @@
     }
 }
 
-bool SkDropShadowImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
-                                             SkIRect* dst) const {
-    SkIRect bounds = src;
+void SkDropShadowImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
+                                                 SkIRect* dst, MapDirection direction) const {
+    *dst = src;
     SkVector offsetVec = SkVector::Make(fDx, fDy);
+    if (kReverse_MapDirection == direction) {
+        offsetVec.negate();
+    }
     ctm.mapVectors(&offsetVec, 1);
-    bounds.offset(-SkScalarCeilToInt(offsetVec.x()),
-                  -SkScalarCeilToInt(offsetVec.y()));
+    dst->offset(SkScalarCeilToInt(offsetVec.x()),
+                SkScalarCeilToInt(offsetVec.y()));
     SkVector sigma = SkVector::Make(fSigmaX, fSigmaY);
     ctm.mapVectors(&sigma, 1);
-    bounds.outset(SkScalarCeilToInt(SkScalarMul(sigma.x(), SkIntToScalar(3))),
-                  SkScalarCeilToInt(SkScalarMul(sigma.y(), SkIntToScalar(3))));
+    dst->outset(SkScalarCeilToInt(SkScalarMul(sigma.x(), SkIntToScalar(3))),
+                SkScalarCeilToInt(SkScalarMul(sigma.y(), SkIntToScalar(3))));
     if (fShadowMode == kDrawShadowAndForeground_ShadowMode) {
-        bounds.join(src);
+        dst->join(src);
     }
-    if (getInput(0) && !getInput(0)->filterBounds(bounds, ctm, &bounds)) {
-        return false;
-    }
-    *dst = bounds;
-    return true;
 }
 
 #ifndef SK_IGNORE_TO_STRING
diff --git a/src/effects/SkMatrixConvolutionImageFilter.cpp b/src/effects/SkMatrixConvolutionImageFilter.cpp
index 7c5dd83..a1f23f7 100644
--- a/src/effects/SkMatrixConvolutionImageFilter.cpp
+++ b/src/effects/SkMatrixConvolutionImageFilter.cpp
@@ -280,7 +280,7 @@
     }
 
     SkIRect bounds;
-    if (!this->applyCropRect(ctx, proxy, src, &srcOffset, &bounds, &src)) {
+    if (!this->applyCropRect(this->mapContext(ctx), proxy, src, &srcOffset, &bounds, &src)) {
         return false;
     }
 
@@ -322,17 +322,17 @@
     return true;
 }
 
-bool SkMatrixConvolutionImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
-                                                    SkIRect* dst) const {
-    SkIRect bounds = src;
-    bounds.fRight += fKernelSize.width() - 1;
-    bounds.fBottom += fKernelSize.height() - 1;
-    bounds.offset(-fKernelOffset);
-    if (getInput(0) && !getInput(0)->filterBounds(bounds, ctm, &bounds)) {
-        return false;
+void SkMatrixConvolutionImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
+                                                    SkIRect* dst, MapDirection direction) const {
+    *dst = src;
+    int w = fKernelSize.width() - 1, h = fKernelSize.height() - 1;
+    dst->fRight += w;
+    dst->fBottom += h;
+    if (kReverse_MapDirection == direction) {
+        dst->offset(-fKernelOffset);
+    } else {
+        dst->offset(fKernelOffset - SkIPoint::Make(w, h));
     }
-    *dst = bounds;
-    return true;
 }
 
 bool SkMatrixConvolutionImageFilter::canComputeFastBounds() const {
diff --git a/src/effects/SkMorphologyImageFilter.cpp b/src/effects/SkMorphologyImageFilter.cpp
index c6bbce7..205fc0d 100644
--- a/src/effects/SkMorphologyImageFilter.cpp
+++ b/src/effects/SkMorphologyImageFilter.cpp
@@ -70,7 +70,7 @@
     }
 
     SkIRect bounds;
-    if (!this->applyCropRect(ctx, proxy, src, &srcOffset, &bounds, &src)) {
+    if (!this->applyCropRect(this->mapContext(ctx), proxy, src, &srcOffset, &bounds, &src)) {
         return false;
     }
 
@@ -149,18 +149,13 @@
     dst->outset(SkIntToScalar(fRadius.width()), SkIntToScalar(fRadius.height()));
 }
 
-bool SkMorphologyImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
-                                             SkIRect* dst) const {
-    SkIRect bounds = src;
+void SkMorphologyImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
+                                                 SkIRect* dst, MapDirection) const {
+    *dst = src;
     SkVector radius = SkVector::Make(SkIntToScalar(this->radius().width()),
                                      SkIntToScalar(this->radius().height()));
     ctm.mapVectors(&radius, 1);
-    bounds.outset(SkScalarCeilToInt(radius.x()), SkScalarCeilToInt(radius.y()));
-    if (this->getInput(0) && !this->getInput(0)->filterBounds(bounds, ctm, &bounds)) {
-        return false;
-    }
-    *dst = bounds;
-    return true;
+    dst->outset(SkScalarCeilToInt(radius.x()), SkScalarCeilToInt(radius.y()));
 }
 
 SkFlattenable* SkErodeImageFilter::CreateProc(SkReadBuffer& buffer) {
@@ -637,7 +632,7 @@
         return false;
     }
     SkIRect bounds;
-    if (!this->applyCropRect(ctx, proxy, input, &srcOffset, &bounds, &input)) {
+    if (!this->applyCropRect(this->mapContext(ctx), proxy, input, &srcOffset, &bounds, &input)) {
         return false;
     }
     SkVector radius = SkVector::Make(SkIntToScalar(this->radius().width()),
diff --git a/src/effects/SkOffsetImageFilter.cpp b/src/effects/SkOffsetImageFilter.cpp
index 9da026b..c4fc5eb 100644
--- a/src/effects/SkOffsetImageFilter.cpp
+++ b/src/effects/SkOffsetImageFilter.cpp
@@ -70,24 +70,28 @@
     } else {
         *dst = src;
     }
+#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
     SkRect copy = *dst;
+#endif
     dst->offset(fOffset.fX, fOffset.fY);
+#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
     dst->join(copy);
+#endif
 }
 
-bool SkOffsetImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
-                                         SkIRect* dst) const {
+void SkOffsetImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
+                                             SkIRect* dst, MapDirection direction) const {
     SkVector vec;
     ctm.mapVectors(&vec, &fOffset, 1);
-
-    SkIRect bounds = src;
-    bounds.offset(-SkScalarCeilToInt(vec.fX), -SkScalarCeilToInt(vec.fY));
-    bounds.join(src);
-    if (getInput(0)) {
-        return getInput(0)->filterBounds(bounds, ctm, dst);
+    if (kReverse_MapDirection == direction) {
+        vec.negate();
     }
-    *dst = bounds;
-    return true;
+
+    *dst = src;
+    dst->offset(SkScalarCeilToInt(vec.fX), SkScalarCeilToInt(vec.fY));
+#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
+    dst->join(src);
+#endif
 }
 
 SkFlattenable* SkOffsetImageFilter::CreateProc(SkReadBuffer& buffer) {
diff --git a/src/effects/SkTileImageFilter.cpp b/src/effects/SkTileImageFilter.cpp
index 52ea6a7..8ef617d 100644
--- a/src/effects/SkTileImageFilter.cpp
+++ b/src/effects/SkTileImageFilter.cpp
@@ -81,21 +81,30 @@
     return true;
 }
 
+void SkTileImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
+                                          SkIRect* dst, MapDirection direction) const {
+    SkRect rect = kReverse_MapDirection == direction ? fSrcRect : fDstRect;
+    ctm.mapRect(&rect);
+    rect.roundOut(dst);
+#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
+    dst->join(src);
+#endif
+}
+
 bool SkTileImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
                                        SkIRect* dst) const {
-    SkRect srcRect;
-    ctm.mapRect(&srcRect, fSrcRect);
-    SkIRect srcIRect;
-    srcRect.roundOut(&srcIRect);
-    srcIRect.join(src);
-    *dst = srcIRect;
+    this->onFilterNodeBounds(src, ctm, dst, kReverse_MapDirection);
     return true;
 }
 
 void SkTileImageFilter::computeFastBounds(const SkRect& src, SkRect* dst) const {
+#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
     // This is a workaround for skia:3194.
     *dst = src;
     dst->join(fDstRect);
+#else
+    *dst = fDstRect;
+#endif
 }
 
 SkFlattenable* SkTileImageFilter::CreateProc(SkReadBuffer& buffer) {
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 2545313..52f730a 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -1164,7 +1164,11 @@
         SkIPoint offset = SkIPoint::Make(0, 0);
         SkMatrix matrix(*draw.fMatrix);
         matrix.postTranslate(SkIntToScalar(-left), SkIntToScalar(-top));
+#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
         SkIRect clipBounds = SkIRect::MakeWH(bitmap.width(), bitmap.height());
+#else
+        SkIRect clipBounds = draw.fClip->getBounds().makeOffset(-left, -top);
+#endif
         SkAutoTUnref<SkImageFilter::Cache> cache(getImageFilterCache());
         // This cache is transient, and is freed (along with all its contained
         // textures) when it goes out of scope.
@@ -1327,7 +1331,11 @@
         SkIPoint offset = SkIPoint::Make(0, 0);
         SkMatrix matrix(*draw.fMatrix);
         matrix.postTranslate(SkIntToScalar(-x), SkIntToScalar(-y));
+#ifdef SK_SUPPORT_SRC_BOUNDS_BLOAT_FOR_IMAGEFILTERS
         SkIRect clipBounds = SkIRect::MakeWH(devTex->width(), devTex->height());
+#else
+        SkIRect clipBounds = draw.fClip->getBounds().makeOffset(-x, -y);
+#endif
         // This cache is transient, and is freed (along with all its contained
         // textures) when it goes out of scope.
         SkAutoTUnref<SkImageFilter::Cache> cache(getImageFilterCache());