--- /dev/null
+/*
+ * 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);
+
+}
# include this gypi to include all the golden master slides.
{
+ 'includes': [
+ 'pathops.gypi',
+ ],
'sources': [
'../gm/aaclip.cpp',
'../gm/aarectmodes.cpp',
'../gm/patheffects.cpp',
'../gm/pathfill.cpp',
'../gm/pathinterior.cpp',
+ '../gm/pathopsinverse.cpp',
'../gm/pathreverse.cpp',
'../gm/perlinnoise.cpp',
'../gm/points.cpp',
'../tests/PathOpsDTriangleTest.cpp',
'../tests/PathOpsDVectorTest.cpp',
'../tests/PathOpsExtendedTest.cpp',
+ '../tests/PathOpsInverseTest.cpp',
'../tests/PathOpsLineIntersectionTest.cpp',
'../tests/PathOpsLineParametetersTest.cpp',
'../tests/PathOpsOpCubicThreadedTest.cpp',
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
#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
{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
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;
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;
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();
#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
: !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();
--- /dev/null
+/*
+ * 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)