path ops : add support for inverse fill

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
This commit is contained in:
caryclark@google.com 2013-04-22 14:37:05 +00:00
parent c4c9870953
commit 7dfbb0720a
8 changed files with 205 additions and 19 deletions

116
gm/pathopsinverse.cpp Normal file
View File

@ -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);
}

View 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',

View 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',

View 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

View 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;

View 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();

View 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();

View File

@ -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)