diff options
author | bsalomon <bsalomon@google.com> | 2016-06-10 08:05:14 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-06-10 08:05:14 -0700 |
commit | 7049396b65660907af5292d899053280430d929a (patch) | |
tree | 3a15e986397427c28acd312650035f9e32179728 | |
parent | 013e0e6d482f61181b829bf5ebfcad912c0061b1 (diff) |
Make GrShape capable of representing inverse filled rrects.
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2056523002
Review-Url: https://codereview.chromium.org/2056523002
-rw-r--r-- | src/gpu/GrShape.cpp | 38 | ||||
-rw-r--r-- | src/gpu/GrShape.h | 55 | ||||
-rw-r--r-- | tests/GrShapeTest.cpp | 351 |
3 files changed, 383 insertions, 61 deletions
diff --git a/src/gpu/GrShape.cpp b/src/gpu/GrShape.cpp index 7605cbf1ff..d2bec72cbf 100644 --- a/src/gpu/GrShape.cpp +++ b/src/gpu/GrShape.cpp @@ -24,6 +24,7 @@ GrShape& GrShape::operator=(const GrShape& that) { fRRect = that.fRRect; fRRectDir = that.fRRectDir; fRRectStart = that.fRRectStart; + fRRectIsInverted = that.fRRectIsInverted; break; case Type::kPath: if (wasPath) { @@ -71,13 +72,14 @@ int GrShape::unstyledKeySize() const { case Type::kRRect: SkASSERT(!fInheritedKey.count()); SkASSERT(0 == SkRRect::kSizeInMemory % sizeof(uint32_t)); - // + 1 for the direction + start index. + // + 1 for the direction, start index, and inverseness. return SkRRect::kSizeInMemory / sizeof(uint32_t) + 1; case Type::kPath: if (fPath.get()->isVolatile()) { return -1; } else { - return 1; + // The key is the path ID and fill type. + return 2; } } SkFAIL("Should never get here."); @@ -99,12 +101,16 @@ void GrShape::writeUnstyledKey(uint32_t* key) const { fRRect.writeToMemory(key); key += SkRRect::kSizeInMemory / sizeof(uint32_t); *key = (fRRectDir == SkPath::kCCW_Direction) ? (1 << 31) : 0; + *key |= fRRectIsInverted ? (1 << 30) : 0; *key++ |= fRRectStart; SkASSERT(fRRectStart < 8); break; case Type::kPath: SkASSERT(!fPath.get()->isVolatile()); *key++ = fPath.get()->getGenerationID(); + // We could canonicalize the fill rule for paths that don't differentiate between + // even/odd or winding fill (e.g. convex). + *key++ = fPath.get()->getFillType(); break; } } @@ -163,6 +169,9 @@ GrShape::GrShape(const GrShape& that) : fType(that.fType), fStyle(that.fStyle) { return; case Type::kRRect: fRRect = that.fRRect; + fRRectDir = that.fRRectDir; + fRRectStart = that.fRRectStart; + fRRectIsInverted = that.fRRectIsInverted; return; case Type::kPath: fPath.set(*that.fPath.get()); @@ -230,15 +239,16 @@ GrShape::GrShape(const GrShape& parent, GrStyle::Apply apply, SkScalar scale) { SkRRect rrect; SkPath::Direction dir; unsigned start; + bool inverted; Type parentType = AttemptToReduceFromPathImpl(*fPath.get(), &rrect, &dir, &start, - nullptr, strokeRec); + &inverted, nullptr, strokeRec); switch (parentType) { case Type::kEmpty: tmpParent.init(); parentForKey = tmpParent.get(); break; case Type::kRRect: - tmpParent.init(rrect, dir, start, GrStyle(strokeRec, nullptr)); + tmpParent.init(rrect, dir, start, inverted, GrStyle(strokeRec, nullptr)); parentForKey = tmpParent.get(); case Type::kPath: break; @@ -269,9 +279,25 @@ GrShape::GrShape(const GrShape& parent, GrStyle::Apply apply, SkScalar scale) { this->setInheritedKey(*parentForKey, apply, scale); } +static inline bool rrect_path_is_inverse_filled(const SkPath& path, const SkStrokeRec& strokeRec, + const SkPathEffect* pe) { + // Dashing doesn't use the path fill type. Dashing only works with stroking + if (pe && pe->asADash(nullptr)) { + pe = nullptr; + } + + SkStrokeRec::Style style = strokeRec.getStyle(); + if (!pe && (SkStrokeRec::kStroke_Style == style || SkStrokeRec::kHairline_Style == style)) { + // stroking ignores the path fill rule. + return false; + } + return path.isInverseFillType(); +} + GrShape::Type GrShape::AttemptToReduceFromPathImpl(const SkPath& path, SkRRect* rrect, SkPath::Direction* rrectDir, unsigned* rrectStart, + bool* rrectIsInverted, const SkPathEffect* pe, const SkStrokeRec& strokeRec) { if (path.isEmpty()) { @@ -285,6 +311,7 @@ GrShape::Type GrShape::AttemptToReduceFromPathImpl(const SkPath& path, SkRRect* if (!pe) { *rrectStart = DefaultRRectDirAndStartIndex(*rrect, false, rrectDir); } + *rrectIsInverted = rrect_path_is_inverse_filled(path, strokeRec, pe); return Type::kRRect; } SkRect rect; @@ -297,6 +324,7 @@ GrShape::Type GrShape::AttemptToReduceFromPathImpl(const SkPath& path, SkRRect* // convert from oval indexing to rrect indexiing. *rrectStart *= 2; } + *rrectIsInverted = rrect_path_is_inverse_filled(path, strokeRec, pe); return Type::kRRect; } // When there is a path effect we restrict rect detection to the narrower API that @@ -310,6 +338,7 @@ GrShape::Type GrShape::AttemptToReduceFromPathImpl(const SkPath& path, SkRRect* *rrectStart *= 2; } rrect->setRect(rect); + *rrectIsInverted = rrect_path_is_inverse_filled(path, strokeRec, pe); return Type::kRRect; } if (!pe) { @@ -320,6 +349,7 @@ GrShape::Type GrShape::AttemptToReduceFromPathImpl(const SkPath& path, SkRRect* // Since there is no path effect the dir and start index is immaterial. *rrectDir = kDefaultRRectDir; *rrectStart = kDefaultRRectStart; + *rrectIsInverted = rrect_path_is_inverse_filled(path, strokeRec, pe); return Type::kRRect; } } diff --git a/src/gpu/GrShape.h b/src/gpu/GrShape.h index 4ba56061e5..3efb66fd6b 100644 --- a/src/gpu/GrShape.h +++ b/src/gpu/GrShape.h @@ -44,14 +44,16 @@ public: explicit GrShape(const SkRRect& rrect) : fType(Type::kRRect) - , fRRect(rrect) { + , fRRect(rrect) + , fRRectIsInverted(false) { fRRectStart = DefaultRRectDirAndStartIndex(rrect, false, &fRRectDir); this->attemptToReduceFromRRect(); } explicit GrShape(const SkRect& rect) : fType(Type::kRRect) - , fRRect(SkRRect::MakeRect(rect)) { + , fRRect(SkRRect::MakeRect(rect)) + , fRRectIsInverted(false) { fRRectStart = DefaultRectDirAndStartIndex(rect, false, &fRRectDir); this->attemptToReduceFromRRect(); } @@ -66,18 +68,26 @@ public: GrShape(const SkRRect& rrect, const GrStyle& style) : fType(Type::kRRect) , fRRect(rrect) + , fRRectIsInverted(false) , fStyle(style) { fRRectStart = DefaultRRectDirAndStartIndex(rrect, style.hasPathEffect(), &fRRectDir); this->attemptToReduceFromRRect(); } - GrShape(const SkRRect& rrect, SkPath::Direction dir, unsigned start, const GrStyle& style) + GrShape(const SkRRect& rrect, SkPath::Direction dir, unsigned start, bool inverted, + const GrStyle& style) : fType(Type::kRRect) , fRRect(rrect) + , fRRectIsInverted(inverted) , fStyle(style) { if (style.pathEffect()) { fRRectDir = dir; fRRectStart = start; + if (fRRect.getType() == SkRRect::kRect_Type) { + fRRectStart = (fRRectStart + 1) & 0b110; + } else if (fRRect.getType() == SkRRect::kOval_Type) { + fRRectStart &= 0b110; + } } else { fRRectStart = DefaultRRectDirAndStartIndex(rrect, false, &fRRectDir); } @@ -87,6 +97,7 @@ public: GrShape(const SkRect& rect, const GrStyle& style) : fType(Type::kRRect) , fRRect(SkRRect::MakeRect(rect)) + , fRRectIsInverted(false) , fStyle(style) { fRRectStart = DefaultRectDirAndStartIndex(rect, style.hasPathEffect(), &fRRectDir); this->attemptToReduceFromRRect(); @@ -102,6 +113,7 @@ public: GrShape(const SkRRect& rrect, const SkPaint& paint) : fType(Type::kRRect) , fRRect(rrect) + , fRRectIsInverted(false) , fStyle(paint) { fRRectStart = DefaultRRectDirAndStartIndex(rrect, fStyle.hasPathEffect(), &fRRectDir); this->attemptToReduceFromRRect(); @@ -110,6 +122,7 @@ public: GrShape(const SkRect& rect, const SkPaint& paint) : fType(Type::kRRect) , fRRect(SkRRect::MakeRect(rect)) + , fRRectIsInverted(false) , fStyle(paint) { fRRectStart = DefaultRectDirAndStartIndex(rect, fStyle.hasPathEffect(), &fRRectDir); this->attemptToReduceFromRRect(); @@ -136,7 +149,7 @@ public: } /** Returns the unstyled geometry as a rrect if possible. */ - bool asRRect(SkRRect* rrect, SkPath::Direction* dir, unsigned* start) const { + bool asRRect(SkRRect* rrect, SkPath::Direction* dir, unsigned* start, bool* inverted) const { if (Type::kRRect != fType) { return false; } @@ -149,6 +162,9 @@ public: if (start) { *start = fRRectStart; } + if (inverted) { + *inverted = fRRectIsInverted; + } return true; } @@ -161,6 +177,9 @@ public: case Type::kRRect: out->reset(); out->addRRect(fRRect, fRRectDir, fRRectStart); + if (fRRectIsInverted) { + out->setFillType(SkPath::kInverseWinding_FillType); + } break; case Type::kPath: *out = *fPath.get(); @@ -174,10 +193,16 @@ public: */ bool isEmpty() const { return Type::kEmpty == fType; } - /** Gets the bounds of the geometry without reflecting the shape's styling. */ + /** + * Gets the bounds of the geometry without reflecting the shape's styling. This ignores + * the inverse fill nature of the geometry. + */ const SkRect& bounds() const; - /** Gets the bounds of the geometry reflecting the shape's styling. */ + /** + * Gets the bounds of the geometry reflecting the shape's styling (ignoring inverse fill + * status). + */ void styledBounds(SkRect* bounds) const; /** @@ -245,7 +270,8 @@ private: void attemptToReduceFromPath() { SkASSERT(Type::kPath == fType); fType = AttemptToReduceFromPathImpl(*fPath.get(), &fRRect, &fRRectDir, &fRRectStart, - fStyle.pathEffect(), fStyle.strokeRec()); + &fRRectIsInverted, fStyle.pathEffect(), + fStyle.strokeRec()); if (Type::kPath != fType) { fPath.reset(); fInheritedKey.reset(0); @@ -255,14 +281,24 @@ private: void attemptToReduceFromRRect() { SkASSERT(Type::kRRect == fType); SkASSERT(!fInheritedKey.count()); - if (fRRect.isEmpty()) { + if (fRRectIsInverted) { + if (!fStyle.hasNonDashPathEffect()) { + SkStrokeRec::Style recStyle = fStyle.strokeRec().getStyle(); + if (SkStrokeRec::kStroke_Style == recStyle || + SkStrokeRec::kHairline_Style == recStyle) { + // stroking ignores the path fill rule. + fRRectIsInverted = false; + } + } + } else if (fRRect.isEmpty()) { fType = Type::kEmpty; } } static Type AttemptToReduceFromPathImpl(const SkPath& path, SkRRect* rrect, SkPath::Direction* rrectDir, unsigned* rrectStart, - const SkPathEffect* pe, const SkStrokeRec& strokeRec); + bool* rrectIsInverted, const SkPathEffect* pe, + const SkStrokeRec& strokeRec); static constexpr SkPath::Direction kDefaultRRectDir = SkPath::kCW_Direction; static constexpr unsigned kDefaultRRectStart = 0; @@ -313,6 +349,7 @@ private: SkRRect fRRect; SkPath::Direction fRRectDir; unsigned fRRectStart; + bool fRRectIsInverted; SkTLazy<SkPath> fPath; GrStyle fStyle; SkAutoSTArray<8, uint32_t> fInheritedKey; diff --git a/tests/GrShapeTest.cpp b/tests/GrShapeTest.cpp index 246bab6580..6c23d67548 100644 --- a/tests/GrShapeTest.cpp +++ b/tests/GrShapeTest.cpp @@ -82,6 +82,11 @@ public: this->init(r, scale); } + TestCase(const GrShape& shape, skiatest::Reporter* r, SkScalar scale = SK_Scalar1) + : fBase(shape) { + this->init(r, scale); + } + struct SelfExpectations { bool fPEHasEffect; bool fPEHasValidKey; @@ -121,7 +126,10 @@ private: if (path.isEmpty()) { return; } - REPORTER_ASSERT(r, test_bounds_by_rasterizing(path, bounds)); + // The bounds API explicitly calls out that it does not consider inverseness. + SkPath p = path; + p.setFillType(SkPath::ConvertToNonInverseFillType(path.getFillType())); + REPORTER_ASSERT(r, test_bounds_by_rasterizing(p, bounds)); } void init(skiatest::Reporter* r, SkScalar scale) { @@ -147,7 +155,7 @@ private: // fAppliedPEThenStroke will have converted the rrect_as_path back to a rrect. However, // now that there is no longer a path effect, the direction and starting index get // canonicalized before the stroke. - if (fAppliedPE.asRRect(nullptr, nullptr, nullptr)) { + if (fAppliedPE.asRRect(nullptr, nullptr, nullptr, nullptr)) { REPORTER_ASSERT(r, paths_fill_same(a, b)); } else { REPORTER_ASSERT(r, a == b); @@ -261,26 +269,56 @@ void check_equivalence(skiatest::Reporter* r, const GrShape& a, const GrShape& b SkRRect rrectA = SkRRect::MakeEmpty(), rrectB = SkRRect::MakeEmpty(); SkPath::Direction dirA = SkPath::kCW_Direction, dirB = SkPath::kCW_Direction; unsigned startA = ~0U, startB = ~0U; + bool invertedA = true, invertedB = true; - bool aIsRRect = a.asRRect(&rrectA, &dirA, &startA); - bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB); + bool aIsRRect = a.asRRect(&rrectA, &dirA, &startA, &invertedA); + bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB, &invertedB); bool aHasPE = a.style().hasPathEffect(); bool bHasPE = b.style().hasPathEffect(); bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE); + SkPath pathA, pathB; a.asPath(&pathA); b.asPath(&pathB); + + // Having a fill style or non-dash path effect can prevent 'a' but not 'b' from turning an + // inverse fill type into a non-inverse fill type. + bool ignoreInversenessDifference = false; + if (pathA.isInverseFillType() != pathB.isInverseFillType()) { + const GrShape* s1 = pathA.isInverseFillType() ? &a : &b; + const GrShape* s2 = pathA.isInverseFillType() ? &b : &a; + SkStrokeRec::Style style1 = s1->style().strokeRec().getStyle(); + SkStrokeRec::Style style2 = s2->style().strokeRec().getStyle(); + bool canDropInverse1 = !s1->style().hasNonDashPathEffect() && + (SkStrokeRec::kStroke_Style == style1 || + SkStrokeRec::kHairline_Style == style1); + bool canDropInverse2 = !s2->style().hasNonDashPathEffect() && + (SkStrokeRec::kStroke_Style == style2 || + SkStrokeRec::kHairline_Style == style2); + ignoreInversenessDifference = !canDropInverse1 && canDropInverse2; + } + if (allowSameRRectButDiffStartAndDir) { REPORTER_ASSERT(r, rrectA == rrectB); REPORTER_ASSERT(r, paths_fill_same(pathA, pathB)); + REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB); } else { - REPORTER_ASSERT(r, pathA == pathB); - REPORTER_ASSERT(r, keyA == keyB); + SkPath pA = pathA; + SkPath pB = pathB; + if (ignoreInversenessDifference) { + pA.setFillType(SkPath::ConvertToNonInverseFillType(pathA.getFillType())); + pB.setFillType(SkPath::ConvertToNonInverseFillType(pathB.getFillType())); + REPORTER_ASSERT(r, keyA != keyB); + } else { + REPORTER_ASSERT(r, keyA == keyB); + } + REPORTER_ASSERT(r, pA == pB); REPORTER_ASSERT(r, aIsRRect == bIsRRect); if (aIsRRect) { REPORTER_ASSERT(r, rrectA == rrectB); REPORTER_ASSERT(r, dirA == dirB); REPORTER_ASSERT(r, startA == startB); + REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB); } } REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty()); @@ -458,11 +496,11 @@ static void test_scale(skiatest::Reporter* reporter, const GEO& geo) { // Scale affects the stroke. Though, this can wind up creating a rect when the input is a rect. // In that case we wind up with a pure geometry key and the geometries are the same. SkRRect rrect; - if (strokeAndFillCase1.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr)) { + if (strokeAndFillCase1.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr, nullptr)) { // We currently only expect to get here in the rect->rect case. REPORTER_ASSERT(reporter, rrect.isRect()); REPORTER_ASSERT(reporter, - strokeAndFillCase1.baseShape().asRRect(&rrect, nullptr, nullptr) && + strokeAndFillCase1.baseShape().asRRect(&rrect, nullptr, nullptr, nullptr) && rrect.isRect()); strokeAndFillCase1.compare(reporter, strokeAndFillCase2, TestCase::kAllSame_ComparisonExpecation); @@ -687,22 +725,24 @@ void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const GEO& geo) SkRRect rrect; // Applying the path effect should make a SkRRect shape. There is no further stroking in the // geoPECase, so the full style should be the same as just the PE. - REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr)); + REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr, + nullptr)); REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect()); REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey()); - REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr)); + REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr, + nullptr)); REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect()); REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == rrectFillCase.baseKey()); // In the PE+stroke case applying the full style should be the same as just stroking the rrect. - REPORTER_ASSERT(reporter, - geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr)); + REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr, + nullptr, nullptr)); REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect()); REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey()); - REPORTER_ASSERT(reporter, - !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr)); + REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr, + nullptr, nullptr)); REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == rrectStrokeCase.appliedFullStyleKey()); } @@ -761,7 +801,7 @@ void test_make_hairline_path_effect(skiatest::Reporter* reporter, const GEO& geo strokeRec->setHairlineStyle(); return true; } - void computeFastBounds(SkRect* dst, const SkRect& src) const override { *dst = src; } + void computeFastBounds(SkRect* dst, const SkRect& src) const override { *dst = src; } static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new MakeHairlinePathEffect); } @@ -988,6 +1028,204 @@ void test_empty_shape(skiatest::Reporter* reporter) { TestCase::kAllSame_ComparisonExpecation); } +// rect and oval types have rrect start indices that collapse to the same point. Here we select the +// canonical point in these cases. +unsigned canonicalize_rrect_start(int s, const SkRRect& rrect) { + switch (rrect.getType()) { + case SkRRect::kRect_Type: + return (s + 1) & 0b110; + case SkRRect::kOval_Type: + return s & 0b110; + default: + return s; + } +} + +void test_rrect(skiatest::Reporter* r, const SkRRect& rrect) { + enum { + kFill, + kStroke, + kHairline, + kStrokeAndFill + }; + + // SkStrokeRec has no default cons., so init with kFill before calling the setters below. + SkStrokeRec strokeRecs[4] { SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle, + SkStrokeRec::kFill_InitStyle, SkStrokeRec::kFill_InitStyle}; + strokeRecs[kFill].setFillStyle(); + strokeRecs[kStroke].setStrokeStyle(2.f); + strokeRecs[kHairline].setHairlineStyle(); + strokeRecs[kStrokeAndFill].setStrokeStyle(3.f, true); + sk_sp<SkPathEffect> dashEffect = make_dash(); + + GrShape shapes[2 /* inverted */] + [2 /* direction */] + [8 /* start index */] + [SK_ARRAY_COUNT(strokeRecs)] + [2 /* dash */]; + for (int inverted = 0; inverted < 2; ++inverted) { + for (int ccw = 0; ccw < 2; ++ccw) { + for (unsigned s = 0; s < 8; ++s) { + for (size_t style = 0; style < SK_ARRAY_COUNT(strokeRecs); ++style) { + for (int dash = 0; dash < 2; ++dash) { + SkPath::Direction dir = ccw ? SkPath::kCCW_Direction + : SkPath::kCW_Direction; + SkPathEffect* pe = dash ? dashEffect.get() : nullptr; + shapes[inverted][ccw][s][style][dash] = + GrShape(rrect, dir, s, SkToBool(inverted), + GrStyle(strokeRecs[style], pe)); + } + } + } + } + } + + const GrShape& exampleFillCase = shapes[0][0][0][kFill][0]; + Key exampleFillCaseKey; + make_key(&exampleFillCaseKey, exampleFillCase); + + const GrShape& exampleStrokeAndFillCase = shapes[0][0][0][kStrokeAndFill][0]; + Key exampleStrokeAndFillCaseKey; + make_key(&exampleStrokeAndFillCaseKey, exampleStrokeAndFillCase); + + const GrShape& exampleInvFillCase = shapes[1][0][0][kFill][0]; + Key exampleInvFillCaseKey; + make_key(&exampleInvFillCaseKey, exampleInvFillCase); + + const GrShape& exampleInvStrokeAndFillCase = shapes[1][0][0][kStrokeAndFill][0]; + Key exampleInvStrokeAndFillCaseKey; + make_key(&exampleInvStrokeAndFillCaseKey, exampleInvStrokeAndFillCase); + + const GrShape& exampleStrokeCase = shapes[0][0][0][kStroke][0]; + Key exampleStrokeCaseKey; + make_key(&exampleStrokeCaseKey, exampleStrokeCase); + + const GrShape& exampleHairlineCase = shapes[0][0][0][kHairline][0]; + Key exampleHairlineCaseKey; + make_key(&exampleHairlineCaseKey, exampleHairlineCase); + + // These are dummy initializations to suppress warnings. + SkRRect rr = SkRRect::MakeEmpty(); + SkPath::Direction dir = SkPath::kCW_Direction; + unsigned start = ~0U; + bool inv = true; + + REPORTER_ASSERT(r, exampleFillCase.asRRect(&rr, &dir, &start, &inv)); + REPORTER_ASSERT(r, rr == rrect); + REPORTER_ASSERT(r, SkPath::kCW_Direction == dir); + REPORTER_ASSERT(r, 0 == start); + REPORTER_ASSERT(r, !inv); + + REPORTER_ASSERT(r, exampleInvFillCase.asRRect(&rr, &dir, &start, &inv)); + REPORTER_ASSERT(r, rr == rrect); + REPORTER_ASSERT(r, SkPath::kCW_Direction == dir); + REPORTER_ASSERT(r, 0 == start); + REPORTER_ASSERT(r, inv); + + REPORTER_ASSERT(r, exampleStrokeAndFillCase.asRRect(&rr, &dir, &start, &inv)); + REPORTER_ASSERT(r, rr == rrect); + REPORTER_ASSERT(r, SkPath::kCW_Direction == dir); + REPORTER_ASSERT(r, 0 == start); + REPORTER_ASSERT(r, !inv); + + REPORTER_ASSERT(r, exampleInvStrokeAndFillCase.asRRect(&rr, &dir, &start, &inv)); + REPORTER_ASSERT(r, rr == rrect); + REPORTER_ASSERT(r, SkPath::kCW_Direction == dir); + REPORTER_ASSERT(r, 0 == start); + REPORTER_ASSERT(r, inv); + + REPORTER_ASSERT(r, exampleHairlineCase.asRRect(&rr, &dir, &start, &inv)); + REPORTER_ASSERT(r, rr == rrect); + REPORTER_ASSERT(r, SkPath::kCW_Direction == dir); + REPORTER_ASSERT(r, 0 == start); + REPORTER_ASSERT(r, !inv); + + REPORTER_ASSERT(r, exampleStrokeCase.asRRect(&rr, &dir, &start, &inv)); + REPORTER_ASSERT(r, rr == rrect); + REPORTER_ASSERT(r, SkPath::kCW_Direction == dir); + REPORTER_ASSERT(r, 0 == start); + REPORTER_ASSERT(r, !inv); + + // Remember that the key reflects the geometry before styling is applied. + REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvFillCaseKey); + REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeAndFillCaseKey); + REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvStrokeAndFillCaseKey); + REPORTER_ASSERT(r, exampleFillCaseKey == exampleStrokeCaseKey); + REPORTER_ASSERT(r, exampleFillCaseKey == exampleHairlineCaseKey); + REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvFillCaseKey); + + for (int inverted = 0; inverted < 2; ++inverted) { + for (int ccw = 0; ccw < 2; ++ccw) { + for (unsigned s = 0; s < 8; ++s) { + for (int dash = 0; dash < 2; ++dash) { + const GrShape& fillCase = shapes[inverted][ccw][s][kFill][dash]; + Key fillCaseKey; + make_key(&fillCaseKey, fillCase); + + const GrShape& strokeAndFillCase = + shapes[inverted][ccw][s][kStrokeAndFill][dash]; + Key strokeAndFillCaseKey; + make_key(&strokeAndFillCaseKey, strokeAndFillCase); + + // Both fill and stroke-and-fill shapes must respect the inverseness and both + // ignore dashing. + REPORTER_ASSERT(r, !fillCase.style().pathEffect()); + REPORTER_ASSERT(r, !strokeAndFillCase.style().pathEffect()); + TestCase a(fillCase, r); + TestCase b(inverted ? exampleInvFillCase : exampleFillCase, r); + TestCase c(strokeAndFillCase, r); + TestCase d(inverted ? exampleInvStrokeAndFillCase + : exampleStrokeAndFillCase, r); + a.compare(r, b, TestCase::kAllSame_ComparisonExpecation); + c.compare(r, d, TestCase::kAllSame_ComparisonExpecation); + + const GrShape& strokeCase = shapes[inverted][ccw][s][kStroke][dash]; + const GrShape& hairlineCase = shapes[inverted][ccw][s][kHairline][dash]; + + TestCase e(strokeCase, r); + TestCase f(exampleStrokeCase, r); + TestCase g(hairlineCase, r); + TestCase h(exampleHairlineCase, r); + + // Both hairline and stroke shapes must respect the dashing and both + // ignore inverseness. + if (dash) { + unsigned expectedStart = canonicalize_rrect_start(s, rrect); + REPORTER_ASSERT(r, strokeCase.style().pathEffect()); + REPORTER_ASSERT(r, hairlineCase.style().pathEffect()); + + REPORTER_ASSERT(r, strokeCase.asRRect(&rr, &dir, &start, &inv)); + REPORTER_ASSERT(r, rr == rrect); + REPORTER_ASSERT(r, (SkPath::kCCW_Direction == dir) == ccw); + REPORTER_ASSERT(r, start == expectedStart); + REPORTER_ASSERT(r, !inv); + REPORTER_ASSERT(r, hairlineCase.asRRect(&rr, &dir, &start, &inv)); + REPORTER_ASSERT(r, rr == rrect); + REPORTER_ASSERT(r, (SkPath::kCCW_Direction == dir) == ccw); + REPORTER_ASSERT(r, start == expectedStart); + REPORTER_ASSERT(r, !inv); + + // The pre-style case for the dash will match the non-dash example iff the + // dir and start match (dir=cw, start=0). + if (0 == expectedStart && 0 == ccw) { + e.compare(r, f, TestCase::kSameUpToPE_ComparisonExpecation); + g.compare(r, h, TestCase::kSameUpToPE_ComparisonExpecation); + } else { + e.compare(r, f, TestCase::kAllDifferent_ComparisonExpecation); + g.compare(r, h, TestCase::kAllDifferent_ComparisonExpecation); + } + } else { + REPORTER_ASSERT(r, !strokeCase.style().pathEffect()); + REPORTER_ASSERT(r, !hairlineCase.style().pathEffect()); + e.compare(r, f, TestCase::kAllSame_ComparisonExpecation); + g.compare(r, h, TestCase::kAllSame_ComparisonExpecation); + } + } + } + } + } +} + DEF_TEST(GrShape, reporter) { for (auto r : { SkRect::MakeWH(10, 20), SkRect::MakeWH(-10, -20), @@ -1019,6 +1257,7 @@ DEF_TEST(GrShape, reporter) { SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4), SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) { test_basic(reporter, rr); + test_rrect(reporter, rr); test_scale(reporter, rr); test_dash_fill(reporter, rr); test_null_dash(reporter, rr); @@ -1041,7 +1280,7 @@ DEF_TEST(GrShape, reporter) { } struct TestPath { - TestPath(const SkPath& path, bool isRRectFill, bool isRRectStroke ,const SkRRect& rrect) + TestPath(const SkPath& path, bool isRRectFill, bool isRRectStroke, const SkRRect& rrect) : fPath(path) , fIsRRectForFill(isRRectFill) , fIsRRectForStroke(isRRectStroke) @@ -1073,53 +1312,69 @@ DEF_TEST(GrShape, reporter) { paths.emplace_back(quadPath, false, false, SkRRect()); for (auto testPath : paths) { - const SkPath& path = testPath.fPath; - // These tests all assume that the original GrShape for fill and stroke will be the same. - // However, that is not the case in special cases (e.g. a unclosed rect becomes a RRect - // GrShape with a fill style but becomes a Path GrShape when stroked). - if (testPath.fIsRRectForFill == testPath.fIsRRectForStroke) { - test_basic(reporter, path); - test_null_dash(reporter, path); - test_path_effect_makes_rrect(reporter, path); + for (bool inverseFill : {false, true}) { + if (inverseFill) { + if (testPath.fPath.getFillType() == SkPath::kEvenOdd_FillType) { + testPath.fPath.setFillType(SkPath::kInverseEvenOdd_FillType); + } else { + SkASSERT(testPath.fPath.getFillType() == SkPath::kWinding_FillType); + testPath.fPath.setFillType(SkPath::kInverseWinding_FillType); + } + } + const SkPath& path = testPath.fPath; + // These tests all assume that the original GrShape for fill and stroke will be the + // same. + // However, that is not the case in special cases (e.g. an unclosed rect becomes a RRect + // GrShape with a fill style but becomes a Path GrShape when stroked). + if (testPath.fIsRRectForFill == testPath.fIsRRectForStroke) { + test_basic(reporter, path); + test_null_dash(reporter, path); + test_path_effect_makes_rrect(reporter, path); + } + test_scale(reporter, path); + // This test uses a stroking paint, hence use of fIsRRectForStroke + test_volatile_path(reporter, path, testPath.fIsRRectForStroke); + test_dash_fill(reporter, path); + // Test modifying various stroke params. + test_stroke_param<SkPath, SkScalar>( + reporter, path, + [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);}, + SkIntToScalar(2), SkIntToScalar(4)); + test_stroke_param<SkPath, SkPaint::Join>( + reporter, path, + [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);}, + SkPaint::kMiter_Join, SkPaint::kRound_Join); + test_stroke_cap(reporter, path); + test_miter_limit(reporter, path); + test_unknown_path_effect(reporter, path); + test_path_effect_makes_empty_shape(reporter, path); + test_path_effect_fails(reporter, path); + test_make_hairline_path_effect(reporter, path, testPath.fIsRRectForStroke); } - test_scale(reporter, path); - // This test uses a stroking paint, hence use of fIsRRectForStroke - test_volatile_path(reporter, path, testPath.fIsRRectForStroke); - test_dash_fill(reporter, path); - // Test modifying various stroke params. - test_stroke_param<SkPath, SkScalar>( - reporter, path, - [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);}, - SkIntToScalar(2), SkIntToScalar(4)); - test_stroke_param<SkPath, SkPaint::Join>( - reporter, path, - [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);}, - SkPaint::kMiter_Join, SkPaint::kRound_Join); - test_stroke_cap(reporter, path); - test_miter_limit(reporter, path); - test_unknown_path_effect(reporter, path); - test_path_effect_makes_empty_shape(reporter, path); - test_path_effect_fails(reporter, path); - test_make_hairline_path_effect(reporter, path, testPath.fIsRRectForStroke); + } + for (auto testPath : paths) { + const SkPath& path = testPath.fPath; SkPaint fillPaint; TestCase fillPathCase(path, fillPaint, reporter); SkRRect rrect; REPORTER_ASSERT(reporter, testPath.fIsRRectForFill == - fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr)); + fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr, + nullptr)); if (testPath.fIsRRectForFill) { - TestCase fillPathCase2(path, fillPaint, reporter); + TestCase fillPathCase2(testPath.fPath, fillPaint, reporter); REPORTER_ASSERT(reporter, rrect == testPath.fRRect); TestCase fillRRectCase(rrect, fillPaint, reporter); - fillPathCase2.compare(reporter, fillRRectCase, TestCase::kAllSame_ComparisonExpecation); + fillPathCase2.compare(reporter, fillRRectCase, + TestCase::kAllSame_ComparisonExpecation); } - SkPaint strokePaint; strokePaint.setStrokeWidth(3.f); strokePaint.setStyle(SkPaint::kStroke_Style); TestCase strokePathCase(path, strokePaint, reporter); REPORTER_ASSERT(reporter, testPath.fIsRRectForStroke == - strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr)); + strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr, + nullptr)); if (testPath.fIsRRectForStroke) { REPORTER_ASSERT(reporter, rrect == testPath.fRRect); TestCase strokeRRectCase(rrect, strokePaint, reporter); |