Add new GM (filterfastbounds)

This new GM visualizes the fast bounds computed by various image-filter-based SkPaints. This is lead up to fixing some issues in fast bound computation.

BUG=418417

Review URL: https://codereview.chromium.org/788613003
diff --git a/gm/filterfastbounds.cpp b/gm/filterfastbounds.cpp
new file mode 100644
index 0000000..6d4d3f1
--- /dev/null
+++ b/gm/filterfastbounds.cpp
@@ -0,0 +1,336 @@
+/*
+ * 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 "SkBitmapSource.h"
+#include "SkBlurImageFilter.h"
+#include "SkDropShadowImageFilter.h"
+#include "SkMatrixImageFilter.h"
+#include "SkOffsetImageFilter.h"
+#include "SkPictureImageFilter.h"
+#include "SkPictureRecorder.h"
+#include "SkRandom.h"
+
+namespace skiagm {
+
+// Each method of this type must draw its geometry inside 'r' using 'p'
+typedef void(*drawMth)(SkCanvas* canvas, const SkRect& r, const SkPaint& p);
+
+static void draw_rect(SkCanvas* canvas, const SkRect& r, const SkPaint& p) {
+    canvas->drawRect(r, p);
+}
+
+static void draw_oval(SkCanvas* canvas, const SkRect& r, const SkPaint& p) {
+    canvas->drawOval(r, p);
+}
+
+static void draw_rrect(SkCanvas* canvas, const SkRect& r, const SkPaint& p) {
+    SkScalar xRad = r.width() / 4.0f;
+    SkScalar yRad = r.height() / 4.0f;
+
+    SkRRect rr;
+    rr.setRectXY(r, xRad, yRad);
+    canvas->drawRRect(rr, p);
+}
+
+static void draw_drrect(SkCanvas* canvas, const SkRect& r, const SkPaint& p) {
+    SkScalar xRad = r.width() / 4.0f;
+    SkScalar yRad = r.height() / 4.0f;
+
+    SkRRect outer;
+    outer.setRectXY(r, xRad, yRad);
+    SkRRect inner = outer;
+    inner.inset(xRad, yRad);
+    canvas->drawDRRect(outer, inner, p);
+}
+
+static void draw_path(SkCanvas* canvas, const SkRect& r, const SkPaint& p) {
+    SkPath path;
+
+    path.moveTo(r.fLeft, r.fTop);
+    path.lineTo(r.fLeft, r.fBottom);
+    path.lineTo(r.fRight, r.fBottom);
+    path.close();
+
+    canvas->drawPath(path, p);
+}
+
+static void draw_points(SkCanvas* canvas, const SkRect& r, const SkPaint& p) {
+    SkPoint pts0[2] = { { r.fLeft, r.fTop }, { r.fRight, r.fBottom } };
+    SkPoint pts1[2] = { { r.fLeft, r.fBottom }, { r.fRight, r.fTop } };
+
+    canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts0, p);
+    canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts1, p);
+}
+
+static void draw_bitmap(SkCanvas* canvas, const SkRect& r, const SkPaint& p) {
+    SkBitmap bm;
+
+    bm.allocN32Pixels(64, 64);
+    SkCanvas temp(bm);
+    temp.clear(SK_ColorMAGENTA);
+
+    canvas->drawBitmapRect(bm, r, &p);
+}
+
+static const drawMth gDrawMthds[] = {
+    draw_rect, draw_oval, draw_rrect, draw_drrect, draw_path, draw_points, draw_bitmap
+};
+
+static void add_paint(SkImageFilter* filter, SkTArray<SkPaint>* paints) {
+    SkPaint& p = paints->push_back();
+    p.setImageFilter(filter);
+    SkASSERT(p.canComputeFastBounds());
+}
+
+// Create a selection of imagefilter-based paints to test
+static void create_paints(SkImageFilter* source, SkTArray<SkPaint>* paints) {
+    {
+        SkMatrix scale;
+        scale.setScale(2.0f, 2.0f);
+
+        SkAutoTUnref<SkMatrixImageFilter> scaleMIF(
+            SkMatrixImageFilter::Create(scale, SkPaint::kLow_FilterLevel, source));
+
+        add_paint(scaleMIF, paints);
+    }
+
+    {
+        SkMatrix rot;
+        rot.setRotate(-33.3f);
+
+        SkAutoTUnref<SkMatrixImageFilter> rotMIF(
+            SkMatrixImageFilter::Create(rot, SkPaint::kLow_FilterLevel, source));
+
+        add_paint(rotMIF, paints);
+    }
+
+    {
+        static const SkDropShadowImageFilter::ShadowMode kBoth =
+                    SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode;
+
+        SkAutoTUnref<SkDropShadowImageFilter> dsif(
+            SkDropShadowImageFilter::Create(10.0f, 10.0f,
+                                            3.0f, 3.0f,
+                                            SK_ColorRED, kBoth,
+                                            source, NULL, 0));
+
+        add_paint(dsif, paints);
+    }
+
+    {
+        SkAutoTUnref<SkDropShadowImageFilter> dsif(
+            SkDropShadowImageFilter::Create(27.0f, 27.0f,
+                                            3.0f, 3.0f,
+                                            SK_ColorRED,
+                                            SkDropShadowImageFilter::kDrawShadowOnly_ShadowMode,
+                                            source, NULL, 0));
+
+        add_paint(dsif, paints);
+    }
+
+    {
+        SkAutoTUnref<SkBlurImageFilter> bif(SkBlurImageFilter::Create(3, 3, source));
+
+        add_paint(bif, paints);
+    }
+
+    {
+        SkAutoTUnref<SkOffsetImageFilter> oif(SkOffsetImageFilter::Create(15, 15, source));
+
+        add_paint(oif, paints);
+    }
+}
+
+// This GM visualizes the fast bounds for various combinations of geometry
+// and image filter
+class ImageFilterFastBoundGM : public GM {
+public:
+    ImageFilterFastBoundGM() {
+        this->setBGColor(0xFFCCCCCC);
+    }
+
+protected:
+    static const int kTileWidth = 100;
+    static const int kTileHeight = 100;
+    static const int kNumVertTiles = 6;
+    static const int kNumXtraCols = 2;
+
+    // SkPictureImageFilter doesn't support serialization yet.
+    uint32_t onGetFlags() const SK_OVERRIDE {
+        return kSkipPicture_Flag |
+               kSkipPipe_Flag |
+               kSkipPipeCrossProcess_Flag |
+               kSkipTiled_Flag |
+               kSkipScaledReplay_Flag;
+    }
+
+    SkString onShortName() SK_OVERRIDE{ return SkString("filterfastbounds"); }
+
+    SkISize onISize() SK_OVERRIDE{
+        return SkISize::Make((SK_ARRAY_COUNT(gDrawMthds) + kNumXtraCols) * kTileWidth,
+                             kNumVertTiles * kTileHeight);
+    }
+
+    static void draw_geom_with_paint(drawMth draw, const SkIPoint& off,
+                                     SkCanvas* canvas, const SkPaint& p) {
+        SkPaint redStroked;
+        redStroked.setColor(SK_ColorRED);
+        redStroked.setStyle(SkPaint::kStroke_Style);
+
+        SkPaint blueStroked;
+        blueStroked.setColor(SK_ColorBLUE);
+        blueStroked.setStyle(SkPaint::kStroke_Style);
+
+        const SkRect r = SkRect::MakeLTRB(20, 20, 30, 30);
+        SkRect storage;
+
+        canvas->save();
+            canvas->translate(SkIntToScalar(off.fX), SkIntToScalar(off.fY));
+            canvas->scale(1.5f, 1.5f);
+
+            const SkRect& fastBound = p.computeFastBounds(r, &storage);
+
+            canvas->save();
+                canvas->clipRect(fastBound);
+                (*draw)(canvas, r, p);
+            canvas->restore();
+
+            canvas->drawRect(r, redStroked);
+            canvas->drawRect(fastBound, blueStroked);
+        canvas->restore();
+    }
+
+    static void draw_savelayer_with_paint(const SkIPoint& off,
+                                          SkCanvas* canvas,
+                                          const SkPaint& p) {
+        SkPaint redStroked;
+        redStroked.setColor(SK_ColorRED);
+        redStroked.setStyle(SkPaint::kStroke_Style);
+
+        SkPaint blueStroked;
+        blueStroked.setColor(SK_ColorBLUE);
+        blueStroked.setStyle(SkPaint::kStroke_Style);
+
+        const SkRect bounds = SkRect::MakeWH(10, 10);
+        SkRect storage;
+
+        canvas->save();
+            canvas->translate(30, 30);
+            canvas->translate(SkIntToScalar(off.fX), SkIntToScalar(off.fY));
+            canvas->scale(1.5f, 1.5f);
+
+            const SkRect& fastBound = p.computeFastBounds(bounds, &storage);
+
+            canvas->saveLayer(&fastBound, &p);
+            canvas->restore();
+
+            canvas->drawRect(bounds, redStroked);
+            canvas->drawRect(fastBound, blueStroked);
+        canvas->restore();
+    }
+
+    void onDraw(SkCanvas* canvas) SK_OVERRIDE{
+
+        SkPaint blackFill;
+
+        //-----------
+        // Normal paints (no source)
+        SkTArray<SkPaint> paints;
+        create_paints(NULL, &paints);
+
+        //-----------
+        // Paints with a PictureImageFilter as a source
+        SkAutoTUnref<SkPicture> pic;
+
+        {
+            SkPictureRecorder rec;
+
+            SkCanvas* c = rec.beginRecording(10, 10);
+            c->drawRect(SkRect::MakeWH(10, 10), blackFill);
+            pic.reset(rec.endRecording());
+        }
+
+        SkAutoTUnref<SkPictureImageFilter> pif(SkPictureImageFilter::Create(pic));
+
+        SkTArray<SkPaint> pifPaints;
+        create_paints(pif, &pifPaints);
+
+        //-----------
+        // Paints with a BitmapSource as a source
+        SkBitmap bm;
+
+        {
+            SkPaint p;
+            bm.allocN32Pixels(10, 10);
+            SkCanvas temp(bm);
+            temp.clear(SK_ColorYELLOW);
+            p.setColor(SK_ColorBLUE);
+            temp.drawRect(SkRect::MakeLTRB(5, 5, 10, 10), p);
+            p.setColor(SK_ColorGREEN);
+            temp.drawRect(SkRect::MakeLTRB(5, 0, 10, 5), p);
+        }
+
+        SkAutoTUnref<SkBitmapSource> bms(SkBitmapSource::Create(bm));
+
+        SkTArray<SkPaint> bmsPaints;
+        create_paints(bms, &bmsPaints);
+
+        //-----------
+        SkASSERT(paints.count() == kNumVertTiles);
+        SkASSERT(paints.count() == pifPaints.count());
+        SkASSERT(paints.count() == bmsPaints.count());
+
+        // horizontal separators
+        for (int i = 1; i < paints.count(); ++i) {
+            canvas->drawLine(0,
+                             i*SkIntToScalar(kTileHeight),
+                             SkIntToScalar((SK_ARRAY_COUNT(gDrawMthds) + kNumXtraCols)*kTileWidth),
+                             i*SkIntToScalar(kTileHeight),
+                             blackFill);
+        }
+        // vertical separators
+        for (int i = 0; i < (int)SK_ARRAY_COUNT(gDrawMthds) + kNumXtraCols; ++i) {
+            canvas->drawLine(SkIntToScalar(i * kTileWidth),
+                             0,
+                             SkIntToScalar(i * kTileWidth), 
+                             SkIntToScalar(paints.count() * kTileWidth),
+                             blackFill);
+        }
+
+        // A column of saveLayers with PictureImageFilters
+        for (int i = 0; i < pifPaints.count(); ++i) {
+            draw_savelayer_with_paint(SkIPoint::Make(0, i*kTileHeight), 
+                                      canvas, pifPaints[i]);
+        }
+
+        // A column of saveLayers with BitmapSources
+        for (int i = 0; i < pifPaints.count(); ++i) {
+            draw_savelayer_with_paint(SkIPoint::Make(kTileWidth, i*kTileHeight),
+                                      canvas, bmsPaints[i]);
+        }
+
+        // Multiple columns with different geometry
+        for (int i = 0; i < (int)SK_ARRAY_COUNT(gDrawMthds); ++i) {
+            for (int j = 0; j < paints.count(); ++j) {
+                draw_geom_with_paint(*gDrawMthds[i],
+                                     SkIPoint::Make((i+kNumXtraCols) * kTileWidth, j*kTileHeight),
+                                     canvas, paints[j]);
+            }
+        }
+
+    }
+
+private:
+    typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+DEF_GM(return SkNEW(ImageFilterFastBoundGM);)
+
+}