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
This commit is contained in:
bsalomon 2016-06-10 08:05:14 -07:00 committed by Commit bot
parent 013e0e6d48
commit 7049396b65
3 changed files with 383 additions and 61 deletions

View File

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

View File

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

View File

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