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/expectations/gm/ignored-tests.txt b/expectations/gm/ignored-tests.txt
index b6c9107..f4bb10a 100644
--- a/expectations/gm/ignored-tests.txt
+++ b/expectations/gm/ignored-tests.txt
@@ -40,3 +40,17 @@
 # deprecated calling pattern.
 # https://codereview.chromium.org/154163002/
 extractbitmap
+
+# Added by senorblanco as part of https://codereview.chromium.org/23011012/
+colorfilterimagefilter
+dropshadowimagefilter
+imageblur
+imageblur_large
+imagefiltersbase
+imagefilterscropped
+imagefiltersgraph
+imagefiltersscaled
+morphology
+offsetimagefilter
+spritebitmap
+xfermodeimagefilter
diff --git a/gm/imageblurtiled.cpp b/gm/imageblurtiled.cpp
new file mode 100644
index 0000000..96df436
--- /dev/null
+++ b/gm/imageblurtiled.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkBlurImageFilter.h"
+#include "SkRandom.h"
+
+#define WIDTH 640
+#define HEIGHT 480
+
+namespace skiagm {
+
+class ImageBlurTiledGM : public GM {
+public:
+    ImageBlurTiledGM(SkScalar sigmaX, SkScalar sigmaY)
+        : fSigmaX(sigmaX), fSigmaY(sigmaY) {
+    }
+
+protected:
+    virtual SkString onShortName() {
+        return SkString("imageblurtiled");
+    }
+
+    virtual SkISize onISize() {
+        return make_isize(WIDTH, HEIGHT);
+    }
+
+    virtual void onDraw(SkCanvas* canvas) {
+        SkPaint paint;
+        paint.setImageFilter(new SkBlurImageFilter(fSigmaX, fSigmaY))->unref();
+        const SkScalar tile_size = SkIntToScalar(128);
+        SkRect bounds;
+        canvas->getClipBounds(&bounds);
+        for (SkScalar y = bounds.top(); y < bounds.bottom(); y += tile_size) {
+            for (SkScalar x = bounds.left(); x < bounds.right(); x += tile_size) {
+                canvas->save();
+                canvas->clipRect(SkRect::MakeXYWH(x, y, tile_size, tile_size));
+                canvas->saveLayer(NULL, &paint);
+                const char* str[] = {
+                    "The quick",
+                    "brown fox",
+                    "jumped over",
+                    "the lazy dog.",
+                };
+                SkPaint textPaint;
+                textPaint.setAntiAlias(true);
+                textPaint.setTextSize(SkIntToScalar(100));
+                int posY = 0;
+                for (unsigned i = 0; i < SK_ARRAY_COUNT(str); i++) {
+                    posY += 100;
+                    canvas->drawText(str[i], strlen(str[i]), SkIntToScalar(0),
+                                     SkIntToScalar(posY), textPaint);
+                }
+                canvas->restore();
+                canvas->restore();
+            }
+        }
+    }
+
+private:
+    SkScalar fSigmaX;
+    SkScalar fSigmaY;
+
+    typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static GM* MyFactory1(void*) { return new ImageBlurTiledGM(3.0f, 3.0f); }
+static GMRegistry reg1(MyFactory1);
+
+}
diff --git a/gm/imagefiltersclipped.cpp b/gm/imagefiltersclipped.cpp
new file mode 100644
index 0000000..514fc20
--- /dev/null
+++ b/gm/imagefiltersclipped.cpp
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkColor.h"
+#include "SkBitmapSource.h"
+#include "SkBlurImageFilter.h"
+#include "SkDisplacementMapEffect.h"
+#include "SkDropShadowImageFilter.h"
+#include "SkGradientShader.h"
+#include "SkMorphologyImageFilter.h"
+#include "SkOffsetImageFilter.h"
+#include "SkScalar.h"
+
+namespace skiagm {
+
+class ImageFiltersClippedGM : public GM {
+public:
+    ImageFiltersClippedGM() : fInitialized(false) {
+        this->setBGColor(0x00000000);
+    }
+
+protected:
+    virtual SkString onShortName() {
+        return SkString("imagefiltersclipped");
+    }
+
+    virtual SkISize onISize() {
+        return make_isize(860, 500);
+    }
+
+    void make_checkerboard() {
+        fCheckerboard.allocN32Pixels(64, 64);
+        SkBitmapDevice device(fCheckerboard);
+        SkCanvas canvas(&device);
+        canvas.clear(0x00000000);
+        SkPaint darkPaint;
+        darkPaint.setColor(0xFF404040);
+        SkPaint lightPaint;
+        lightPaint.setColor(0xFFA0A0A0);
+        for (int y = 0; y < 64; y += 16) {
+          for (int x = 0; x < 64; x += 16) {
+            canvas.save();
+            canvas.translate(SkIntToScalar(x), SkIntToScalar(y));
+            canvas.drawRect(SkRect::MakeXYWH(0, 0, 8, 8), darkPaint);
+            canvas.drawRect(SkRect::MakeXYWH(8, 0, 8, 8), lightPaint);
+            canvas.drawRect(SkRect::MakeXYWH(0, 8, 8, 8), lightPaint);
+            canvas.drawRect(SkRect::MakeXYWH(8, 8, 8, 8), darkPaint);
+            canvas.restore();
+          }
+        }
+    }
+
+    void make_gradient_circle(int width, int height) {
+        SkScalar x = SkIntToScalar(width / 2);
+        SkScalar y = SkIntToScalar(height / 2);
+        SkScalar radius = SkScalarMul(SkMinScalar(x, y), SkIntToScalar(4) / SkIntToScalar(5));
+        fGradientCircle.allocN32Pixels(width, height);
+        SkBitmapDevice device(fGradientCircle);
+        SkCanvas canvas(&device);
+        canvas.clear(0x00000000);
+        SkColor colors[2];
+        colors[0] = SK_ColorWHITE;
+        colors[1] = SK_ColorBLACK;
+        SkAutoTUnref<SkShader> shader(
+            SkGradientShader::CreateRadial(SkPoint::Make(x, y), radius, colors, NULL, 2,
+                                           SkShader::kClamp_TileMode)
+        );
+        SkPaint paint;
+        paint.setShader(shader);
+        canvas.drawCircle(x, y, radius, paint);
+    }
+
+    virtual void onDraw(SkCanvas* canvas) {
+        if (!fInitialized) {
+            this->make_checkerboard();
+            this->make_gradient_circle(64, 64);
+            fInitialized = true;
+        }
+        canvas->clear(0x00000000);
+
+        SkAutoTUnref<SkImageFilter> gradient(new SkBitmapSource(fGradientCircle));
+        SkAutoTUnref<SkImageFilter> checkerboard(new SkBitmapSource(fCheckerboard));
+
+        SkImageFilter* filters[] = {
+            new SkBlurImageFilter(SkIntToScalar(12), SkIntToScalar(12)),
+            new SkDropShadowImageFilter(SkIntToScalar(10), SkIntToScalar(10), SkIntToScalar(3),
+                                        SK_ColorGREEN),
+            new SkDisplacementMapEffect(SkDisplacementMapEffect::kR_ChannelSelectorType,
+                                        SkDisplacementMapEffect::kR_ChannelSelectorType,
+                                        SkIntToScalar(12),
+                                        gradient.get(),
+                                        checkerboard.get()),
+            new SkDilateImageFilter(2, 2, checkerboard.get()),
+            new SkErodeImageFilter(2, 2, checkerboard.get()),
+            new SkOffsetImageFilter(SkIntToScalar(-16), SkIntToScalar(32)),
+        };
+
+        SkRect r = SkRect::MakeWH(SkIntToScalar(64), SkIntToScalar(64));
+        SkScalar margin = SkIntToScalar(16);
+        SkRect bounds = r;
+        bounds.outset(margin, margin);
+
+        for (int xOffset = 0; xOffset < 80; xOffset += 16) {
+            canvas->save();
+            bounds.fLeft = SkIntToScalar(xOffset);
+            for (size_t i = 0; i < SK_ARRAY_COUNT(filters); ++i) {
+                SkPaint paint;
+                paint.setColor(SK_ColorWHITE);
+                paint.setImageFilter(filters[i]);
+                paint.setAntiAlias(true);
+                canvas->save();
+                canvas->clipRect(bounds);
+                if (i == 5) {
+                    canvas->translate(SkIntToScalar(16), SkIntToScalar(-32));
+                }
+                canvas->drawCircle(r.centerX(), r.centerY(),
+                                   SkScalarDiv(r.width()*2, SkIntToScalar(5)), paint);
+                canvas->restore();
+                canvas->translate(r.width() + margin, 0);
+            }
+            canvas->restore();
+            canvas->translate(0, r.height() + margin);
+        }
+    }
+
+private:
+    bool fInitialized;
+    SkBitmap fCheckerboard;
+    SkBitmap fGradientCircle;
+    typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static GM* MyFactory(void*) { return new ImageFiltersClippedGM; }
+static GMRegistry reg(MyFactory);
+
+}
diff --git a/gm/imagefiltersscaled.cpp b/gm/imagefiltersscaled.cpp
index ad52d90..a9c4a10 100644
--- a/gm/imagefiltersscaled.cpp
+++ b/gm/imagefiltersscaled.cpp
@@ -13,6 +13,7 @@
 #include "SkDropShadowImageFilter.h"
 #include "SkGradientShader.h"
 #include "SkMorphologyImageFilter.h"
+#include "SkOffsetImageFilter.h"
 #include "SkScalar.h"
 
 namespace skiagm {
@@ -96,6 +97,7 @@
                                         checkerboard.get()),
             new SkDilateImageFilter(1, 1, checkerboard.get()),
             new SkErodeImageFilter(1, 1, checkerboard.get()),
+            new SkOffsetImageFilter(SkIntToScalar(32), 0),
         };
 
         SkVector scales[] = {
@@ -120,7 +122,9 @@
                 paint.setAntiAlias(true);
                 canvas->save();
                 canvas->scale(scales[j].fX, scales[j].fY);
-                canvas->clipRect(bounds);
+                if (5 == i) {
+                    canvas->translate(SkIntToScalar(-32), 0);
+                }
                 canvas->drawCircle(r.centerX(), r.centerY(),
                                    SkScalarDiv(r.width()*2, SkIntToScalar(5)), paint);
                 canvas->restore();
diff --git a/gyp/gmslides.gypi b/gyp/gmslides.gypi
index c6c11e3..30fd5e6 100644
--- a/gyp/gmslides.gypi
+++ b/gyp/gmslides.gypi
@@ -88,6 +88,7 @@
     '../gm/hittestpath.cpp',
     '../gm/imagealphathreshold.cpp',
     '../gm/imageblur.cpp',
+    '../gm/imageblurtiled.cpp',
     '../gm/imagemagnifier.cpp',
     '../gm/inversepaths.cpp',
     '../gm/lerpmode.cpp',
@@ -95,6 +96,7 @@
     '../gm/lumafilter.cpp',
     '../gm/image.cpp',
     '../gm/imagefiltersbase.cpp',
+    '../gm/imagefiltersclipped.cpp',
     '../gm/imagefilterscropped.cpp',
     '../gm/imagefiltersgraph.cpp',
     '../gm/imagefiltersscaled.cpp',
diff --git a/include/core/SkCanvas.h b/include/core/SkCanvas.h
index af42c12..e6c9ee2 100644
--- a/include/core/SkCanvas.h
+++ b/include/core/SkCanvas.h
@@ -1039,8 +1039,11 @@
 
     // Clip rectangle bounds. Called internally by saveLayer.
     // returns false if the entire rectangle is entirely clipped out
+    // If non-NULL, The imageFilter parameter will be used to expand the clip
+    // and offscreen bounds for any margin required by the filter DAG.
     bool clipRectBounds(const SkRect* bounds, SaveFlags flags,
-                        SkIRect* intersection);
+                        SkIRect* intersection,
+                        const SkImageFilter* imageFilter = NULL);
 
     // Called by child classes that override clipPath and clipRRect to only
     // track fast conservative clip bounds, rather than exact clips.
diff --git a/include/core/SkImageFilter.h b/include/core/SkImageFilter.h
index 37993c0..6b53987 100644
--- a/include/core/SkImageFilter.h
+++ b/include/core/SkImageFilter.h
@@ -83,7 +83,7 @@
      *  Given the src bounds of an image, this returns the bounds of the result
      *  image after the filter has been applied.
      */
-    bool filterBounds(const SkIRect& src, const SkMatrix& ctm, SkIRect* dst);
+    bool filterBounds(const SkIRect& src, const SkMatrix& ctm, SkIRect* dst) const;
 
     /**
      *  Returns true if the filter can be processed on the GPU.  This is most
@@ -188,8 +188,14 @@
      */
     virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
                                SkBitmap* result, SkIPoint* offset);
