Set empty device clip when an image filter has zero bounds

When an image filter has zero input bounds, we should not paint any
contents of it, so we should set an empty clip. This is like the
case that the clip rect doesn't intersect with the image filter's
input bounds.

Bug: chromium:771643
Change-Id: I063c14128dacb83e3572bd2ef4dfeee93c871064
Reviewed-on: https://skia-review.googlesource.com/96943
Reviewed-by: Mike Reed <reed@google.com>
Reviewed-by: Stephen White <senorblanco@chromium.org>
Commit-Queue: Mike Reed <reed@google.com>
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index 0f079aa..7fbb952 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -953,9 +953,6 @@
 
     if (imageFilter) {
         clipBounds = imageFilter->filterBounds(clipBounds, ctm);
-        if (clipBounds.isEmpty()) {
-            return false;
-        }
         if (bounds && !imageFilter->canComputeFastBounds()) {
             bounds = nullptr;
         }
@@ -963,21 +960,21 @@
     SkIRect ir;
     if (bounds) {
         SkRect r;
-
         ctm.mapRect(&r, *bounds);
         r.roundOut(&ir);
-        // early exit if the layer's bounds are clipped out
-        if (!ir.intersect(clipBounds)) {
-            if (BoundsAffectsClip(saveLayerFlags)) {
-                fMCRec->fTopLayer->fDevice->clipRegion(SkRegion(), SkClipOp::kIntersect); // empty
-                fMCRec->fRasterClip.setEmpty();
-                fDeviceClipBounds.setEmpty();
-            }
-            return false;
-        }
     } else {    // no user bounds, so just use the clip
         ir = clipBounds;
     }
+
+    // early exit if the layer's bounds are clipped out
+    if (!ir.intersect(clipBounds)) {
+        if (BoundsAffectsClip(saveLayerFlags)) {
+            fMCRec->fTopLayer->fDevice->clipRegion(SkRegion(), SkClipOp::kIntersect); // empty
+            fMCRec->fRasterClip.setEmpty();
+            fDeviceClipBounds.setEmpty();
+        }
+        return false;
+    }
     SkASSERT(!ir.isEmpty());
 
     if (BoundsAffectsClip(saveLayerFlags)) {
diff --git a/tests/CanvasTest.cpp b/tests/CanvasTest.cpp
index 9b1e0c9..e83777b 100644
--- a/tests/CanvasTest.cpp
+++ b/tests/CanvasTest.cpp
@@ -61,10 +61,11 @@
 #include "SkRect.h"
 #include "SkRegion.h"
 #include "SkShader.h"
+#include "SkSpecialImage.h"
 #include "SkStream.h"
 #include "SkSurface.h"
-#include "SkTemplates.h"
 #include "SkTDArray.h"
+#include "SkTemplates.h"
 #include "SkVertices.h"
 #include "Test.h"
 
@@ -812,3 +813,50 @@
     REPORTER_ASSERT(r, SK_ColorRED == SkSwizzle_BGRA_to_PMColor(*bitmap.getAddr32(0, 0)));
 }
 #endif
+
+namespace {
+
+class ZeroBoundsImageFilter : public SkImageFilter {
+public:
+    static sk_sp<SkImageFilter> Make() { return sk_sp<SkImageFilter>(new ZeroBoundsImageFilter); }
+
+    SK_TO_STRING_OVERRIDE()
+    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(ZeroBoundsImageFilter)
+
+protected:
+    sk_sp<SkSpecialImage> onFilterImage(SkSpecialImage*, const Context&, SkIPoint*) const override {
+        return nullptr;
+    }
+    sk_sp<SkImageFilter> onMakeColorSpace(SkColorSpaceXformer*) const override { return nullptr; }
+    SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix&, MapDirection) const override {
+        return SkIRect::MakeEmpty();
+    }
+
+private:
+    ZeroBoundsImageFilter() : INHERITED(nullptr, 0, nullptr) {}
+
+    typedef SkImageFilter INHERITED;
+};
+
+sk_sp<SkFlattenable> ZeroBoundsImageFilter::CreateProc(SkReadBuffer& buffer) {
+    SkDEBUGFAIL("Should never get here");
+    return nullptr;
+}
+
+#ifndef SK_IGNORE_TO_STRING
+void ZeroBoundsImageFilter::toString(SkString* str) const {
+    str->appendf("ZeroBoundsImageFilter: ()");
+}
+#endif
+
+}  // anonymous namespace
+
+DEF_TEST(Canvas_SaveLayerWithNullBoundsAndZeroBoundsImageFilter, r) {
+    SkCanvas canvas(10, 10);
+    SkPaint p;
+    p.setImageFilter(ZeroBoundsImageFilter::Make());
+    // This should not fail any assert.
+    canvas.saveLayer(nullptr, &p);
+    REPORTER_ASSERT(r, canvas.getDeviceClipBounds().isEmpty());
+    canvas.restore();
+}