Fixup rendering of empty paths (including inverted fills)
Add GM and Sample that draw empty paths with various styles and fills

Review URL: http://codereview.appspot.com/5185047/



git-svn-id: http://skia.googlecode.com/svn/trunk@2414 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gm/emptypath.cpp b/gm/emptypath.cpp
new file mode 100644
index 0000000..dd33f68
--- /dev/null
+++ b/gm/emptypath.cpp
@@ -0,0 +1,138 @@
+
+/*
+ * Copyright 2011 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 "SkCanvas.h"
+#include "SkPaint.h"
+#include "SkRandom.h"
+
+namespace skiagm {
+
+class EmptyPathGM : public GM {
+public:
+    EmptyPathGM() {}
+
+protected:
+    SkString onShortName() {
+        return SkString("emptypath");
+    }
+        
+    SkISize onISize() { return make_isize(600, 280); }
+
+    void drawBG(SkCanvas* canvas) {
+        canvas->drawColor(SK_ColorWHITE);
+    }
+
+    void drawEmpty(SkCanvas* canvas,
+                    SkColor color,
+                    const SkRect& clip,
+                    SkPaint::Style style,
+                    SkPath::FillType fill) {
+        SkPath path;
+        path.setFillType(fill);
+        SkPaint paint;
+        paint.setColor(color);
+        paint.setStyle(style);
+        canvas->save();
+        canvas->clipRect(clip);
+        canvas->drawPath(path, paint);
+        canvas->restore();
+    }
+    
+    virtual void onDraw(SkCanvas* canvas) {
+        this->drawBG(canvas);
+        struct FillAndName {
+            SkPath::FillType fFill;
+            const char*      fName;
+        };
+        static const FillAndName gFills[] = {
+            {SkPath::kWinding_FillType, "Winding"},
+            {SkPath::kEvenOdd_FillType, "Even / Odd"},
+            {SkPath::kInverseWinding_FillType, "Inverse Winding"},
+            {SkPath::kInverseEvenOdd_FillType, "Inverse Even / Odd"},
+        };
+        struct StyleAndName {
+            SkPaint::Style fStyle;
+            const char*    fName;
+        };
+        static const StyleAndName gStyles[] = {
+            {SkPaint::kFill_Style, "Fill"},
+            {SkPaint::kStroke_Style, "Stroke"},
+            {SkPaint::kStrokeAndFill_Style, "Stroke And Fill"},
+        };
+
+        SkPaint titlePaint;
+        titlePaint.setColor(SK_ColorBLACK);
+        titlePaint.setAntiAlias(true);
+        titlePaint.setLCDRenderText(true);
+        titlePaint.setTextSize(15 * SK_Scalar1);
+        const char title[] = "Empty Paths Drawn Into Rectangle Clips With "
+                             "Indicated Style and Fill";
+        canvas->drawText(title, strlen(title),
+                            20 * SK_Scalar1,
+                            20 * SK_Scalar1,
+                            titlePaint);
+
+        SkRandom rand;
+        SkRect rect = SkRect::MakeWH(100*SK_Scalar1, 30*SK_Scalar1);
+        int i = 0;
+        canvas->save();
+        canvas->translate(10 * SK_Scalar1, 0);
+        canvas->save();
+        for (int style = 0; style < SK_ARRAY_COUNT(gStyles); ++style) {
+            for (int fill = 0; fill < SK_ARRAY_COUNT(gFills); ++fill) {
+                if (0 == i % 4) {
+                    canvas->restore();
+                    canvas->translate(0, rect.height() + 40 * SK_Scalar1);
+                    canvas->save();
+                } else {
+                    canvas->translate(rect.width() + 40 * SK_Scalar1, 0);
+                }
+                ++i;
+
+
+                SkColor color = rand.nextU();
+                color = 0xff000000| color; // force solid
+                this->drawEmpty(canvas, color, rect,
+                                gStyles[style].fStyle, gFills[fill].fFill);
+
+                SkPaint rectPaint;
+                rectPaint.setColor(SK_ColorBLACK);
+                rectPaint.setStyle(SkPaint::kStroke_Style);
+                rectPaint.setStrokeWidth(-1);
+                rectPaint.setAntiAlias(true);
+                canvas->drawRect(rect, rectPaint);
+
+                SkPaint labelPaint;
+                labelPaint.setColor(color);
+                labelPaint.setAntiAlias(true);
+                labelPaint.setLCDRenderText(true);
+                labelPaint.setTextSize(12 * SK_Scalar1);
+                canvas->drawText(gStyles[style].fName,
+                                 strlen(gStyles[style].fName),
+                                 0, rect.height() + 15 * SK_Scalar1,
+                                 labelPaint);
+                canvas->drawText(gFills[fill].fName,
+                                 strlen(gFills[fill].fName),
+                                 0, rect.height() + 28 * SK_Scalar1,
+                                 labelPaint);
+            }
+        }
+        canvas->restore();
+        canvas->restore();
+    }
+    
+private:
+    typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static GM* MyFactory(void*) { return new EmptyPathGM; }
+static GMRegistry reg(MyFactory);
+
+}
diff --git a/gpu/include/GrTypes.h b/gpu/include/GrTypes.h
index af43c1f..8bbdecf 100644
--- a/gpu/include/GrTypes.h
+++ b/gpu/include/GrTypes.h
@@ -435,7 +435,7 @@
     kPathFillCount
 };
 
-static inline GrPathFill NonInvertedFill(GrPathFill fill) {
+static inline GrPathFill GrNonInvertedFill(GrPathFill fill) {
     static const GrPathFill gNonInvertedFills[] = {
         kWinding_PathFill, // kWinding_PathFill
         kEvenOdd_PathFill, // kEvenOdd_PathFill
@@ -452,7 +452,7 @@
     return gNonInvertedFills[fill];
 }
 
-static inline bool IsFillInverted(GrPathFill fill) {
+static inline bool GrIsFillInverted(GrPathFill fill) {
     static const bool gIsFillInverted[] = {
         false, // kWinding_PathFill
         false, // kEvenOdd_PathFill
diff --git a/gpu/src/GrContext.cpp b/gpu/src/GrContext.cpp
index e0c7b55..d9fb9e8 100644
--- a/gpu/src/GrContext.cpp
+++ b/gpu/src/GrContext.cpp
@@ -1445,6 +1445,16 @@
 void GrContext::drawPath(const GrPaint& paint, const GrPath& path,
                          GrPathFill fill, const GrPoint* translate) {
 
+    if (path.isEmpty()) {
+#if GR_DEBUG
+       GrPrintf("Empty path should have been caught by canvas.\n");
+#endif
+       if (GrIsFillInverted(fill)) {
+           this->drawPaint(paint);
+       }
+       return;
+    }
+
     GrDrawTarget* target = this->prepareToDraw(paint, kUnbuffered_DrawCategory);
 
     // An Assumption here is that path renderer would use some form of tweaking
@@ -1503,7 +1513,7 @@
                 }
             }
             this->cleanupOffscreenAA(target, pr, &record);
-            if (IsFillInverted(fill) && bound != clipIBounds) {
+            if (GrIsFillInverted(fill) && bound != clipIBounds) {
                 GrDrawTarget::AutoDeviceCoordDraw adcd(target, stageMask);
                 GrRect rect;
                 if (clipIBounds.fTop < bound.fTop) {
diff --git a/gpu/src/GrGpu.cpp b/gpu/src/GrGpu.cpp
index 7b98ffa..850fa60 100644
--- a/gpu/src/GrGpu.cpp
+++ b/gpu/src/GrGpu.cpp
@@ -596,8 +596,8 @@
                     fillInverted = false;
                 } else {
                     fill = clip.getPathFill(c);
-                    fillInverted = IsFillInverted(fill);
-                    fill = NonInvertedFill(fill);
+                    fillInverted = GrIsFillInverted(fill);
+                    fill = GrNonInvertedFill(fill);
                     clipPath = &clip.getPath(c);
                     pr = this->getClipPathRenderer(*clipPath, fill);
                     if (NULL == pr) {
diff --git a/gpu/src/GrTesselatedPathRenderer.cpp b/gpu/src/GrTesselatedPathRenderer.cpp
index 5d544f2..3c4bb01 100644
--- a/gpu/src/GrTesselatedPathRenderer.cpp
+++ b/gpu/src/GrTesselatedPathRenderer.cpp
@@ -366,7 +366,7 @@
         }
     }
 
-    bool inverted = IsFillInverted(fFill);
+    bool inverted = GrIsFillInverted(fFill);
     if (inverted) {
         maxPts += 4;
         subpathCnt++;
diff --git a/gyp/SampleApp.gyp b/gyp/SampleApp.gyp
index fd84893..dfd1e4c 100644
--- a/gyp/SampleApp.gyp
+++ b/gyp/SampleApp.gyp
@@ -59,6 +59,7 @@
         '../samplecode/SampleDrawLooper.cpp',
         '../samplecode/SampleEffects.cpp',
         '../samplecode/SampleEmboss.cpp',
+        '../samplecode/SampleEmptyPath.cpp',
         '../samplecode/SampleEncode.cpp',
         '../samplecode/SampleExtractAlpha.cpp',
         '../samplecode/SampleFillType.cpp',
diff --git a/gyp/gm.gyp b/gyp/gm.gyp
index f0aeb64..6226546 100644
--- a/gyp/gm.gyp
+++ b/gyp/gm.gyp
@@ -14,6 +14,7 @@
         '../gm/blurs.cpp',
         '../gm/complexclip.cpp',
         '../gm/complexclip2.cpp',
+        '../gm/emptypath.cpp',
         '../gm/filltypes.cpp',
         '../gm/filltypespersp.cpp',
         '../gm/gmmain.cpp',
diff --git a/include/core/SkCanvas.h b/include/core/SkCanvas.h
index a6c9528..6ca9fa0 100644
--- a/include/core/SkCanvas.h
+++ b/include/core/SkCanvas.h
@@ -854,6 +854,8 @@
                                 const SkRect& dst, const SkPaint* paint);
     void internalDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center,
                                 const SkRect& dst, const SkPaint* paint);
+    void internalDrawPaint(const SkPaint& paint);
+
         
     void drawDevice(SkDevice*, int x, int y, const SkPaint*);
     // shared by save() and saveLayer()
diff --git a/samplecode/SampleEmptyPath.cpp b/samplecode/SampleEmptyPath.cpp
new file mode 100644
index 0000000..5c865c6
--- /dev/null
+++ b/samplecode/SampleEmptyPath.cpp
@@ -0,0 +1,130 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SampleCode.h"
+#include "SkView.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+#include "SkRandom.h"
+
+class EmptyPathView : public SampleView {
+public:
+    EmptyPathView() {}
+
+protected:
+    // overrides from SkEventSink
+    virtual bool onQuery(SkEvent* evt) {
+        if (SampleCode::TitleQ(*evt)) {
+            SampleCode::TitleR(evt, "EmptyPath");
+            return true;
+        }
+        return this->INHERITED::onQuery(evt);
+    }
+    
+    void drawEmpty(SkCanvas* canvas,
+                   SkColor color,
+                   const SkRect& clip,
+                   SkPaint::Style style,
+                   SkPath::FillType fill) {
+        SkPath path;
+        path.setFillType(fill);
+        SkPaint paint;
+        paint.setColor(color);
+        paint.setStyle(style);
+        canvas->save();
+        canvas->clipRect(clip);
+        canvas->drawPath(path, paint);
+        canvas->restore();
+    }
+    
+    virtual void onDrawContent(SkCanvas* canvas) {
+        struct FillAndName {
+            SkPath::FillType fFill;
+            const char*      fName;
+        };
+        static const FillAndName gFills[] = {
+            {SkPath::kWinding_FillType, "Winding"},
+            {SkPath::kEvenOdd_FillType, "Even / Odd"},
+            {SkPath::kInverseWinding_FillType, "Inverse Winding"},
+            {SkPath::kInverseEvenOdd_FillType, "Inverse Even / Odd"},
+        };
+        struct StyleAndName {
+            SkPaint::Style fStyle;
+            const char*    fName;
+        };
+        static const StyleAndName gStyles[] = {
+            {SkPaint::kFill_Style, "Fill"},
+            {SkPaint::kStroke_Style, "Stroke"},
+            {SkPaint::kStrokeAndFill_Style, "Stroke And Fill"},
+        };
+
+        SkPaint titlePaint;
+        titlePaint.setColor(SK_ColorBLACK);
+        titlePaint.setAntiAlias(true);
+        titlePaint.setLCDRenderText(true);
+        titlePaint.setTextSize(24 * SK_Scalar1);
+        const char title[] = "Empty Paths Drawn Into Rectangle Clips With Indicated Style and Fill";
+        canvas->drawText(title, strlen(title),
+                         40 * SK_Scalar1,
+                         100*SK_Scalar1,
+                         titlePaint);
+
+        SkRandom rand;
+        SkRect rect = SkRect::MakeWH(125*SK_Scalar1, 100*SK_Scalar1);
+        int i = 0;
+        canvas->save();
+        canvas->translate(80 * SK_Scalar1, 0);
+        canvas->save();
+        for (int style = 0; style < SK_ARRAY_COUNT(gStyles); ++style) {
+            for (int fill = 0; fill < SK_ARRAY_COUNT(gFills); ++fill) {
+                if (0 == i % 4) {
+                    canvas->restore();
+                    canvas->translate(0, rect.height() + 50 * SK_Scalar1);
+                    canvas->save();
+                } else {
+                    canvas->translate(rect.width() + 100 * SK_Scalar1, 0);
+                }
+                ++i;
+
+
+                SkColor color = rand.nextU();
+                color = 0xff000000| color; // force solid
+                this->drawEmpty(canvas, color, rect,
+                                gStyles[style].fStyle, gFills[fill].fFill);
+
+                SkPaint rectPaint;
+                rectPaint.setColor(SK_ColorBLACK);
+                rectPaint.setStyle(SkPaint::kStroke_Style);
+                rectPaint.setStrokeWidth(-1);
+                rectPaint.setAntiAlias(true);
+                canvas->drawRect(rect, rectPaint);
+
+                char label[1024];
+                sprintf(label, "%s, %s", gStyles[style].fName,
+                                         gFills[fill].fName);
+                SkPaint labelPaint;
+                labelPaint.setColor(color);
+                labelPaint.setAntiAlias(true);
+                labelPaint.setLCDRenderText(true);
+                canvas->drawText(label, strlen(label), 
+                                 0, rect.height() + 15 * SK_Scalar1,
+                                 labelPaint);
+            }
+        }
+        canvas->restore();
+        canvas->restore();
+    }
+    
+private:
+    typedef SampleView INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static SkView* MyFactory() { return new EmptyPathView; }
+static SkViewRegister reg(MyFactory);
+
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index 3ee13bc..70b53fe 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -1217,6 +1217,10 @@
 }
 
 void SkCanvas::drawPaint(const SkPaint& paint) {
+    this->internalDrawPaint(paint);
+}
+
+void SkCanvas::internalDrawPaint(const SkPaint& paint) {
     CHECK_NOTHING_TO_DRAW(paint);
 
     LOOPER_BEGIN(paint, SkDrawFilter::kPaint_Type)
@@ -1269,7 +1273,7 @@
 void SkCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
     CHECK_NOTHING_TO_DRAW(paint);
 
-    if (paint.canComputeFastBounds()) {
+    if (!path.isInverseFillType() && paint.canComputeFastBounds()) {
         SkRect storage;
         const SkRect& bounds = path.getBounds();
         if (this->quickReject(paint.computeFastBounds(bounds, &storage),
@@ -1277,6 +1281,12 @@
             return;
         }
     }
+    if (path.isEmpty()) {
+        if (path.isInverseFillType()) {
+            this->internalDrawPaint(paint);
+        }
+        return;
+    }
 
     LOOPER_BEGIN(paint, SkDrawFilter::kPath_Type)