-    // Default impl copies src into dst and returns true
-    virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*);
+    // Given the bounds of the destination rect to be filled in device
+    // coordinates (first parameter), and the CTM, compute (conservatively)
+    // which rect of the source image would be required (third parameter).
+    // Used for clipping and temp-buffer allocations, so the result need not
+    // be exact, but should never be smaller than the real answer. The default
+    // implementation recursively unions all input bounds, or returns false if
+    // no inputs.
+    virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) const;
 
     // Applies "matrix" to the crop rect, and sets "rect" to the intersection of
     // "rect" and the transformed crop rect. If there is no overlap, returns
diff --git a/include/effects/SkBitmapSource.h b/include/effects/SkBitmapSource.h
index e0e241b..fa6dafc 100644
--- a/include/effects/SkBitmapSource.h
+++ b/include/effects/SkBitmapSource.h
@@ -24,6 +24,7 @@
     virtual void flatten(SkWriteBuffer&) const SK_OVERRIDE;
     virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
                                SkBitmap* result, SkIPoint* offset) SK_OVERRIDE;
+    virtual bool onFilterBounds(const SkIRect& src, const SkMatrix& ctm, SkIRect* dst) const SK_OVERRIDE;
 
 private:
     SkBitmap fBitmap;
diff --git a/include/effects/SkBlurImageFilter.h b/include/effects/SkBlurImageFilter.h
index 1d51f9a..f352691 100644
--- a/include/effects/SkBlurImageFilter.h
+++ b/include/effects/SkBlurImageFilter.h
@@ -27,6 +27,8 @@
 
     virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
                                SkBitmap* result, SkIPoint* offset) SK_OVERRIDE;
