add Convexity enum to SkPath
authorreed@google.com <reed@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Sun, 15 May 2011 04:08:24 +0000 (04:08 +0000)
committerreed@google.com <reed@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Sun, 15 May 2011 04:08:24 +0000 (04:08 +0000)
git-svn-id: http://skia.googlecode.com/svn/trunk@1324 2bbb7eff-a529-9590-31e7-b0007b416f81

include/core/SkPath.h
samplecode/SampleTests.cpp
src/core/SkPath.cpp
tests/PathTest.cpp

index 2ebc59d..18dcd11 100644 (file)
@@ -96,19 +96,54 @@ public:
         GEN_ID_INC;
      }
 
-    /** Returns true if the path is flagged as being convex. This is not a
-        confirmed by any analysis, it is just the value set earlier.
+    enum Convexity {
+        kUnknown_Convexity,
+        kConvex_Convexity,
+        kConcave_Convexity
+    };
+
+    /**
+     *  Return the path's convexity, as stored in the path.
+     */
+    Convexity getConvexity() const { return (Convexity)fConvexity; }
+
+    /**
+     *  Store a convexity setting in the path. There is no automatic check to
+     *  see if this value actually agress with the return value from
+     *  ComputeConvexity().
+     */
+    void setConvexity(Convexity);
+
+    /**
+     *  Compute the convexity of the specified path. This does not look at the
+     *  value stored in the path, but computes it directly from the path's data.
+     *
+     *  If there is more than one contour, this returns kConcave_Convexity.
+     *  If the contour is degenerate (i.e. all segements are colinear,
+     *  then this returns kUnknown_Convexity.
+     *  The contour is treated as if it were closed, even if there is no kClose
+     *  verb.
      */
-    bool isConvex() const { return fIsConvex != 0; }
+    static Convexity ComputeConvexity(const SkPath&);
 
