diff --git a/tests/GrShapeTest.cpp b/tests/GrShapeTest.cpp index 9246c73f40..8ac27b4f98 100644 --- a/tests/GrShapeTest.cpp +++ b/tests/GrShapeTest.cpp @@ -74,14 +74,186 @@ static bool test_bounds_by_rasterizing(const SkPath& path, const SkRect& bounds) } namespace { +/** + * Geo is a factory for creating a GrShape from another representation. It also answers some + * questions about expected behavior for GrShape given the inputs. + */ +class Geo { +public: + virtual ~Geo() {}; + virtual GrShape makeShape(const SkPaint&) const = 0; + virtual SkPath path() const = 0; + // These functions allow tests to check for special cases where style gets + // applied by GrShape in its constructor (without calling GrShape::applyStyle). + // These unfortunately rely on knowing details of GrShape's implementation. + // These predicates are factored out here to avoid littering the rest of the + // test code with GrShape implementation details. + virtual bool fillChangesGeom() const { return false; } + virtual bool strokeIsConvertedToFill() const { return false; } + virtual bool strokeAndFillIsConvertedToFill(const SkPaint&) const { return false; } + // Is this something we expect GrShape to recognize as something simpler than a path. + virtual bool isNonPath(const SkPaint& paint) const { return true; } +}; + +class RectGeo : public Geo { +public: + RectGeo(const SkRect& rect) : fRect(rect) {} + + SkPath path() const override { + SkPath path; + path.addRect(fRect); + return path; + } + + GrShape makeShape(const SkPaint& paint) const override { + return GrShape(fRect, paint); + } + + bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override { + SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style); + // Converted to an outset rectangle. + return paint.getStrokeJoin() == SkPaint::kMiter_Join && + paint.getStrokeMiter() >= SK_ScalarSqrt2; + } + +private: + SkRect fRect; +}; + +class RRectGeo : public Geo { +public: + RRectGeo(const SkRRect& rrect) : fRRect(rrect) {} + + GrShape makeShape(const SkPaint& paint) const override { + return GrShape(fRRect, paint); + } + + SkPath path() const override { + SkPath path; + path.addRRect(fRRect); + return path; + } + + bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override { + SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style); + if (fRRect.isRect()) { + return RectGeo(fRRect.rect()).strokeAndFillIsConvertedToFill(paint); + } + return false; + } + +private: + SkRRect fRRect; +}; + +class PathGeo : public Geo { +public: + enum class Invert { kNo, kYes }; + + PathGeo(const SkPath& path, Invert invert) : fPath(path) { + SkASSERT(!path.isInverseFillType()); + if (Invert::kYes == invert) { + if (fPath.getFillType() == SkPath::kEvenOdd_FillType) { + fPath.setFillType(SkPath::kInverseEvenOdd_FillType); + } else { + SkASSERT(fPath.getFillType() == SkPath::kWinding_FillType); + fPath.setFillType(SkPath::kInverseWinding_FillType); + } + } + } + + GrShape makeShape(const SkPaint& paint) const override { + return GrShape(fPath, paint); + } + + SkPath path() const override { return fPath; } + + bool fillChangesGeom() const override { + // unclosed rects get closed. Lines get turned into empty geometry + return this->isUnclosedRect() || (fPath.isLine(nullptr) && !fPath.isInverseFillType()); + } + + bool strokeIsConvertedToFill() const override { + return this->isAxisAlignedLine(); + } + + bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override { + SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style); + if (this->isAxisAlignedLine()) { + // The fill is ignored (zero area) and the stroke is converted to a rrect. + return true; + } + SkRect rect; + unsigned start; + SkPath::Direction dir; + if (SkPathPriv::IsSimpleClosedRect(fPath, &rect, &dir, &start)) { + return RectGeo(rect).strokeAndFillIsConvertedToFill(paint); + } + return false; + } + + bool isNonPath(const SkPaint& paint) const override { + return fPath.isLine(nullptr) || fPath.isEmpty(); + } + +private: + bool isAxisAlignedLine() const { + SkPoint pts[2]; + if (!fPath.isLine(pts)) { + return false; + } + return pts[0].fX == pts[1].fX || pts[0].fY == pts[1].fY; + } + + bool isUnclosedRect() const { + bool closed; + return fPath.isRect(nullptr, &closed, nullptr) && !closed; + } + + SkPath fPath; +}; + +class RRectPathGeo : public PathGeo { +public: + enum class RRectForStroke { kNo, kYes }; + + RRectPathGeo(const SkPath& path, const SkRRect& equivalentRRect, RRectForStroke rrectForStroke, + Invert invert) + : PathGeo(path, invert) + , fRRect(equivalentRRect) + , fRRectForStroke(rrectForStroke) {} + + RRectPathGeo(const SkPath& path, const SkRect& equivalentRect, RRectForStroke rrectForStroke, + Invert invert) + : RRectPathGeo(path, SkRRect::MakeRect(equivalentRect), rrectForStroke, invert) {} + + bool isNonPath(const SkPaint& paint) const override { + if (SkPaint::kFill_Style == paint.getStyle() || RRectForStroke::kYes == fRRectForStroke) { + return true; + } + return false; + } + + const SkRRect& rrect() const { return fRRect; } + +private: + SkRRect fRRect; + RRectForStroke fRRectForStroke; +}; + class TestCase { public: - template - TestCase(const GEO& geo, const SkPaint& paint, skiatest::Reporter* r, - SkScalar scale = SK_Scalar1) : fBase(geo, paint) { + TestCase(const Geo& geo, const SkPaint& paint, skiatest::Reporter* r, + SkScalar scale = SK_Scalar1) : fBase(geo.makeShape(paint)) { this->init(r, scale); } + template + TestCase(skiatest::Reporter* r, ShapeArgs... shapeArgs) + : fBase(shapeArgs...) { + this->init(r, SK_Scalar1); + } + TestCase(const GrShape& shape, skiatest::Reporter* r, SkScalar scale = SK_Scalar1) : fBase(shape) { this->init(r, scale); @@ -433,78 +605,7 @@ static sk_sp make_null_dash() { return SkDashPathEffect::Make(kNullIntervals, SK_ARRAY_COUNT(kNullIntervals), 0.f); } -////////////////////////////////////////////////////////////////////////////// -// These functions allow tests to check for special cases where style gets -// applied by GrShape in its constructor (without calling GrShape::applyStyle). -// These unfortunately rely on knowing details of GrShape's implementation. -// These predicates are factored out here to avoid littering the rest of the -// test code with GrShape implementation details. - -static bool path_is_axis_aligned_line(const SkPath& path) { - SkPoint pts[2]; - if (!path.isLine(pts)) { - return false; - } - return pts[0].fX == pts[1].fX || pts[0].fY == pts[1].fY; -} - -static bool path_is_unclosed_rect(const SkPath& path) { - bool closed; - return path.isRect(nullptr, &closed, nullptr) && !closed; -} - -// Will a GrShape constructed from a geometry perform a geometric transformation if the style is -// simple fill that would not otherwise be applied. -template static bool fill_changes_geom(const GEO& geo) { return false; } -template <> bool fill_changes_geom(const SkPath& path) { - // unclosed rects get closed. Lines get turned into empty geometry - return path_is_unclosed_rect(path) || (path.isLine(nullptr) && !path.isInverseFillType()); -} - -// Will a GrShape constructed from the geometry with a stroke style (without path effect) perform a -// geometric transformation that applies the the stroke immediately without storing a stroke style. -template static bool stroke_is_converted_to_fill(const GEO& geo) { return false; } -template <> bool stroke_is_converted_to_fill(const SkPath& path) { - // converted to a rrect. - return path_is_axis_aligned_line(path); -} - -// Will a GrShape constructed from the geometry with a stroke-and-fill style (without path effect) -// perform a geometric transformation that applies the the stroke immediately without storing a -// stroke-and-fill style. -template static bool stroke_and_fill_is_converted_to_fill(const GEO& geo, const SkPaint& paint); -template <> bool stroke_and_fill_is_converted_to_fill(const SkRect& rect, const SkPaint& paint) { - SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style); - // Converted to an outset rectangle. - return paint.getStrokeJoin() == SkPaint::kMiter_Join && - paint.getStrokeMiter() >= SK_ScalarSqrt2; -} -template <> bool stroke_and_fill_is_converted_to_fill(const SkPath& path, const SkPaint& paint) { - SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style); - if (path_is_axis_aligned_line(path)) { - // The fill is ignored (zero area) and the stroke is converted to a rrect. - return true; - } - SkRect rect; - unsigned start; - SkPath::Direction dir; - if (SkPathPriv::IsSimpleClosedRect(path, &rect, &dir, &start)) { - return stroke_and_fill_is_converted_to_fill(rect, paint); - } - return false; -} -template <> bool stroke_and_fill_is_converted_to_fill(const SkRRect& rr, const SkPaint& paint) { - SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style); - if (rr.isRect()) { - return stroke_and_fill_is_converted_to_fill(rr.rect(), paint); - } - return false; -} - -////////////////////////////////////////////////////////////////////////////// - -template -static void test_basic(skiatest::Reporter* reporter, const GEO& geo) { +static void test_basic(skiatest::Reporter* reporter, const Geo& geo) { sk_sp dashPE = make_dash(); TestCase::SelfExpectations expectations; @@ -527,7 +628,7 @@ static void test_basic(skiatest::Reporter* reporter, const GEO& geo) { TestCase stroke2RoundBevelCase(geo, stroke2RoundBevel, reporter); expectations.fPEHasValidKey = true; expectations.fPEHasEffect = false; - expectations.fStrokeApplies = !stroke_is_converted_to_fill(geo); + expectations.fStrokeApplies = !geo.strokeIsConvertedToFill(); stroke2RoundBevelCase.testExpectations(reporter, expectations); TestCase(geo, stroke2RoundBevel, reporter).compare(reporter, stroke2RoundBevelCase, TestCase::kAllSame_ComparisonExpecation); @@ -542,7 +643,7 @@ static void test_basic(skiatest::Reporter* reporter, const GEO& geo) { TestCase(geo, stroke2RoundBevelDash, reporter).compare(reporter, stroke2RoundBevelDashCase, TestCase::kAllSame_ComparisonExpecation); - if (fill_changes_geom(geo) || stroke_is_converted_to_fill(geo)) { + if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) { fillCase.compare(reporter, stroke2RoundBevelCase, TestCase::kAllDifferent_ComparisonExpecation); fillCase.compare(reporter, stroke2RoundBevelDashCase, @@ -553,7 +654,7 @@ static void test_basic(skiatest::Reporter* reporter, const GEO& geo) { fillCase.compare(reporter, stroke2RoundBevelDashCase, TestCase::kSameUpToPE_ComparisonExpecation); } - if (stroke_is_converted_to_fill(geo)) { + if (geo.strokeIsConvertedToFill()) { stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase, TestCase::kAllDifferent_ComparisonExpecation); } else { @@ -567,7 +668,7 @@ static void test_basic(skiatest::Reporter* reporter, const GEO& geo) { TestCase stroke2RoundBevelAndFillCase(geo, stroke2RoundBevelAndFill, reporter); expectations.fPEHasValidKey = true; expectations.fPEHasEffect = false; - expectations.fStrokeApplies = !stroke_is_converted_to_fill(geo); + expectations.fStrokeApplies = !geo.strokeIsConvertedToFill(); stroke2RoundBevelAndFillCase.testExpectations(reporter, expectations); TestCase(geo, stroke2RoundBevelAndFill, reporter).compare(reporter, stroke2RoundBevelAndFillCase, TestCase::kAllSame_ComparisonExpecation); @@ -577,7 +678,7 @@ static void test_basic(skiatest::Reporter* reporter, const GEO& geo) { TestCase stroke2RoundBevelAndFillDashCase(geo, stroke2RoundBevelAndFillDash, reporter); expectations.fPEHasValidKey = true; expectations.fPEHasEffect = false; - expectations.fStrokeApplies = !stroke_is_converted_to_fill(geo); + expectations.fStrokeApplies = !geo.strokeIsConvertedToFill(); stroke2RoundBevelAndFillDashCase.testExpectations(reporter, expectations); TestCase(geo, stroke2RoundBevelAndFillDash, reporter).compare( reporter, stroke2RoundBevelAndFillDashCase, TestCase::kAllSame_ComparisonExpecation); @@ -590,7 +691,7 @@ static void test_basic(skiatest::Reporter* reporter, const GEO& geo) { TestCase hairlineCase(geo, hairline, reporter); // Since hairline style doesn't change the SkPath data, it is keyed identically to fill (except // in the line and unclosed rect cases). - if (fill_changes_geom(geo)) { + if (geo.fillChangesGeom()) { hairlineCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation); } else { hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation); @@ -601,8 +702,7 @@ static void test_basic(skiatest::Reporter* reporter, const GEO& geo) { } -template -static void test_scale(skiatest::Reporter* reporter, const GEO& geo) { +static void test_scale(skiatest::Reporter* reporter, const Geo& geo) { sk_sp dashPE = make_dash(); static const SkScalar kS1 = 1.f; @@ -628,7 +728,7 @@ static void test_scale(skiatest::Reporter* reporter, const GEO& geo) { TestCase strokeCase1(geo, stroke, reporter, kS1); TestCase strokeCase2(geo, stroke, reporter, kS2); // Scale affects the stroke - if (stroke_is_converted_to_fill(geo)) { + if (geo.strokeIsConvertedToFill()) { REPORTER_ASSERT(reporter, !strokeCase1.baseShape().style().applies()); strokeCase1.compare(reporter, strokeCase2, TestCase::kAllSame_ComparisonExpecation); } else { @@ -656,7 +756,7 @@ static void test_scale(skiatest::Reporter* reporter, const GEO& geo) { // Scale affects the stroke, but check to make sure this didn't become a simpler shape (e.g. // stroke-and-filled rect can become a rect), in which case the scale shouldn't matter and the // geometries should agree. - if (stroke_and_fill_is_converted_to_fill(geo, strokeAndFillDash)) { + if (geo.strokeAndFillIsConvertedToFill(strokeAndFillDash)) { REPORTER_ASSERT(reporter, !strokeAndFillCase1.baseShape().style().applies()); strokeAndFillCase1.compare(reporter, strokeAndFillCase2, TestCase::kAllSame_ComparisonExpecation); @@ -672,8 +772,8 @@ static void test_scale(skiatest::Reporter* reporter, const GEO& geo) { TestCase::kAllSame_ComparisonExpecation); } -template -static void test_stroke_param_impl(skiatest::Reporter* reporter, const GEO& geo, +template +static void test_stroke_param_impl(skiatest::Reporter* reporter, const Geo& geo, std::function setter, T a, T b, bool paramAffectsStroke, bool paramAffectsDashAndStroke) { @@ -693,7 +793,7 @@ static void test_stroke_param_impl(skiatest::Reporter* reporter, const GEO& geo, if (paramAffectsStroke) { // If stroking is immediately incorporated into a geometric transformation then the base // shapes will differ. - if (stroke_is_converted_to_fill(geo)) { + if (geo.strokeIsConvertedToFill()) { strokeACase.compare(reporter, strokeBCase, TestCase::kAllDifferent_ComparisonExpecation); } else { @@ -713,8 +813,8 @@ static void test_stroke_param_impl(skiatest::Reporter* reporter, const GEO& geo, if (paramAffectsStroke) { // If stroking is immediately incorporated into a geometric transformation then the base // shapes will differ. - if (stroke_and_fill_is_converted_to_fill(geo, strokeAndFillA) || - stroke_and_fill_is_converted_to_fill(geo, strokeAndFillB)) { + if (geo.strokeAndFillIsConvertedToFill(strokeAndFillA) || + geo.strokeAndFillIsConvertedToFill(strokeAndFillB)) { strokeAndFillACase.compare(reporter, strokeAndFillBCase, TestCase::kAllDifferent_ComparisonExpecation); } else { @@ -748,20 +848,22 @@ static void test_stroke_param_impl(skiatest::Reporter* reporter, const GEO& geo, } } -template -static void test_stroke_param(skiatest::Reporter* reporter, const GEO& geo, +template +static void test_stroke_param(skiatest::Reporter* reporter, const Geo& geo, std::function setter, T a, T b) { test_stroke_param_impl(reporter, geo, setter, a, b, true, true); }; -template -static void test_stroke_cap(skiatest::Reporter* reporter, const GEO& geo) { - GrShape shape(geo, GrStyle(SkStrokeRec::kHairline_InitStyle)); +static void test_stroke_cap(skiatest::Reporter* reporter, const Geo& geo) { + SkPaint hairline; + hairline.setStrokeWidth(0); + hairline.setStyle(SkPaint::kStroke_Style); + GrShape shape = geo.makeShape(hairline); // The cap should only affect shapes that may be open. bool affectsStroke = !shape.knownToBeClosed(); // Dashing adds ends that need caps. bool affectsDashAndStroke = true; - test_stroke_param_impl( + test_stroke_param_impl( reporter, geo, [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);}, @@ -774,15 +876,17 @@ static bool shape_known_not_to_have_joins(const GrShape& shape) { return shape.asLine(nullptr, nullptr) || shape.isEmpty(); } -template -static void test_stroke_join(skiatest::Reporter* reporter, const GEO& geo) { - GrShape shape(geo, GrStyle(SkStrokeRec::kHairline_InitStyle)); +static void test_stroke_join(skiatest::Reporter* reporter, const Geo& geo) { + SkPaint hairline; + hairline.setStrokeWidth(0); + hairline.setStyle(SkPaint::kStroke_Style); + GrShape shape = geo.makeShape(hairline); // GrShape recognizes certain types don't have joins and will prevent the join type from // affecting the style key. // Dashing doesn't add additional joins. However, GrShape currently loses track of this // after applying the dash. bool affectsStroke = !shape_known_not_to_have_joins(shape); - test_stroke_param_impl( + test_stroke_param_impl( reporter, geo, [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);}, @@ -790,8 +894,7 @@ static void test_stroke_join(skiatest::Reporter* reporter, const GEO& geo) { affectsStroke, true); }; -template -static void test_miter_limit(skiatest::Reporter* reporter, const GEO& geo) { +static void test_miter_limit(skiatest::Reporter* reporter, const Geo& geo) { auto setMiterJoinAndLimit = [](SkPaint* p, SkScalar miter) { p->setStrokeJoin(SkPaint::kMiter_Join); p->setStrokeMiter(miter); @@ -802,12 +905,15 @@ static void test_miter_limit(skiatest::Reporter* reporter, const GEO& geo) { p->setStrokeMiter(miter); }; - GrShape shape(geo, GrStyle(SkStrokeRec::kHairline_InitStyle)); + SkPaint hairline; + hairline.setStrokeWidth(0); + hairline.setStyle(SkPaint::kStroke_Style); + GrShape shape = geo.makeShape(hairline); bool mayHaveJoins = !shape_known_not_to_have_joins(shape); // The miter limit should affect stroked and dashed-stroked cases when the join type is // miter. - test_stroke_param_impl( + test_stroke_param_impl( reporter, geo, setMiterJoinAndLimit, @@ -817,7 +923,7 @@ static void test_miter_limit(skiatest::Reporter* reporter, const GEO& geo) { // The miter limit should not affect stroked and dashed-stroked cases when the join type is // not miter. - test_stroke_param_impl( + test_stroke_param_impl( reporter, geo, setOtherJoinAndLimit, @@ -826,8 +932,7 @@ static void test_miter_limit(skiatest::Reporter* reporter, const GEO& geo) { false); } -template -static void test_dash_fill(skiatest::Reporter* reporter, const GEO& geo) { +static void test_dash_fill(skiatest::Reporter* reporter, const Geo& geo) { // A dash with no stroke should have no effect using DashFactoryFn = sk_sp(*)(); for (DashFactoryFn md : {&make_dash, &make_null_dash}) { @@ -840,8 +945,7 @@ static void test_dash_fill(skiatest::Reporter* reporter, const GEO& geo) { } } -template -void test_null_dash(skiatest::Reporter* reporter, const GEO& geo) { +void test_null_dash(skiatest::Reporter* reporter, const Geo& geo) { SkPaint fill; SkPaint stroke; stroke.setStyle(SkPaint::kStroke_Style); @@ -864,21 +968,20 @@ void test_null_dash(skiatest::Reporter* reporter, const GEO& geo) { nullDashCase.compare(reporter, strokeCase, TestCase::kAllSame_ComparisonExpecation); // Check whether the fillCase or strokeCase/nullDashCase would undergo a geometric tranformation // on construction in order to determine how to compare the fill and stroke. - if (fill_changes_geom(geo) || stroke_is_converted_to_fill(geo)) { + if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) { nullDashCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation); } else { nullDashCase.compare(reporter, fillCase, TestCase::kSameUpToStroke_ComparisonExpecation); } // In the null dash case we may immediately convert to a fill, but not for the normal dash case. - if (stroke_is_converted_to_fill(geo)) { + if (geo.strokeIsConvertedToFill()) { nullDashCase.compare(reporter, dashCase, TestCase::kAllDifferent_ComparisonExpecation); } else { nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation); } } -template -void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const GEO& geo) { +void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const Geo& geo) { /** * This path effect takes any input path and turns it into a rrect. It passes through stroke * info. @@ -921,7 +1024,7 @@ void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const GEO& geo) // Check whether constructing the filled case would cause the base shape to have a different // geometry (because of a geometric transformation upon initial GrShape construction). - if (fill_changes_geom(geo)) { + if (geo.fillChangesGeom()) { fillGeoCase.compare(reporter, geoPECase, TestCase::kAllDifferent_ComparisonExpecation); fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kAllDifferent_ComparisonExpecation); @@ -932,10 +1035,10 @@ void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const GEO& geo) geoPECase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToStroke_ComparisonExpecation); - TestCase rrectFillCase(RRectPathEffect::RRect(), fill, reporter); + TestCase rrectFillCase(reporter, RRectPathEffect::RRect(), fill); SkPaint stroke = peStroke; stroke.setPathEffect(nullptr); - TestCase rrectStrokeCase(RRectPathEffect::RRect(), stroke, reporter); + TestCase rrectStrokeCase(reporter, RRectPathEffect::RRect(), stroke); SkRRect rrect; // Applying the path effect should make a SkRRect shape. There is no further stroking in the @@ -962,8 +1065,7 @@ void test_path_effect_makes_rrect(skiatest::Reporter* reporter, const GEO& geo) rrectStrokeCase.appliedFullStyleKey()); } -template -void test_unknown_path_effect(skiatest::Reporter* reporter, const GEO& geo) { +void test_unknown_path_effect(skiatest::Reporter* reporter, const Geo& geo) { /** * This path effect just adds two lineTos to the input path. */ @@ -1003,8 +1105,7 @@ void test_unknown_path_effect(skiatest::Reporter* reporter, const GEO& geo) { geoPEStrokeCase.testExpectations(reporter, expectations); } -template -void test_make_hairline_path_effect(skiatest::Reporter* reporter, const GEO& geo, bool isNonPath) { +void test_make_hairline_path_effect(skiatest::Reporter* reporter, const Geo& geo) { /** * This path effect just changes the stroke rec to hairline. */ @@ -1036,7 +1137,7 @@ void test_make_hairline_path_effect(skiatest::Reporter* reporter, const GEO& geo peCase.baseShape().asPath(&a); peCase.appliedPathEffectShape().asPath(&b); peCase.appliedFullStyleShape().asPath(&c); - if (isNonPath) { + if (geo.isNonPath(pe)) { // RRect types can have a change in start index or direction after the PE is applied. This // is because once the PE is applied, GrShape may canonicalize the dir and index since it // is not germane to the styling any longer. @@ -1058,26 +1159,21 @@ void test_make_hairline_path_effect(skiatest::Reporter* reporter, const GEO& geo REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline()); } -/** - * isNonPath indicates whether the initial shape made from the path is expected to be recognized - * as a simpler shape type (e.g. rrect) - */ -void test_volatile_path(skiatest::Reporter* reporter, const SkPath& path, - bool isNonPath) { - SkPath vPath(path); +void test_volatile_path(skiatest::Reporter* reporter, const Geo& geo) { + SkPath vPath = geo.path(); vPath.setIsVolatile(true); SkPaint dashAndStroke; dashAndStroke.setPathEffect(make_dash()); dashAndStroke.setStrokeWidth(2.f); dashAndStroke.setStyle(SkPaint::kStroke_Style); - TestCase volatileCase(vPath, dashAndStroke, reporter); + TestCase volatileCase(reporter, vPath, dashAndStroke); // We expect a shape made from a volatile path to have a key iff the shape is recognized // as a specialized geometry. - if (isNonPath) { + if (geo.isNonPath(dashAndStroke)) { REPORTER_ASSERT(reporter, SkToBool(volatileCase.baseKey().count())); // In this case all the keys should be identical to the non-volatile case. - TestCase nonVolatileCase(path, dashAndStroke, reporter); + TestCase nonVolatileCase(reporter, geo.path(), dashAndStroke); volatileCase.compare(reporter, nonVolatileCase, TestCase::kAllSame_ComparisonExpecation); } else { // None of the keys should be valid. @@ -1088,8 +1184,7 @@ void test_volatile_path(skiatest::Reporter* reporter, const SkPath& path, } } -template -void test_path_effect_makes_empty_shape(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. */ @@ -1137,8 +1232,7 @@ void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const GEO& REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty()); } -template -void test_path_effect_fails(skiatest::Reporter* reporter, const GEO& geo) { +void test_path_effect_fails(skiatest::Reporter* reporter, const Geo& geo) { /** * This path effect always fails to apply. */ @@ -1203,7 +1297,7 @@ void test_path_effect_fails(skiatest::Reporter* reporter, const GEO& geo) { void test_empty_shape(skiatest::Reporter* reporter) { SkPath emptyPath; SkPaint fill; - TestCase fillEmptyCase(emptyPath, fill, reporter); + TestCase fillEmptyCase(reporter, emptyPath, fill); REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty()); REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty()); REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty()); @@ -1221,7 +1315,7 @@ void test_empty_shape(skiatest::Reporter* reporter) { SkPaint stroke; stroke.setStrokeWidth(2.f); stroke.setStyle(SkPaint::kStroke_Style); - TestCase strokeEmptyCase(emptyPath2, stroke, reporter); + TestCase strokeEmptyCase(reporter, emptyPath2, stroke); strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation); // Dashing and stroking an empty path should have no effect @@ -1230,20 +1324,20 @@ void test_empty_shape(skiatest::Reporter* reporter) { dashAndStroke.setPathEffect(make_dash()); dashAndStroke.setStrokeWidth(2.f); dashAndStroke.setStyle(SkPaint::kStroke_Style); - TestCase dashAndStrokeEmptyCase(emptyPath3, dashAndStroke, reporter); + TestCase dashAndStrokeEmptyCase(reporter, emptyPath3, dashAndStroke); dashAndStrokeEmptyCase.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()); REPORTER_ASSERT(reporter, emptyRRect.getType() == SkRRect::kEmpty_Type); - TestCase dashAndStrokeEmptyRRectCase(emptyRRect, dashAndStroke, reporter); + TestCase dashAndStrokeEmptyRRectCase(reporter, emptyRRect, dashAndStroke); dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation); // Same for a rect. SkRect emptyRect = SkRect::MakeEmpty(); - TestCase dashAndStrokeEmptyRectCase(emptyRect, dashAndStroke, reporter); + TestCase dashAndStrokeEmptyRectCase(reporter, emptyRect, dashAndStroke); dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation); } @@ -1535,22 +1629,22 @@ void test_lines(skiatest::Reporter* r) { SkPaint dash = stroke; dash.setPathEffect(make_dash()); - TestCase fillAB(lineAB, fill, r); - TestCase fillEmpty(SkPath(), fill, r); + TestCase fillAB(r, lineAB, fill); + TestCase fillEmpty(r, SkPath(), fill); fillAB.compare(r, fillEmpty, TestCase::kAllSame_ComparisonExpecation); REPORTER_ASSERT(r, !fillAB.baseShape().asLine(nullptr, nullptr)); - TestCase strokeAB(lineAB, stroke, r); - TestCase strokeBA(lineBA, stroke, r); - TestCase strokeAC(lineAC, stroke, r); + TestCase strokeAB(r, lineAB, stroke); + TestCase strokeBA(r, lineBA, stroke); + TestCase strokeAC(r, lineAC, stroke); - TestCase hairlineAB(lineAB, hairline, r); - TestCase hairlineBA(lineBA, hairline, r); - TestCase hairlineAC(lineAC, hairline, r); + TestCase hairlineAB(r, lineAB, hairline); + TestCase hairlineBA(r, lineBA, hairline); + TestCase hairlineAC(r, lineAC, hairline); - TestCase dashAB(lineAB, dash, r); - TestCase dashBA(lineBA, dash, r); - TestCase dashAC(lineAC, dash, r); + TestCase dashAB(r, lineAB, dash); + TestCase dashBA(r, lineBA, dash); + TestCase dashAC(r, lineAC, dash); strokeAB.compare(r, fillAB, TestCase::kAllDifferent_ComparisonExpecation); @@ -1595,9 +1689,9 @@ void test_lines(skiatest::Reporter* r) { pts[0] == kB && pts[1] == kA); - TestCase strokeInvAB(invLineAB, stroke, r); - TestCase hairlineInvAB(invLineAB, hairline, r); - TestCase dashInvAB(invLineAB, dash, r); + TestCase strokeInvAB(r, invLineAB, stroke); + TestCase hairlineInvAB(r, invLineAB, hairline); + TestCase dashInvAB(r, invLineAB, dash); strokeInvAB.compare(r, strokeAB, TestCase::kAllDifferent_ComparisonExpecation); hairlineInvAB.compare(r, hairlineAB, TestCase::kAllDifferent_ComparisonExpecation); // Dashing ignores inverse. @@ -1633,14 +1727,14 @@ static void test_stroked_lines(skiatest::Reporter* r) { SkPaint fill; - TestCase(linePath, buttCap, r).compare(r, TestCase(SkRect::MakeLTRB(2, 4, 6, 5), fill, r), + TestCase(r, linePath, buttCap).compare(r, TestCase(r, SkRect::MakeLTRB(2, 4, 6, 5), fill), TestCase::kAllSame_ComparisonExpecation); - TestCase(linePath, squareCap, r).compare(r, TestCase(SkRect::MakeLTRB(2, 2, 6, 7), fill, r), + TestCase(r, linePath, squareCap).compare(r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 7), fill), TestCase::kAllSame_ComparisonExpecation); - TestCase(linePath, roundCap, r).compare(r, - TestCase(SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 7), 2, 2), fill, r), + TestCase(r, linePath, roundCap).compare(r, + TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 7), 2, 2), fill), TestCase::kAllSame_ComparisonExpecation); // horizontal @@ -1648,12 +1742,12 @@ static void test_stroked_lines(skiatest::Reporter* r) { linePath.moveTo(4, 4); linePath.lineTo(5, 4); - TestCase(linePath, buttCap, r).compare(r, TestCase(SkRect::MakeLTRB(4, 2, 5, 6), fill, r), + TestCase(r, linePath, buttCap).compare(r, TestCase(r, SkRect::MakeLTRB(4, 2, 5, 6), fill), TestCase::kAllSame_ComparisonExpecation); - TestCase(linePath, squareCap, r).compare(r, TestCase(SkRect::MakeLTRB(2, 2, 7, 6), fill, r), + TestCase(r, linePath, squareCap).compare(r, TestCase(r, SkRect::MakeLTRB(2, 2, 7, 6), fill), TestCase::kAllSame_ComparisonExpecation); - TestCase(linePath, roundCap, r).compare(r, - TestCase(SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 7, 6), 2, 2), fill, r), + TestCase(r, linePath, roundCap).compare(r, + TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 7, 6), 2, 2), fill), TestCase::kAllSame_ComparisonExpecation); // point @@ -1661,183 +1755,135 @@ static void test_stroked_lines(skiatest::Reporter* r) { linePath.moveTo(4, 4); linePath.lineTo(4, 4); - TestCase(linePath, buttCap, r).compare(r, TestCase(SkRect::MakeEmpty(), fill, r), + TestCase(r, linePath, buttCap).compare(r, TestCase(r, SkRect::MakeEmpty(), fill), TestCase::kAllSame_ComparisonExpecation); - TestCase(linePath, squareCap, r).compare(r, TestCase(SkRect::MakeLTRB(2, 2, 6, 6), fill, r), + TestCase(r, linePath, squareCap).compare(r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 6), fill), TestCase::kAllSame_ComparisonExpecation); - TestCase(linePath, roundCap, r).compare(r, - TestCase(SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 6), 2, 2), fill, r), + TestCase(r, linePath, roundCap).compare(r, + TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 6), 2, 2), fill), TestCase::kAllSame_ComparisonExpecation); } DEF_TEST(GrShape, reporter) { + SkTArray> geos; + SkTArray> rrectPathGeos; + for (auto r : { SkRect::MakeWH(10, 20), SkRect::MakeWH(-10, -20), SkRect::MakeWH(-10, 20), SkRect::MakeWH(10, -20)}) { - test_basic(reporter, r); - test_scale(reporter, r); - test_dash_fill(reporter, r); - test_null_dash(reporter, r); - // Test modifying various stroke params. - test_stroke_param( - reporter, r, - [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);}, - SkIntToScalar(2), SkIntToScalar(4)); - test_stroke_join(reporter, r); - test_stroke_cap(reporter, r); - test_miter_limit(reporter, r); - test_path_effect_makes_rrect(reporter, r); - test_unknown_path_effect(reporter, r); - test_path_effect_makes_empty_shape(reporter, r); - test_path_effect_fails(reporter, r); - test_make_hairline_path_effect(reporter, r, true); - GrShape shape(r); - REPORTER_ASSERT(reporter, !shape.asLine(nullptr, nullptr)); + geos.emplace_back(new RectGeo(r)); + SkPath rectPath; + rectPath.addRect(r); + geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes, + PathGeo::Invert::kNo)); + geos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes, + PathGeo::Invert::kYes)); + rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, r, RRectPathGeo::RRectForStroke::kYes, + PathGeo::Invert::kNo)); } - for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)), SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4), SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) { - test_basic(reporter, rr); + geos.emplace_back(new RRectGeo(rr)); test_rrect(reporter, rr); - test_scale(reporter, rr); - test_dash_fill(reporter, rr); - test_null_dash(reporter, rr); - // Test modifying various stroke params. - test_stroke_param( - reporter, rr, - [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);}, - SkIntToScalar(2), SkIntToScalar(4)); - test_stroke_join(reporter, rr); - test_stroke_cap(reporter, rr); - test_miter_limit(reporter, rr); - test_path_effect_makes_rrect(reporter, rr); - test_unknown_path_effect(reporter, rr); - test_path_effect_makes_empty_shape(reporter, rr); - test_path_effect_fails(reporter, rr); - test_make_hairline_path_effect(reporter, rr, true); - GrShape shape(rr); - REPORTER_ASSERT(reporter, !shape.asLine(nullptr, nullptr)); + SkPath rectPath; + rectPath.addRRect(rr); + geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes, + PathGeo::Invert::kNo)); + geos.emplace_back(new RRectPathGeo(rectPath, rr, RRectPathGeo::RRectForStroke::kYes, + PathGeo::Invert::kYes)); + rrectPathGeos.emplace_back(new RRectPathGeo(rectPath, rr, + RRectPathGeo::RRectForStroke::kYes, + PathGeo::Invert::kNo)); } - struct TestPath { - TestPath(const SkPath& path, bool isRRectFill, bool isRRectStroke, bool isLine, - const SkRRect& rrect) - : fPath(path) - , fIsRRectForFill(isRRectFill) - , fIsRRectForStroke(isRRectStroke) - , fIsLine(isLine) - , fRRect(rrect) {} - SkPath fPath; - bool fIsRRectForFill; - bool fIsRRectForStroke; - bool fIsLine; - SkRRect fRRect; - }; - SkTArray paths; - - SkPath circlePath; - circlePath.addCircle(10, 10, 10); - paths.emplace_back(circlePath, true, true, false, SkRRect::MakeOval(SkRect::MakeWH(20,20))); - - SkPath rectPath; - rectPath.addRect(SkRect::MakeWH(10, 10)); - paths.emplace_back(rectPath, true, true, false, SkRRect::MakeRect(SkRect::MakeWH(10, 10))); - SkPath openRectPath; openRectPath.moveTo(0, 0); openRectPath.lineTo(10, 0); openRectPath.lineTo(10, 10); openRectPath.lineTo(0, 10); - paths.emplace_back(openRectPath, true, false, false, SkRRect::MakeRect(SkRect::MakeWH(10, 10))); + geos.emplace_back(new RRectPathGeo(openRectPath, SkRect::MakeWH(10, 10), + RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kNo)); + geos.emplace_back(new RRectPathGeo(openRectPath, SkRect::MakeWH(10, 10), + RRectPathGeo::RRectForStroke::kNo, PathGeo::Invert::kYes)); + rrectPathGeos.emplace_back(new RRectPathGeo(openRectPath, SkRect::MakeWH(10, 10), + RRectPathGeo::RRectForStroke::kNo, + PathGeo::Invert::kNo)); SkPath quadPath; quadPath.quadTo(10, 10, 5, 8); - paths.emplace_back(quadPath, false, false, false, SkRRect()); + geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kNo)); + geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kYes)); SkPath linePath; linePath.lineTo(10, 10); - paths.emplace_back(linePath, false, false, true, SkRRect()); + geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kNo)); + geos.emplace_back(new PathGeo(linePath, PathGeo::Invert::kYes)); // Horizontal and vertical paths become rrects when stroked. SkPath vLinePath; vLinePath.lineTo(0, 10); - paths.emplace_back(vLinePath, false, false, true, SkRRect()); + geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kNo)); + geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kYes)); SkPath hLinePath; hLinePath.lineTo(10, 0); - paths.emplace_back(hLinePath, false, false, true, SkRRect()); + geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kNo)); + geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kYes)); - for (auto testPath : paths) { - 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; - 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 || testPath.fIsLine); - test_dash_fill(reporter, path); - // Test modifying various stroke params. - test_stroke_param( - reporter, path, + for (int i = 0; i < geos.count(); ++i) { + test_basic(reporter, *geos[i]); + test_scale(reporter, *geos[i]); + test_dash_fill(reporter, *geos[i]); + test_null_dash(reporter, *geos[i]); + // Test modifying various stroke params. + test_stroke_param( + reporter, *geos[i], [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);}, SkIntToScalar(2), SkIntToScalar(4)); - test_stroke_join(reporter, path); - 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 || - testPath.fIsLine); - } + test_stroke_join(reporter, *geos[i]); + test_stroke_cap(reporter, *geos[i]); + test_miter_limit(reporter, *geos[i]); + test_path_effect_makes_rrect(reporter, *geos[i]); + test_unknown_path_effect(reporter, *geos[i]); + test_path_effect_makes_empty_shape(reporter, *geos[i]); + test_path_effect_fails(reporter, *geos[i]); + test_make_hairline_path_effect(reporter, *geos[i]); + test_volatile_path(reporter, *geos[i]); } - for (auto testPath : paths) { - const SkPath& path = testPath.fPath; - + for (int i = 0; i < rrectPathGeos.count(); ++i) { + const RRectPathGeo& rrgeo = *rrectPathGeos[i]; SkPaint fillPaint; - TestCase fillPathCase(path, fillPaint, reporter); + TestCase fillPathCase(reporter, rrgeo.path(), fillPaint); SkRRect rrect; - REPORTER_ASSERT(reporter, testPath.fIsRRectForFill == + REPORTER_ASSERT(reporter, rrgeo.isNonPath(fillPaint) == fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr, nullptr)); - if (testPath.fIsRRectForFill) { - TestCase fillPathCase2(testPath.fPath, fillPaint, reporter); - REPORTER_ASSERT(reporter, rrect == testPath.fRRect); - TestCase fillRRectCase(rrect, fillPaint, reporter); + if (rrgeo.isNonPath(fillPaint)) { + TestCase fillPathCase2(reporter, rrgeo.path(), fillPaint); + REPORTER_ASSERT(reporter, rrect == rrgeo.rrect()); + TestCase fillRRectCase(reporter, rrect, fillPaint); fillPathCase2.compare(reporter, fillRRectCase, TestCase::kAllSame_ComparisonExpecation); } SkPaint strokePaint; strokePaint.setStrokeWidth(3.f); strokePaint.setStyle(SkPaint::kStroke_Style); - TestCase strokePathCase(path, strokePaint, reporter); - if (testPath.fIsRRectForStroke) { + TestCase strokePathCase(reporter, rrgeo.path(), strokePaint); + if (rrgeo.isNonPath(strokePaint)) { REPORTER_ASSERT(reporter, strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr, nullptr)); - } - - if (testPath.fIsRRectForStroke) { - REPORTER_ASSERT(reporter, rrect == testPath.fRRect); - TestCase strokeRRectCase(rrect, strokePaint, reporter); + REPORTER_ASSERT(reporter, rrect == rrgeo.rrect()); + TestCase strokeRRectCase(reporter, rrect, strokePaint); strokePathCase.compare(reporter, strokeRRectCase, TestCase::kAllSame_ComparisonExpecation); } } // Test a volatile empty path. - test_volatile_path(reporter, SkPath(), true); + test_volatile_path(reporter, PathGeo(SkPath(), PathGeo::Invert::kNo)); test_empty_shape(reporter);