+    virtual bool onFilterBounds(const SkIRect& src, const SkMatrix&,
+                                SkIRect* dst) const SK_OVERRIDE;
 
     bool canFilterImageGPU() const SK_OVERRIDE { return true; }
     virtual bool filterImageGPU(Proxy* proxy, const SkBitmap& src, const SkMatrix& ctm,
diff --git a/include/effects/SkComposeImageFilter.h b/include/effects/SkComposeImageFilter.h
index 744647d..9cf7465 100644
--- a/include/effects/SkComposeImageFilter.h
+++ b/include/effects/SkComposeImageFilter.h
@@ -22,7 +22,7 @@
 
     virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
                                SkBitmap* result, SkIPoint* loc) SK_OVERRIDE;
-    virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) SK_OVERRIDE;
+    virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) const SK_OVERRIDE;
 
 private:
     typedef SkImageFilter INHERITED;
diff --git a/include/effects/SkDisplacementMapEffect.h b/include/effects/SkDisplacementMapEffect.h
index ba7e259..de07fe4 100644
--- a/include/effects/SkDisplacementMapEffect.h
+++ b/include/effects/SkDisplacementMapEffect.h
@@ -39,6 +39,9 @@
                                SkIPoint* offset) SK_OVERRIDE;
     virtual void computeFastBounds(const SkRect& src, SkRect* dst) const SK_OVERRIDE;
 
