aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/gpu/GrShape.cpp27
-rw-r--r--src/gpu/GrShape.h18
-rw-r--r--tests/GrShapeTest.cpp110
3 files changed, 131 insertions, 24 deletions
diff --git a/src/gpu/GrShape.cpp b/src/gpu/GrShape.cpp
index eb07ffc558..221247808b 100644
--- a/src/gpu/GrShape.cpp
+++ b/src/gpu/GrShape.cpp
@@ -13,6 +13,8 @@ GrShape& GrShape::operator=(const GrShape& that) {
switch (fType) {
case Type::kEmpty:
break;
+ case Type::kInvertedEmpty:
+ break;
case Type::kRRect:
fRRectData = that.fRRectData;
break;
@@ -36,6 +38,8 @@ SkRect GrShape::bounds() const {
switch (fType) {
case Type::kEmpty:
return kInverted;
+ case Type::kInvertedEmpty:
+ return kInverted;
case Type::kLine: {
SkRect bounds;
if (fLineData.fPts[0].fX < fLineData.fPts[1].fX) {
@@ -64,9 +68,10 @@ SkRect GrShape::bounds() const {
}
SkRect GrShape::styledBounds() const {
- if (Type::kEmpty == fType && !fStyle.hasNonDashPathEffect()) {
+ if (this->isEmpty() && !fStyle.hasNonDashPathEffect()) {
return SkRect::MakeEmpty();
}
+
SkRect bounds;
fStyle.adjustBounds(&bounds, this->bounds());
return bounds;
@@ -122,6 +127,8 @@ int GrShape::unstyledKeySize() const {
switch (fType) {
case Type::kEmpty:
return 1;
+ case Type::kInvertedEmpty:
+ return 1;
case Type::kRRect:
SkASSERT(!fInheritedKey.count());
SkASSERT(0 == SkRRect::kSizeInMemory % sizeof(uint32_t));
@@ -158,6 +165,9 @@ void GrShape::writeUnstyledKey(uint32_t* key) const {
case Type::kEmpty:
*key++ = 1;
break;
+ case Type::kInvertedEmpty:
+ *key++ = 2;
+ break;
case Type::kRRect:
fRRectData.fRRect.writeToMemory(key);
key += SkRRect::kSizeInMemory / sizeof(uint32_t);
@@ -244,6 +254,8 @@ GrShape::GrShape(const GrShape& that) : fStyle(that.fStyle) {
switch (fType) {
case Type::kEmpty:
break;
+ case Type::kInvertedEmpty:
+ break;
case Type::kRRect:
fRRectData = that.fRRectData;
break;
@@ -354,7 +366,9 @@ void GrShape::attemptToSimplifyPath() {
bool inverted = this->path().isInverseFillType();
SkPoint pts[2];
if (this->path().isEmpty()) {
- this->changeType(Type::kEmpty);
+ // Dashing ignores inverseness skbug.com/5421.
+ this->changeType(inverted && !this->style().isDashed() ? Type::kInvertedEmpty
+ : Type::kEmpty);
} else if (this->path().isLine(pts)) {
this->changeType(Type::kLine);
fLineData.fPts[0] = pts[0];
@@ -442,7 +456,8 @@ void GrShape::attemptToSimplifyRRect() {
SkASSERT(Type::kRRect == fType);
SkASSERT(!fInheritedKey.count());
if (fRRectData.fRRect.isEmpty()) {
- fType = Type::kEmpty;
+ // Dashing ignores the inverseness currently. skbug.com/5421
+ fType = fRRectData.fInverted && !fStyle.isDashed() ? Type::kInvertedEmpty : Type::kEmpty;
return;
}
if (!this->style().hasPathEffect()) {
@@ -480,8 +495,8 @@ void GrShape::attemptToSimplifyLine() {
rec.setStrokeStyle(fStyle.strokeRec().getWidth(), false);
fStyle = GrStyle(rec, nullptr);
}
- if (fStyle.isSimpleFill() && !fLineData.fInverted) {
- this->changeType(Type::kEmpty);
+ if (fStyle.isSimpleFill()) {
+ this->changeType(fLineData.fInverted ? Type::kInvertedEmpty : Type::kEmpty);
return;
}
SkPoint* pts = fLineData.fPts;
@@ -525,7 +540,7 @@ void GrShape::attemptToSimplifyLine() {
fRRectData.fStart = kDefaultRRectStart;
if (fRRectData.fRRect.isEmpty()) {
// This can happen when r is very small relative to the rect edges.
- this->changeType(Type::kEmpty);
+ this->changeType(inverted ? Type::kInvertedEmpty : Type::kEmpty);
return;
}
fStyle = GrStyle::SimpleFill();
diff --git a/src/gpu/GrShape.h b/src/gpu/GrShape.h
index 074278cd86..5226c239cd 100644
--- a/src/gpu/GrShape.h
+++ b/src/gpu/GrShape.h
@@ -176,6 +176,10 @@ public:
case Type::kEmpty:
out->reset();
break;
+ case Type::kInvertedEmpty:
+ out->reset();
+ out->setFillType(kDefaultPathInverseFillType);
+ break;
case Type::kRRect:
out->reset();
out->addRRect(fRRectData.fRRect, fRRectData.fDir, fRRectData.fStart);
@@ -204,9 +208,9 @@ public:
/**
* Returns whether the geometry is empty. Note that applying the style could produce a
- * non-empty shape.
+ * non-empty shape. It also may have an inverse fill.
*/
- bool isEmpty() const { return Type::kEmpty == fType; }
+ bool isEmpty() const { return Type::kEmpty == fType || Type::kInvertedEmpty == fType; }
/**
* Gets the bounds of the geometry without reflecting the shape's styling. This ignores
@@ -229,6 +233,8 @@ public:
switch (fType) {
case Type::kEmpty:
return true;
+ case Type::kInvertedEmpty:
+ return true;
case Type::kRRect:
return true;
case Type::kLine:
@@ -251,6 +257,9 @@ public:
case Type::kEmpty:
ret = false;
break;
+ case Type::kInvertedEmpty:
+ ret = true;
+ break;
case Type::kRRect:
ret = fRRectData.fInverted;
break;
@@ -288,6 +297,8 @@ public:
switch (fType) {
case Type::kEmpty:
return true;
+ case Type::kInvertedEmpty:
+ return true;
case Type::kRRect:
return true;
case Type::kLine:
@@ -303,6 +314,8 @@ public:
switch (fType) {
case Type::kEmpty:
return 0;
+ case Type::kInvertedEmpty:
+ return 0;
case Type::kRRect:
if (fRRectData.fRRect.getType() == SkRRect::kOval_Type) {
return SkPath::kConic_SegmentMask;
@@ -336,6 +349,7 @@ public:
private:
enum class Type {
kEmpty,
+ kInvertedEmpty,
kRRect,
kLine,
kPath,
diff --git a/tests/GrShapeTest.cpp b/tests/GrShapeTest.cpp
index 3f97288c1c..719e5cd974 100644
--- a/tests/GrShapeTest.cpp
+++ b/tests/GrShapeTest.cpp
@@ -175,7 +175,7 @@ public:
bool fillChangesGeom() const override {
// unclosed rects get closed. Lines get turned into empty geometry
- return this->isUnclosedRect() || (fPath.isLine(nullptr) && !fPath.isInverseFillType());
+ return this->isUnclosedRect() || fPath.isLine(nullptr);
}
bool strokeIsConvertedToFill() const override {
@@ -1203,23 +1203,29 @@ void test_volatile_path(skiatest::Reporter* reporter, const Geo& geo) {
void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const Geo& geo) {
/**
- * This path effect returns an empty path.
+ * This path effect returns an empty path (possibly inverted)
*/
class EmptyPathEffect : SkPathEffect {
public:
bool filterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
const SkRect* cullR) const override {
dst->reset();
+ if (fInvert) {
+ dst->toggleInverseFillType();
+ }
return true;
}
void computeFastBounds(SkRect* dst, const SkRect& src) const override {
dst->setEmpty();
}
- static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new EmptyPathEffect); }
+ static sk_sp<SkPathEffect> Make(bool invert) {
+ return sk_sp<SkPathEffect>(new EmptyPathEffect(invert));
+ }
Factory getFactory() const override { return nullptr; }
void toString(SkString*) const override {}
private:
- EmptyPathEffect() {}
+ bool fInvert;
+ EmptyPathEffect(bool invert) : fInvert(invert) {}
};
SkPath emptyPath;
@@ -1228,17 +1234,27 @@ void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const Geo&
make_key(&emptyKey, emptyShape);
REPORTER_ASSERT(reporter, emptyShape.isEmpty());
+ emptyPath.toggleInverseFillType();
+ GrShape invertedEmptyShape(emptyPath);
+ Key invertedEmptyKey;
+ make_key(&invertedEmptyKey, invertedEmptyShape);
+ REPORTER_ASSERT(reporter, invertedEmptyShape.isEmpty());
+
+ REPORTER_ASSERT(reporter, invertedEmptyKey != emptyKey);
+
SkPaint pe;
- pe.setPathEffect(EmptyPathEffect::Make());
- TestCase geoCase(geo, pe, reporter);
- REPORTER_ASSERT(reporter, geoCase.appliedFullStyleKey() == emptyKey);
- REPORTER_ASSERT(reporter, geoCase.appliedPathEffectKey() == emptyKey);
- REPORTER_ASSERT(reporter, geoCase.appliedPathEffectThenStrokeKey() == emptyKey);
- REPORTER_ASSERT(reporter, geoCase.appliedPathEffectShape().isEmpty());
- REPORTER_ASSERT(reporter, geoCase.appliedFullStyleShape().isEmpty());
+ pe.setPathEffect(EmptyPathEffect::Make(false));
+ TestCase geoPECase(geo, pe, reporter);
+ REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == emptyKey);
+ REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == emptyKey);
+ REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectThenStrokeKey() == emptyKey);
+ REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().isEmpty());
+ REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().isEmpty());
+ REPORTER_ASSERT(reporter, !geoPECase.appliedPathEffectShape().inverseFilled());
+ REPORTER_ASSERT(reporter, !geoPECase.appliedFullStyleShape().inverseFilled());
SkPaint peStroke;
- peStroke.setPathEffect(EmptyPathEffect::Make());
+ peStroke.setPathEffect(EmptyPathEffect::Make(false));
peStroke.setStrokeWidth(2.f);
peStroke.setStyle(SkPaint::kStroke_Style);
TestCase geoPEStrokeCase(geo, peStroke, reporter);
@@ -1247,6 +1263,29 @@ void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const Geo&
REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectThenStrokeKey() == emptyKey);
REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().isEmpty());
REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty());
+ REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedPathEffectShape().inverseFilled());
+ REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().inverseFilled());
+ pe.setPathEffect(EmptyPathEffect::Make(true));
+
+ TestCase geoPEInvertCase(geo, pe, reporter);
+ REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleKey() == invertedEmptyKey);
+ REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectKey() == invertedEmptyKey);
+ REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
+ REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().isEmpty());
+ REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().isEmpty());
+ REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().inverseFilled());
+ REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().inverseFilled());
+
+ peStroke.setPathEffect(EmptyPathEffect::Make(true));
+ TestCase geoPEInvertStrokeCase(geo, peStroke, reporter);
+ REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleKey() == invertedEmptyKey);
+ REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectKey() == invertedEmptyKey);
+ REPORTER_ASSERT(reporter,
+ geoPEInvertStrokeCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
+ REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().isEmpty());
+ REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().isEmpty());
+ REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().inverseFilled());
+ REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().inverseFilled());
}
void test_path_effect_fails(skiatest::Reporter* reporter, const Geo& geo) {
@@ -1313,37 +1352,57 @@ void test_path_effect_fails(skiatest::Reporter* reporter, const Geo& geo) {
DEF_TEST(GrShape_empty_shape, reporter) {
SkPath emptyPath;
+ SkPath invertedEmptyPath;
+ invertedEmptyPath.toggleInverseFillType();
SkPaint fill;
TestCase fillEmptyCase(reporter, emptyPath, fill);
REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty());
REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty());
REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty());
+ REPORTER_ASSERT(reporter, !fillEmptyCase.baseShape().inverseFilled());
+ REPORTER_ASSERT(reporter, !fillEmptyCase.appliedPathEffectShape().inverseFilled());
+ REPORTER_ASSERT(reporter, !fillEmptyCase.appliedFullStyleShape().inverseFilled());
+ TestCase fillInvertedEmptyCase(reporter, invertedEmptyPath, fill);
+ REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().isEmpty());
+ REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().isEmpty());
+ REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().isEmpty());
+ REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().inverseFilled());
+ REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().inverseFilled());
+ REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().inverseFilled());
Key emptyKey(fillEmptyCase.baseKey());
REPORTER_ASSERT(reporter, emptyKey.count());
+ Key inverseEmptyKey(fillInvertedEmptyCase.baseKey());
+ REPORTER_ASSERT(reporter, inverseEmptyKey.count());
TestCase::SelfExpectations expectations;
expectations.fStrokeApplies = false;
expectations.fPEHasEffect = false;
// This will test whether applying style preserves emptiness
fillEmptyCase.testExpectations(reporter, expectations);
+ fillInvertedEmptyCase.testExpectations(reporter, expectations);
// Stroking an empty path should have no effect
- SkPath emptyPath2;
SkPaint stroke;
stroke.setStrokeWidth(2.f);
stroke.setStyle(SkPaint::kStroke_Style);
- TestCase strokeEmptyCase(reporter, emptyPath2, stroke);
+ TestCase strokeEmptyCase(reporter, emptyPath, stroke);
strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
+ TestCase strokeInvertedEmptyCase(reporter, invertedEmptyPath, stroke);
+ strokeInvertedEmptyCase.compare(reporter, fillInvertedEmptyCase,
+ TestCase::kAllSame_ComparisonExpecation);
// Dashing and stroking an empty path should have no effect
- SkPath emptyPath3;
SkPaint dashAndStroke;
dashAndStroke.setPathEffect(make_dash());
dashAndStroke.setStrokeWidth(2.f);
dashAndStroke.setStyle(SkPaint::kStroke_Style);
- TestCase dashAndStrokeEmptyCase(reporter, emptyPath3, dashAndStroke);
+ TestCase dashAndStrokeEmptyCase(reporter, emptyPath, dashAndStroke);
dashAndStrokeEmptyCase.compare(reporter, fillEmptyCase,
TestCase::kAllSame_ComparisonExpecation);
+ TestCase dashAndStrokeInvertexEmptyCase(reporter, invertedEmptyPath, dashAndStroke);
+ // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
+ dashAndStrokeInvertexEmptyCase.compare(reporter, fillEmptyCase,
+ TestCase::kAllSame_ComparisonExpecation);
// A shape made from an empty rrect should behave the same as an empty path.
SkRRect emptyRRect = SkRRect::MakeRect(SkRect::MakeEmpty());
@@ -1351,12 +1410,24 @@ DEF_TEST(GrShape_empty_shape, reporter) {
TestCase dashAndStrokeEmptyRRectCase(reporter, emptyRRect, dashAndStroke);
dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase,
TestCase::kAllSame_ComparisonExpecation);
+ static constexpr SkPath::Direction kDir = SkPath::kCCW_Direction;
+ static constexpr int kStart = 0;
+ TestCase dashAndStrokeEmptyInvertedRRectCase(reporter, emptyRRect, kDir, kStart, true,
+ GrStyle(dashAndStroke));
+ // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
+ dashAndStrokeEmptyInvertedRRectCase.compare(reporter, fillEmptyCase,
+ TestCase::kAllSame_ComparisonExpecation);
// Same for a rect.
SkRect emptyRect = SkRect::MakeEmpty();
TestCase dashAndStrokeEmptyRectCase(reporter, emptyRect, dashAndStroke);
dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase,
TestCase::kAllSame_ComparisonExpecation);
+ TestCase dashAndStrokeEmptyInvertedRectCase(reporter, SkRRect::MakeRect(emptyRect), kDir,
+ kStart, true, GrStyle(dashAndStroke));
+ // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
+ dashAndStrokeEmptyInvertedRectCase.compare(reporter, fillEmptyCase,
+ TestCase::kAllSame_ComparisonExpecation);
}
// rect and oval types have rrect start indices that collapse to the same point. Here we select the
@@ -1651,6 +1722,13 @@ DEF_TEST(GrShape_lines, r) {
fillAB.compare(r, fillEmpty, TestCase::kAllSame_ComparisonExpecation);
REPORTER_ASSERT(r, !fillAB.baseShape().asLine(nullptr, nullptr));
+ SkPath path;
+ path.toggleInverseFillType();
+ TestCase fillEmptyInverted(r, path, fill);
+ TestCase fillABInverted(r, invLineAB, fill);
+ fillABInverted.compare(r, fillEmptyInverted, TestCase::kAllSame_ComparisonExpecation);
+ REPORTER_ASSERT(r, !fillABInverted.baseShape().asLine(nullptr, nullptr));
+
TestCase strokeAB(r, lineAB, stroke);
TestCase strokeBA(r, lineBA, stroke);
TestCase strokeAC(r, lineAC, stroke);