-    /** Set the isConvex flag to true or false. Convex paths may draw faster if
-        this flag is set, though setting this to true on a path that is in fact
-        not convex can give undefined results when drawn. Paths default to
-        isConvex == false
+    /**
+     *  DEPRECATED: use getConvexity()
+     *  Returns true if the path is flagged as being convex. This is not a
+     *  confirmed by any analysis, it is just the value set earlier.
+     */
+    bool isConvex() const {
+        return kConvex_Convexity == this->getConvexity();
+    }
+
+    /**
+     *  DEPRECATED: use setConvexity()
+     *  Set the isConvex flag to true or false. Convex paths may draw faster if
+     *  this flag is set, though setting this to true on a path that is in fact
+     *  not convex can give undefined results when drawn. Paths default to
+     *  isConvex == false
      */
     void setIsConvex(bool isConvex) {
-        fIsConvex = (isConvex != 0);
-        GEN_ID_INC;
+        this->setConvexity(isConvex ? kConvex_Convexity : kConcave_Convexity);
     }
 
     /** Clear any lines and curves from the path, making it empty. This frees up
@@ -600,7 +635,7 @@ private:
     mutable SkRect      fBounds;
     mutable uint8_t     fBoundsIsDirty;
     uint8_t             fFillType;
-    uint8_t             fIsConvex;
+    uint8_t             fConvexity;
 #ifdef ANDROID
     uint32_t            fGenerationID;
 #endif
index c05189d..a97c622 100644 (file)
@@ -1,32 +1,60 @@
 #include "SampleCode.h"
 #include "SkView.h"
 #include "SkCanvas.h"
-#include "SkBlurMaskFilter.h"
-#include "SkCamera.h"
-#include "SkColorFilter.h"
-#include "SkColorPriv.h"
-#include "SkDevice.h"
-#include "SkGradientShader.h"
-#include "SkImageDecoder.h"
-#include "SkInterpolator.h"
-#include "SkMaskFilter.h"
-#include "SkPath.h"
-#include "SkRegion.h"
-#include "SkShader.h"
-#include "SkShaderExtras.h"
-#include "SkTime.h"
-#include "SkTypeface.h"
-#include "SkUtils.h"
-#include "SkKey.h"
-#include "SkXfermode.h"
-#include "SkDrawFilter.h"
 
 #include "test.h"
 
-class TestsView : public SkView {
+namespace skiatest {
+    
+class MyReporter : public Reporter {
+protected:
+    virtual void onStart(Test* test) {}
+    virtual void onReport(const char desc[], Reporter::Result result) {
+        SkASSERT(Reporter::kPassed == result);
+    }
+    virtual void onEnd(Test* test) {}
+};
+
+class Iter {
 public:
-    skia::Test::Iter fIter;
+    Iter(Reporter* r) : fReporter(r) {
+        r->ref();
+        fReg = TestRegistry::Head();
+    }
+    
+    ~Iter() {
+        fReporter->unref();
+    }
+    
+    Test* next() {
+        if (fReg) {
+            TestRegistry::Factory fact = fReg->factory();
+            fReg = fReg->next();
+            Test* test = fact(NULL);
+            test->setReporter(fReporter);
+            return test;
+        }
+        return NULL;
+    }
+    
+    static int Count() {
+        const TestRegistry* reg = TestRegistry::Head();
+        int count = 0;
+        while (reg) {
+            count += 1;
+            reg = reg->next();
+        }
+        return count;
+    }
+    
+private:
+    Reporter* fReporter;
+    const TestRegistry* fReg;
+};
+}
 
+class TestsView : public SkView {
+public:
        TestsView() {}
 
 protected:
@@ -45,30 +73,15 @@ protected:
     
     virtual void onDraw(SkCanvas* canvas) {
         this->drawBG(canvas);
+
+        skiatest::MyReporter reporter;
+        skiatest::Iter iter(&reporter);
+        skiatest::Test* test;
         
-        skia::Test* test = fIter.next();
-        if (NULL == test) {
-            fIter.reset();
-            test = fIter.next();
+        while ((test = iter.next()) != NULL) {
+            test->run();
+            SkDELETE(test);
         }
-        
-        SkIPoint    size;
-        test->getSize(&size);
-        
-        SkBitmap    bitmap;
-        bitmap.setConfig(SkBitmap::kARGB_8888_Config, size.fX, size.fY);
-        bitmap.allocPixels();
-        bitmap.eraseColor(0);
-        
-        SkCanvas c(bitmap);
-        test->draw(&c);
-        
-        canvas->drawBitmap(bitmap, SkIntToScalar(10), SkIntToScalar(10), NULL);
-        
-        SkString str;
-        test->getString(skia::Test::kTitle, &str);
-        SkDebugf("--- %s\n", str.c_str());
-        delete test;
     }
     
     virtual SkView::Click* onFindClickHandler(SkScalar x, SkScalar y) {
index 704863c..fa6b3b9 100644 (file)
@@ -97,7 +97,7 @@ static void compute_pt_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
 ////////////////////////////////////////////////////////////////////////////
 
 SkPath::SkPath() : fBoundsIsDirty(true), fFillType(kWinding_FillType) {
-    fIsConvex = false;  // really should be kUnknown
+    fConvexity = kUnknown_Convexity;
 #ifdef ANDROID
     fGenerationID = 0;
 #endif
@@ -125,7 +125,7 @@ SkPath& SkPath::operator=(const SkPath& src) {
         fVerbs          = src.fVerbs;
         fFillType       = src.fFillType;
         fBoundsIsDirty  = src.fBoundsIsDirty;
-        fIsConvex       = src.fIsConvex;
+        fConvexity      = src.fConvexity;
         GEN_ID_INC;
     }
     SkDEBUGCODE(this->validate();)
@@ -148,7 +148,7 @@ void SkPath::swap(SkPath& other) {
         fVerbs.swap(other.fVerbs);
         SkTSwap<uint8_t>(fFillType, other.fFillType);
         SkTSwap<uint8_t>(fBoundsIsDirty, other.fBoundsIsDirty);
-        SkTSwap<uint8_t>(fIsConvex, other.fIsConvex);
+        SkTSwap<uint8_t>(fConvexity, other.fConvexity);
         GEN_ID_INC;
     }
 }
@@ -166,7 +166,7 @@ void SkPath::reset() {
     fVerbs.reset();
     GEN_ID_INC;
     fBoundsIsDirty = true;
-    fIsConvex = false;  // really should be kUnknown
+    fConvexity = kUnknown_Convexity;
 }
 
 void SkPath::rewind() {
@@ -176,7 +176,7 @@ void SkPath::rewind() {
     fVerbs.rewind();
     GEN_ID_INC;
     fBoundsIsDirty = true;
-    fIsConvex = false;  // really should be kUnknown
+    fConvexity = kUnknown_Convexity;
 }
 
 bool SkPath::isEmpty() const {
@@ -244,6 +244,13 @@ void SkPath::computeBounds() const {
     compute_pt_bounds(&fBounds, fPts);
 }
 
+void SkPath::setConvexity(Convexity c) {
+    if (fConvexity != c) {
+        fConvexity = c;
+        GEN_ID_INC;
+    }
+}
+
 //////////////////////////////////////////////////////////////////////////////
 //  Construction methods
 
@@ -1382,3 +1389,125 @@ void SkPath::validate() const {
 }
 #endif
 
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ *  Returns -1 || 0 || 1 depending on the sign of value:
+ *  -1 if value < 0
+ *   0 if vlaue == 0
+ *   1 if value > 0
+ */
+static int SkScalarSign(SkScalar value) {
+    return value < 0 ? -1 : (value > 0);
+}
+
+static int CrossProductSign(const SkVector& a, const SkVector& b) {
+    return SkScalarSign(SkPoint::CrossProduct(a, b));
+}
+
+// only valid for a single contour
+struct Convexicator {
+    Convexicator() : fPtCount(0), fConvexity(SkPath::kUnknown_Convexity) {
+        fSign = 0;
+        // warnings
+        fCurrPt.set(0, 0);
+        fVec0.set(0, 0);
+        fVec1.set(0, 0);
+        fFirstVec.set(0, 0);
+    }
+
+    SkPath::Convexity getConvexity() const { return fConvexity; }
+
+    void addPt(const SkPoint& pt) {
+        if (SkPath::kConcave_Convexity == fConvexity) {
+            return;
+        }
+
+        if (0 == fPtCount) {
+            fCurrPt = pt;
+            ++fPtCount;
+        } else {
+            SkVector vec = pt - fCurrPt;
+            if (vec.fX || vec.fY) {
+                fCurrPt = pt;
+                if (++fPtCount == 2) {
+                    fFirstVec = fVec1 = vec;
+                } else {
+                    SkASSERT(fPtCount > 2);
+                    this->addVec(vec);
+                }
+            }
+        }
+    }
+
+    void close() {
+        if (fPtCount > 2) {
+            this->addVec(fFirstVec);
+        }
+    }
+
+private:
+    void addVec(const SkVector& vec) {
+        SkASSERT(vec.fX || vec.fY);
+        fVec0 = fVec1;
+        fVec1 = vec;
+        int sign = CrossProductSign(fVec0, fVec1);
+        if (0 == fSign) {
+            fSign = sign;
+        } else if (sign) {
+            if (fSign == sign) {
+                fConvexity = SkPath::kConvex_Convexity;
+            } else {
+                fConvexity = SkPath::kConcave_Convexity;
+            }
+        }
+    }
+
+    SkPoint             fCurrPt;
+    SkVector            fVec0, fVec1, fFirstVec;
+    int                 fPtCount;   // non-degenerate points
+    int                 fSign;
+    SkPath::Convexity   fConvexity;
+};
+
+SkPath::Convexity SkPath::ComputeConvexity(const SkPath& path) {
+    SkPoint         pts[4];
+    SkPath::Verb    verb;
+    SkPath::Iter    iter(path, true);
+
+    int             contourCount = 0;
+    int             count;
+    Convexicator    state;
+
+    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+        switch (verb) {
+            case kMove_Verb:
+                if (++contourCount > 1) {
+                    return kConcave_Convexity;
+                }
+                pts[1] = pts[0];
+                count = 1;
+                break;
+            case kLine_Verb: count = 1; break;
+            case kQuad_Verb: count = 2; break;
+            case kCubic_Verb: count = 3; break;
+            case kClose_Verb:
+                state.close();
+                count = 0;
+                break;
+            default:
+                SkASSERT(!"bad verb");
+                return kConcave_Convexity;
+        }
+
+        for (int i = 1; i <= count; i++) {
+            state.addPt(pts[i]);
+        }
+        // early exit
+        if (kConcave_Convexity == state.getConvexity()) {
+            return kConcave_Convexity;
+        }
+    }
+    return state.getConvexity();
+}
+
index b4b6317..4d00f70 100644 (file)
@@ -1,5 +1,6 @@
 #include "Test.h"
 #include "SkPath.h"