+    virtual bool onFilterBounds(const SkIRect& src, const SkMatrix&,
+                                SkIRect* dst) const SK_OVERRIDE;
+
 #if SK_SUPPORT_GPU
     virtual bool canFilterImageGPU() const SK_OVERRIDE { return true; }
     virtual bool filterImageGPU(Proxy* proxy, const SkBitmap& src, const SkMatrix& ctm,
diff --git a/include/effects/SkDropShadowImageFilter.h b/include/effects/SkDropShadowImageFilter.h
index 14a7893..cfcad8c 100644
--- a/include/effects/SkDropShadowImageFilter.h
+++ b/include/effects/SkDropShadowImageFilter.h
@@ -20,6 +20,10 @@
     explicit SkDropShadowImageFilter(SkReadBuffer&);
     virtual void flatten(SkWriteBuffer&) const SK_OVERRIDE;
     virtual bool onFilterImage(Proxy*, const SkBitmap& source, const SkMatrix&, SkBitmap* result, SkIPoint* loc) SK_OVERRIDE;
+    virtual bool onFilterBounds(const SkIRect& src, const SkMatrix&,
+                                SkIRect* dst) const SK_OVERRIDE;
+
+
 
 private:
     SkScalar fDx, fDy, fSigmaX, fSigmaY;
diff --git a/include/effects/SkMergeImageFilter.h b/include/effects/SkMergeImageFilter.h
index bd9ab6c..74cf561 100755
--- a/include/effects/SkMergeImageFilter.h
+++ b/include/effects/SkMergeImageFilter.h
@@ -30,8 +30,6 @@
 
     virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
                                SkBitmap* result, SkIPoint* loc) SK_OVERRIDE;
