Add image as a draw type that can be filtered

Add image as a draw type that can be filtered.

This is needed when SkImage is added as an object to be drawn so that
the draw is forwarded to SkBaseDevice. This would be used in making
filters use SkImages.

BUG=skia:3388

Review URL: https://codereview.chromium.org/960783003
diff --git a/include/core/SkDrawFilter.h b/include/core/SkDrawFilter.h
index 52cbba9..26771be 100644
--- a/include/core/SkDrawFilter.h
+++ b/include/core/SkDrawFilter.h
@@ -35,10 +35,11 @@
         kOval_Type,
         kPath_Type,
         kText_Type,
+        kImage_Type,
     };
 
     enum {
-        kTypeCount = kText_Type + 1
+        kTypeCount = kImage_Type + 1
     };
 
     /**
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index a47bd89..c0c6f8a 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -1848,15 +1848,64 @@
     LOOPER_END
 }
 
-void SkCanvas::onDrawImage(const SkImage* image, SkScalar dx, SkScalar dy, const SkPaint* paint) {
+void SkCanvas::onDrawImage(const SkImage* image, SkScalar dx, SkScalar dy,
+                           const SkPaint* paint) {
     TRACE_EVENT0("disabled-by-default-skia", "SkCanvas::drawImage()");
-    image->draw(this, dx, dy, paint);
+
+    SkRect bounds = SkRect::MakeXYWH(dx, dy, image->width(), image->height());
+    if (NULL == paint || paint->canComputeFastBounds()) {
+        if (NULL != paint) {
+            paint->computeFastBounds(bounds, &bounds);
+        }
+        if (this->quickReject(bounds)) {
+            return;
+        }
+    }
+
+    SkLazyPaint lazy;
+    if (NULL == paint) {
+        paint = lazy.init();
+    }
+
+    LOOPER_BEGIN(*paint, SkDrawFilter::kImage_Type, &bounds)
+
+    while (iter.next()) {
+        SkPaint p = looper.paint();
+        p.setLooper(NULL);
+        image->draw(this, dx, dy, &p);
+    }
+
+    LOOPER_END
 }
 
 void SkCanvas::onDrawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst,
                                const SkPaint* paint) {
     TRACE_EVENT0("disabled-by-default-skia", "SkCanvas::drawImageRect()");
-    image->drawRect(this, src, dst, paint);
+    SkRect storage;
+    const SkRect* bounds = &dst;
+    if (NULL == paint || paint->canComputeFastBounds()) {
+        if (NULL != paint) {
+            bounds = &paint->computeFastBounds(dst, &storage);
+        }
+        if (this->quickReject(*bounds)) {
+            return;
+        }
+    }
+
+    SkLazyPaint lazy;
+    if (NULL == paint) {
+        paint = lazy.init();
+    }
+
+    LOOPER_BEGIN(*paint, SkDrawFilter::kImage_Type, bounds)
+
+    while (iter.next()) {
+        SkPaint p = looper.paint();
+        p.setLooper(NULL);
+        image->drawRect(this, src, dst, &p);
+    }
+
+    LOOPER_END
 }
 
 void SkCanvas::onDrawBitmap(const SkBitmap& bitmap, SkScalar x, SkScalar y, const SkPaint* paint) {
diff --git a/tests/SkImageTest.cpp b/tests/SkImageTest.cpp
index 80ba776..f9684d2 100644
--- a/tests/SkImageTest.cpp
+++ b/tests/SkImageTest.cpp
@@ -6,6 +6,8 @@
  */
 
 #include "SkBitmapDevice.h"
+#include "SkColor.h"
+#include "SkDrawFilter.h"
 #include "SkImagePriv.h"
 #include "Test.h"
 
@@ -47,3 +49,145 @@
     canvas.readPixels(info, &pixel, 4, gWidth - 5, gWidth - 5);
     REPORTER_ASSERT(reporter, pixel == SK_ColorTRANSPARENT);
 }