+#include "SkParse.h"
 #include "SkSize.h"
 
 static void check_convex_bounds(skiatest::Reporter* reporter, const SkPath& p,
@@ -17,7 +18,67 @@ static void check_convex_bounds(skiatest::Reporter* reporter, const SkPath& p,
     REPORTER_ASSERT(reporter, other.getBounds() == bounds);
 }
 
-static void TestPath(skiatest::Reporter* reporter) {
+static void setFromString(SkPath* path, const char str[]) {
+    bool first = true;
+    while (str) {
+        SkScalar x, y;
+        str = SkParse::FindScalar(str, &x);
+        if (NULL == str) {
+            break;
+        }
+        str = SkParse::FindScalar(str, &y);
+        SkASSERT(str);
+        if (first) {
+            path->moveTo(x, y);
+            first = false;
+        } else {
+            path->lineTo(x, y);
+        }
+    }
+}
+
+static void test_convexity(skiatest::Reporter* reporter) {
+    static const SkPath::Convexity U = SkPath::kUnknown_Convexity;
+    static const SkPath::Convexity C = SkPath::kConcave_Convexity;
+    static const SkPath::Convexity V = SkPath::kConvex_Convexity;
+
+    SkPath path;
+
+    REPORTER_ASSERT(reporter, U == SkPath::ComputeConvexity(path));
+    path.addCircle(0, 0, 10);
+    REPORTER_ASSERT(reporter, V == SkPath::ComputeConvexity(path));
+    path.addCircle(0, 0, 10);   // 2nd circle
+    REPORTER_ASSERT(reporter, C == SkPath::ComputeConvexity(path));
+    path.reset();
+    path.addRect(0, 0, 10, 10, SkPath::kCCW_Direction);
+    REPORTER_ASSERT(reporter, V == SkPath::ComputeConvexity(path));
+    path.reset();
+    path.addRect(0, 0, 10, 10, SkPath::kCW_Direction);
+    REPORTER_ASSERT(reporter, V == SkPath::ComputeConvexity(path));
+    
+    static const struct {
+        const char*         fPathStr;
+        SkPath::Convexity   fExpectedConvexity;
+    } gRec[] = {
+        { "0 0", SkPath::kUnknown_Convexity },
+        { "0 0 10 10", SkPath::kUnknown_Convexity },
+        { "0 0 10 10 20 20 0 0 10 10", SkPath::kUnknown_Convexity },
+        { "0 0 10 10 10 20", SkPath::kConvex_Convexity },
+        { "0 0 10 10 10 0", SkPath::kConvex_Convexity },
+        { "0 0 10 10 10 0 0 10", SkPath::kConcave_Convexity },
+        { "0 0 10 0 0 10 -10 -10", SkPath::kConcave_Convexity },
+    };
+
+    for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); ++i) {
+        SkPath path;
+        setFromString(&path, gRec[i].fPathStr);
+        SkPath::Convexity c = SkPath::ComputeConvexity(path);
+        REPORTER_ASSERT(reporter, c == gRec[i].fExpectedConvexity);
+    }
+}
+
+void TestPath(skiatest::Reporter* reporter);
+void TestPath(skiatest::Reporter* reporter) {
     {
         SkSize size;
         size.fWidth = 3.4f;
@@ -100,6 +161,8 @@ static void TestPath(skiatest::Reporter* reporter) {
     REPORTER_ASSERT(reporter, p.isConvex());
     p.rewind();
     REPORTER_ASSERT(reporter, !p.isConvex());
+
+    test_convexity(reporter);
 }
 
 #include "TestClassDef.h"