path ops : add support for inverse fill
authorcaryclark@google.com <caryclark@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Mon, 22 Apr 2013 14:37:05 +0000 (14:37 +0000)
committercaryclark@google.com <caryclark@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Mon, 22 Apr 2013 14:37:05 +0000 (14:37 +0000)
add inverse fill, reverse diff, and gm tests
cleaned up some interfaces
Review URL: https://codereview.chromium.org/14371011

git-svn-id: http://skia.googlecode.com/svn/trunk@8798 2bbb7eff-a529-9590-31e7-b0007b416f81

gm/pathopsinverse.cpp [new file with mode: 0644]
gyp/gmslides.gypi
gyp/pathops_unittest.gypi
include/pathops/SkPathOps.h
src/pathops/SkOpSegment.cpp
src/pathops/SkPathOpsOp.cpp
src/pathops/SkPathOpsSimplify.cpp
tests/PathOpsInverseTest.cpp [new file with mode: 0644]

diff --git a/gm/pathopsinverse.cpp b/gm/pathopsinverse.cpp
new file mode 100644 (file)
index 0000000..e85ff6f
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2013 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 "SkBitmap.h"
+#include "SkPath.h"
+#include "SkPathOps.h"
+#include "SkRect.h"
+
+namespace skiagm {
+
+class PathOpsInverseGM : public GM {
+public:
+    PathOpsInverseGM() {
+        this->makePaints();
+    }
+
+protected:
+    void makePaints() {
+        const unsigned oneColor = 0xFF8080FF;
+        const unsigned twoColor = 0x807F1f1f;
+        SkColor blendColor = blend(oneColor, twoColor);
+        makePaint(&onePaint, oneColor);
+        makePaint(&twoPaint, twoColor);
+        makePaint(&opPaint[kDifference_PathOp], oneColor);
+        makePaint(&opPaint[kIntersect_PathOp], blendColor);
+        makePaint(&opPaint[kUnion_PathOp], 0xFFc0FFc0);
+        makePaint(&opPaint[kReverseDifference_PathOp], twoColor);
+        makePaint(&opPaint[kXOR_PathOp], 0xFFa0FFe0);
+        makePaint(&outlinePaint, 0xFF000000);
+        outlinePaint.setStyle(SkPaint::kStroke_Style);
+    }
+
+    SkColor blend(SkColor one, SkColor two) {
+        SkBitmap temp;
+        temp.setConfig(SkBitmap::kARGB_8888_Config, 1, 1);
+        temp.allocPixels();
+        SkCanvas canvas(temp);
+        canvas.drawColor(one);
+        canvas.drawColor(two);
+        void* pixels = temp.getPixels();
+        return *(SkColor*) pixels;
+    }
+
+    void makePaint(SkPaint* paint, SkColor color) {
+        paint->setAntiAlias(true);
+        paint->setStyle(SkPaint::kFill_Style);
+        paint->setColor(color);
+    }
+
+    virtual SkString onShortName() SK_OVERRIDE {
+        return SkString("pathopsinverse");
+    }
+
+    virtual SkISize onISize() SK_OVERRIDE {
+        return make_isize(1200, 900);
+    }
+
+    virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
+        SkPath one, two;
+        int yPos = 0;
+        for (int oneFill = 0; oneFill <= 1; ++oneFill) {
+            SkPath::FillType oneF = oneFill ? SkPath::kInverseEvenOdd_FillType
+                    : SkPath::kEvenOdd_FillType;
+            for (int twoFill = 0; twoFill <= 1; ++twoFill) {
+                SkPath::FillType twoF = twoFill ? SkPath::kInverseEvenOdd_FillType
+                        : SkPath::kEvenOdd_FillType;
+                one.reset();
+                one.setFillType(oneF);
+                one.addRect(10, 10, 70, 70);
+                two.reset();
+                two.setFillType(twoF);
+                two.addRect(40, 40, 100, 100);
+                canvas->save();
+                canvas->translate(0, SkIntToScalar(yPos));
+                canvas->clipRect(SkRect::MakeWH(110, 110), SkRegion::kIntersect_Op, true);
+                canvas->drawPath(one, onePaint);
+                canvas->drawPath(one, outlinePaint);
+                canvas->drawPath(two, twoPaint);
+                canvas->drawPath(two, outlinePaint);
+                canvas->restore();
+                int xPos = 150;
+                for (int op = kDifference_PathOp; op <= kReverseDifference_PathOp; ++op) {
+                    SkPath result;
+                    Op(one, two, (SkPathOp) op, &result);
+                    canvas->save();
+                    canvas->translate(SkIntToScalar(xPos), SkIntToScalar(yPos));
+                    canvas->clipRect(SkRect::MakeWH(110, 110), SkRegion::kIntersect_Op, true);
+                    canvas->drawPath(result, opPaint[op]);
+                    canvas->drawPath(result, outlinePaint);
+                    canvas->restore();
+                    xPos += 150;
+                }
+                yPos += 150;
+            }
+        }
+    }
+
+private:
+    SkPaint onePaint;
+    SkPaint twoPaint;
+    SkPaint outlinePaint;
+    SkPaint opPaint[kReverseDifference_PathOp - kDifference_PathOp + 1];
+    typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static GM* MyFactory(void*) { return new PathOpsInverseGM; }
+static GMRegistry reg(MyFactory);
+
+}
index f836193..e1daaf8 100644 (file)
@@ -1,5 +1,8 @@
 # include this gypi to include all the golden master slides.
 {
+  'includes': [
+    'pathops.gypi',
+  ],
   'sources': [
     '../gm/aaclip.cpp',
     '../gm/aarectmodes.cpp',
@@ -66,6 +69,7 @@
     '../gm/patheffects.cpp',
     '../gm/pathfill.cpp',
     '../gm/pathinterior.cpp',
+    '../gm/pathopsinverse.cpp',
     '../gm/pathreverse.cpp',
     '../gm/perlinnoise.cpp',
     '../gm/points.cpp',
index ebe1d5b..21fb929 100644 (file)
@@ -15,6 +15,7 @@
     '../tests/PathOpsDTriangleTest.cpp',
     '../tests/PathOpsDVectorTest.cpp',
     '../tests/PathOpsExtendedTest.cpp',
+    '../tests/PathOpsInverseTest.cpp',
     '../tests/PathOpsLineIntersectionTest.cpp',
     '../tests/PathOpsLineParametetersTest.cpp',
     '../tests/PathOpsOpCubicThreadedTest.cpp',
index 0ad6ef2..2851186 100644 (file)
@@ -9,18 +9,18 @@
 
 class SkPath;
 
-// FIXME: move this into SkPaths.h or just use the equivalent in SkRegion.h
+// FIXME: move everything below into the SkPath class
+/**
+  *  The logical operations that can be performed when combining two paths.
+  */
 enum SkPathOp {
-    kDifference_PathOp,  //!< subtract the op path from the first path
-    kIntersect_PathOp,   //!< intersect the two paths
-    kUnion_PathOp,       //!< union (inclusive-or) the two paths
-    kXOR_PathOp,         //!< exclusive-or the two paths
-    /** subtract the first path from the op path */
-    kReverseDifference_PathOp,  // FIXME: unsupported
-    kReplace_PathOp      //!< replace the dst path with the op  FIXME: unsupported: should it be?
+    kDifference_PathOp,         //!< subtract the op path from the first path
+    kIntersect_PathOp,          //!< intersect the two paths
+    kUnion_PathOp,              //!< union (inclusive-or) the two paths
+    kXOR_PathOp,                //!< exclusive-or the two paths
+    kReverseDifference_PathOp,  //!< subtract the first path from the op path
 };
 
-// FIXME: these functions become members of SkPath
 /**
   *  Set this path to the result of applying the Op to this path and the
   *  specified path: this = (this op operand). The resulting path will be constructed
index e221f68..43afa08 100644 (file)
@@ -7,7 +7,7 @@
 #include "SkIntersections.h"
 #include "SkOpSegment.h"
 #include "SkPathWriter.h"
-#include "TSearch.h"
+#include "SkTSort.h"
 
 #define F (false)      // discard the edge
 #define T (true)       // keep the edge
@@ -18,7 +18,6 @@ static const bool gUnaryActiveEdge[2][2] = {
     {F, T}, {T, F},
 };
 
-// FIXME: add support for kReverseDifference_Op
 static const bool gActiveEdge[kXOR_PathOp + 1][2][2][2][2] = {
 //                 miFrom=0                              miFrom=1
 //         miTo=0            miTo=1              miTo=0             miTo=1
@@ -2318,7 +2317,7 @@ bool SkOpSegment::SortAngles(const SkTDArray<SkOpAngle>& angles,
         sortable &= !angle.unsortable();
     }
     if (sortable) {
-        QSort<SkOpAngle>(angleList->begin(), angleList->end() - 1);
+        SkTQSort<SkOpAngle>(angleList->begin(), angleList->end() - 1);
         for (angleIndex = 0; angleIndex < angleCount; ++angleIndex) {
             if (angles[angleIndex].unsortable()) {
                 sortable = false;
index 5a3576f..07a8bab 100644 (file)
@@ -206,18 +206,48 @@ static bool bridgeOp(SkTDArray<SkOpContour*>& contourList, const SkPathOp op,
     return simple->someAssemblyRequired();
 }
 
+// pretty picture:
+// https://docs.google.com/a/google.com/drawings/d/1sPV8rPfpEFXymBp3iSbDRWAycp1b-7vD9JP2V-kn9Ss/edit?usp=sharing
+static const SkPathOp gOpInverse[kReverseDifference_PathOp + 1][2][2] = {
+//                  inside minuend                               outside minuend
+//     inside subtrahend     outside subtrahend      inside subtrahend     outside subtrahend
+    {{ kDifference_PathOp,    kIntersect_PathOp }, { kUnion_PathOp, kReverseDifference_PathOp }},
+    {{ kIntersect_PathOp,    kDifference_PathOp }, { kReverseDifference_PathOp, kUnion_PathOp }},
+    {{ kUnion_PathOp, kReverseDifference_PathOp }, { kDifference_PathOp,    kIntersect_PathOp }},
+    {{ kXOR_PathOp,                 kXOR_PathOp }, { kXOR_PathOp,                 kXOR_PathOp }},
+    {{ kReverseDifference_PathOp, kUnion_PathOp }, { kIntersect_PathOp,    kDifference_PathOp }},
+};
+
+static const bool gOutInverse[kReverseDifference_PathOp + 1][2][2] = {
+    {{ false, false }, { true, false }},  // diff
+    {{ false, false }, { false, true }},  // sect
+    {{ false, true }, { true, true }},    // union
+    {{ false, true }, { true, false }},   // xor
+    {{ false, true }, { false, false }},  // rev diff
+};
+
 void Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result) {
+    op = gOpInverse[op][one.isInverseFillType()][two.isInverseFillType()];
+    result->reset();
+    SkPath::FillType fillType = gOutInverse[op][one.isInverseFillType()][two.isInverseFillType()]
+            ? SkPath::kInverseEvenOdd_FillType : SkPath::kEvenOdd_FillType;
+    result->setFillType(fillType);
+    const SkPath* minuend = &one;
+    const SkPath* subtrahend = &two;
+    if (op == kReverseDifference_PathOp) {
+        minuend = &two;
+        subtrahend = &one;
+        op = kDifference_PathOp;
+    }
 #if DEBUG_SORT || DEBUG_SWAP_TOP
     gDebugSortCount = gDebugSortCountDefault;
 #endif
-    result->reset();
-    result->setFillType(SkPath::kEvenOdd_FillType);
     // turn path into list of segments
     SkTArray<SkOpContour> contours;
     // FIXME: add self-intersecting cubics' T values to segment
-    SkOpEdgeBuilder builder(one, contours);
+    SkOpEdgeBuilder builder(*minuend, contours);
     const int xorMask = builder.xorMask();
-    builder.addOperand(two);
+    builder.addOperand(*subtrahend);
     builder.finish();
     const int xorOpMask = builder.xorMask();
     SkTDArray<SkOpContour*> contourList;
@@ -264,7 +294,7 @@ void Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result) {
     bridgeOp(contourList, op, xorMask, xorOpMask, &wrapper);
     {  // if some edges could not be resolved, assemble remaining fragments
         SkPath temp;
-        temp.setFillType(SkPath::kEvenOdd_FillType);
+        temp.setFillType(fillType);
         SkPathWriter assembled(temp);
         Assemble(wrapper, &assembled);
         *result = *assembled.nativePath();
index 2213c68..5c9d683 100644 (file)
@@ -149,7 +149,9 @@ void Simplify(const SkPath& path, SkPath* result) {
 #endif
     // returns 1 for evenodd, -1 for winding, regardless of inverse-ness
     result->reset();
-    result->setFillType(SkPath::kEvenOdd_FillType);
+    SkPath::FillType fillType = path.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
+            : SkPath::kEvenOdd_FillType;
+    result->setFillType(fillType);
     SkPathWriter simple(*result);
 
     // turn path into list of segments
@@ -187,7 +189,7 @@ void Simplify(const SkPath& path, SkPath* result) {
                 : !bridgeXor(contourList, &simple))
     {  // if some edges could not be resolved, assemble remaining fragments
         SkPath temp;
-        temp.setFillType(SkPath::kEvenOdd_FillType);
+        temp.setFillType(fillType);
         SkPathWriter assembled(temp);
         Assemble(simple, &assembled);
         *result = *assembled.nativePath();
diff --git a/tests/PathOpsInverseTest.cpp b/tests/PathOpsInverseTest.cpp
new file mode 100644 (file)
index 0000000..6d6e0e3
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "PathOpsExtendedTest.h"
+
+static void PathOpsInverseTest(skiatest::Reporter* reporter) {
+    SkPath one, two;
+    for (int op = kDifference_PathOp; op <= kReverseDifference_PathOp; ++op) {
+        for (int oneFill = SkPath::kWinding_FillType; oneFill <= SkPath::kInverseEvenOdd_FillType;
+                    ++oneFill) {
+            for (int oneDir = SkPath::kCW_Direction; oneDir != SkPath::kCCW_Direction; ++oneDir) {
+                one.reset();
+                one.setFillType((SkPath::FillType) oneFill);
+                one.addRect(0, 0, 6, 6, (SkPath::Direction) oneDir);
+                for (int twoFill = SkPath::kWinding_FillType;
+                        twoFill <= SkPath::kInverseEvenOdd_FillType; ++twoFill) {
+                    for (int twoDir = SkPath::kCW_Direction; twoDir != SkPath::kCCW_Direction;
+                            ++twoDir) {
+                        two.reset();
+                        two.setFillType((SkPath::FillType) twoFill);
+                        two.addRect(3, 3, 9, 9, (SkPath::Direction) twoDir);
+                        testPathOp(reporter, one, two, (SkPathOp) op);
+                    }
+                }
+            }
+        }
+    }
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS_SHORT(PathOpsInverseTest)