-    virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) SK_OVERRIDE;
-
 private:
     uint8_t*            fModes; // SkXfermode::Mode
 
diff --git a/include/effects/SkMorphologyImageFilter.h b/include/effects/SkMorphologyImageFilter.h
index 0335908..c2b0d13 100644
--- a/include/effects/SkMorphologyImageFilter.h
+++ b/include/effects/SkMorphologyImageFilter.h
@@ -17,6 +17,7 @@
 public:
     SkMorphologyImageFilter(int radiusX, int radiusY, SkImageFilter* input, const CropRect* cropRect);
     virtual void computeFastBounds(const SkRect& src, SkRect* dst) const SK_OVERRIDE;
+    virtual bool onFilterBounds(const SkIRect& src, const SkMatrix& ctm, SkIRect* dst) const SK_OVERRIDE;
 
     /**
      * All morphology procs have the same signature: src is the source buffer, dst the
diff --git a/include/effects/SkOffsetImageFilter.h b/include/effects/SkOffsetImageFilter.h
index 9237111..aef158c 100644
--- a/include/effects/SkOffsetImageFilter.h
+++ b/include/effects/SkOffsetImageFilter.h
@@ -26,7 +26,7 @@
 
     virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
                                SkBitmap* result, SkIPoint* loc) SK_OVERRIDE;
-    virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) SK_OVERRIDE;
+    virtual bool onFilterBounds(const SkIRect&, const SkMatrix&, SkIRect*) const SK_OVERRIDE;
 
 private:
     SkVector fOffset;
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index 6f4e88d..a14bb63 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -791,11 +791,18 @@
 }
 
 bool SkCanvas::clipRectBounds(const SkRect* bounds, SaveFlags flags,
-                               SkIRect* intersection) {
+                               SkIRect* intersection, const SkImageFilter* imageFilter) {
     SkIRect clipBounds;
+    SkRegion::Op op = SkRegion::kIntersect_Op;
     if (!this->getClipDeviceBounds(&clipBounds)) {
         return false;
     }
+
+    if (imageFilter) {
+        imageFilter->filterBounds(clipBounds, *fMCRec->fMatrix, &clipBounds);
+        // Filters may grow the bounds beyond the device bounds.
+        op = SkRegion::kReplace_Op;
+    }
     SkIRect ir;
     if (NULL != bounds) {
         SkRect r;
@@ -813,11 +820,11 @@
         ir = clipBounds;
     }
 
-    fClipStack.clipDevRect(ir, SkRegion::kIntersect_Op);
+    fClipStack.clipDevRect(ir, op);
 
     // early exit if the clip is now empty
     if (bounds_affects_clip(flags) &&
-        !fMCRec->fRasterClip->op(ir, SkRegion::kIntersect_Op)) {
+        !fMCRec->fRasterClip->op(ir, op)) {
         return false;
     }
 
@@ -861,7 +868,7 @@
     fDeviceCMDirty = true;
 
     SkIRect ir;
-    if (!this->clipRectBounds(bounds, flags, &ir)) {
+    if (!this->clipRectBounds(bounds, flags, &ir, paint ? paint->getImageFilter() : NULL)) {
         return count;
     }
 
diff --git a/src/core/SkImageFilter.cpp b/src/core/SkImageFilter.cpp
index 9b7e5e4..384f2dc 100644
--- a/src/core/SkImageFilter.cpp
+++ b/src/core/SkImageFilter.cpp
@@ -106,7 +106,7 @@
 }
 
 bool SkImageFilter::filterBounds(const SkIRect& src, const SkMatrix& ctm,
-                                 SkIRect* dst) {
+                                 SkIRect* dst) const {
     SkASSERT(&src);
     SkASSERT(dst);
     return this->onFilterBounds(src, ctm, dst);
@@ -210,8 +210,28 @@
 }
 
 bool SkImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
-                                   SkIRect* dst) {
-    *dst = src;
+                                   SkIRect* dst) const {
+    if (fInputCount < 1) {
+        return false;
+    }
+
+    SkIRect bounds;
+    for (int i = 0; i < fInputCount; ++i) {
+        SkImageFilter* filter = this->getInput(i);
+        SkIRect rect = src;
+        if (filter && !filter->filterBounds(src, ctm, &rect)) {
+            return false;
+        }
+        if (0 == i) {
+            bounds = rect;
+        } else {
+            bounds.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;
     return true;
 }
 
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;
 }