+
+namespace {
+class TestDrawFilterImage : public SkDrawFilter {
+public:
+    TestDrawFilterImage()
+            : fFilteredImage(0)
+            , fFilteredOthers(0)
+            , fPreventImages(true)
+            , fPreventOthers(true) {
+    }
+
+    bool filter(SkPaint*, Type type) SK_OVERRIDE {
+        if (type == SkDrawFilter::kImage_Type) {
+            if (fPreventImages) {
+                fFilteredImage++;
+                return false;
+            }
+            return true;
+        }
+
+        if (fPreventOthers) {
+            fFilteredOthers++;
+            return false;
+        }
+        return true;
+    }
+    int fFilteredImage;
+    int fFilteredOthers;
+    bool fPreventImages;
+    bool fPreventOthers;
+};
+}
+
+DEF_TEST(SkCanvas_test_draw_filter_image, reporter) {
+    SkBitmap bitmap;
+    bitmap.allocN32Pixels(1, 1);
+    bitmap.eraseColor(SK_ColorTRANSPARENT);
+    TestDrawFilterImage drawFilter;
+    SkCanvas canvas(bitmap);
+
+    SkBitmap imageBitmap;
+    imageBitmap.allocN32Pixels(1, 1);
+    imageBitmap.eraseColor(SK_ColorGREEN);
+    SkAutoTUnref<SkImage> image(SkNewImageFromBitmap(imageBitmap, true, NULL));
+    canvas.drawImage(image, 0, 0);
+
+    uint32_t pixel;
+    SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1);
+    canvas.readPixels(info, &pixel, 4, 0, 0);
+    REPORTER_ASSERT(reporter, pixel == SK_ColorGREEN);
+
+    canvas.setDrawFilter(&drawFilter);
+    imageBitmap.eraseColor(SK_ColorRED);
+    image.reset(SkNewImageFromBitmap(imageBitmap, true, NULL));
+    canvas.drawImage(image, 0, 0);
+    canvas.readPixels(info, &pixel, 4, 0, 0);
+    REPORTER_ASSERT(reporter, pixel == SK_ColorGREEN);
+    REPORTER_ASSERT(reporter, drawFilter.fFilteredImage == 1);
+    REPORTER_ASSERT(reporter, drawFilter.fFilteredOthers == 0);
+
+    // Document a strange case: filtering everything but the images does not work as
+    // expected. Images go through, but no pixels appear. (This due to SkCanvas::drawImage() using
+    // SkCanvas::drawBitmap() instead of non-existing SkBaseDevice::drawImage()).
+    drawFilter.fFilteredImage = 0;
+    drawFilter.fPreventImages = false;
+
+    canvas.drawImage(image, 0, 0);
+    canvas.readPixels(info, &pixel, 4, 0, 0);
+    REPORTER_ASSERT(reporter, pixel == SK_ColorGREEN);
+    REPORTER_ASSERT(reporter, drawFilter.fFilteredImage == 0);
+    REPORTER_ASSERT(reporter, drawFilter.fFilteredOthers == 1);
+}
+
+namespace {
+/*
+ *  Subclass of looper that just draws once with one pixel offset.
+ */
+class OnceTestLooper : public SkDrawLooper {
+public:
+    OnceTestLooper() { }
+
+    SkDrawLooper::Context* createContext(SkCanvas*, void* storage) const SK_OVERRIDE {
+        return SkNEW_PLACEMENT(storage, OnceTestLooperContext());
+    }
+
+    size_t contextSize() const SK_OVERRIDE { return sizeof(OnceTestLooperContext); }
+
+#ifndef SK_IGNORE_TO_STRING
+    void toString(SkString* str) const SK_OVERRIDE {
+        str->append("OnceTestLooper:");
+    }
+#endif
+
+    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(OnceTestLooper);
+
+private:
+    class OnceTestLooperContext : public SkDrawLooper::Context {
+    public:
+        OnceTestLooperContext() : fCount(0) {}
+        virtual ~OnceTestLooperContext() {}
+
+        bool next(SkCanvas* canvas, SkPaint*) SK_OVERRIDE {
+            SkASSERT(fCount <= 1);
+            canvas->translate(0, 1);
+            return fCount++ < 1;
+        }
+    private:
+        unsigned fCount;
+    };
+};
+
+SkFlattenable* OnceTestLooper::CreateProc(SkReadBuffer&) { return SkNEW(OnceTestLooper); }
+}
+
+DEF_TEST(SkCanvas_test_draw_looper_image, reporter) {
+    // Test that drawImage loops with the looper the correct way. At the time of writing, this was
+    // tricky because drawImage was implemented with drawBitmap. The drawBitmap uses applies the
+    // looper.
+    SkBitmap bitmap;
+    bitmap.allocN32Pixels(10, 10);
+    bitmap.eraseColor(SK_ColorRED);
+    OnceTestLooper drawLooper;
+    SkCanvas canvas(bitmap);
+
+    SkBitmap imageBitmap;
+    imageBitmap.allocN32Pixels(1, 1);
+    imageBitmap.eraseColor(SK_ColorGREEN);
+    SkAutoTUnref<SkImage> image(SkNewImageFromBitmap(imageBitmap, true, NULL));
+    SkPaint p;
+    p.setLooper(&drawLooper);
+    canvas.drawImage(image, 0, 0, &p);
+
+    uint32_t pixel;
+    SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1);
+    canvas.readPixels(info, &pixel, 4, 0, 1);
+    REPORTER_ASSERT(reporter, pixel == SK_ColorGREEN);
+    canvas.readPixels(info, &pixel, 4, 0, 0);
+    REPORTER_ASSERT(reporter, pixel == SK_ColorRED);
+    canvas.readPixels(info, &pixel, 4, 0, 2);
+    REPORTER_ASSERT(reporter, pixel == SK_ColorRED);
+}
+
diff --git a/tools/bench_pictures_main.cpp b/tools/bench_pictures_main.cpp
index f919812..f6d6dea 100644
--- a/tools/bench_pictures_main.cpp
+++ b/tools/bench_pictures_main.cpp
@@ -75,14 +75,19 @@
     "line",
     "bitmap",
     "rect",
+    "rrect",
     "oval",
     "path",
     "text",
+    "image",
     "all",
 };
 
 static const size_t kFilterTypesCount = sizeof(gFilterTypes) / sizeof(gFilterTypes[0]);
 
+SK_COMPILE_ASSERT(kFilterTypesCount - 1u == SkDrawFilter::kTypeCount,
+                  filter_types_list_is_not_exact);
+
 static char const * const gFilterFlags[] = {
     "antiAlias",
     "filterBitmap",