12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339 |
- /*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
- #include "include/core/SkCanvas.h"
- #include "include/core/SkPath.h"
- #include "include/core/SkSurface.h"
- #include "include/effects/SkDashPathEffect.h"
- #include "include/pathops/SkPathOps.h"
- #include "src/core/SkClipOpPriv.h"
- #include "src/core/SkRectPriv.h"
- #include "src/gpu/geometry/GrShape.h"
- #include "tests/Test.h"
- #include <initializer_list>
- #include <functional>
- #include <utility>
- uint32_t GrShape::testingOnly_getOriginalGenerationID() const {
- if (const auto* lp = this->originalPathForListeners()) {
- return lp->getGenerationID();
- }
- return SkPath().getGenerationID();
- }
- bool GrShape::testingOnly_isPath() const {
- return Type::kPath == fType;
- }
- bool GrShape::testingOnly_isNonVolatilePath() const {
- return Type::kPath == fType && !fPathData.fPath.isVolatile();
- }
- using Key = SkTArray<uint32_t>;
- static bool make_key(Key* key, const GrShape& shape) {
- int size = shape.unstyledKeySize();
- if (size <= 0) {
- key->reset(0);
- return false;
- }
- SkASSERT(size);
- key->reset(size);
- shape.writeUnstyledKey(key->begin());
- return true;
- }
- static bool paths_fill_same(const SkPath& a, const SkPath& b) {
- SkPath pathXor;
- Op(a, b, SkPathOp::kXOR_SkPathOp, &pathXor);
- return pathXor.isEmpty();
- }
- static bool test_bounds_by_rasterizing(const SkPath& path, const SkRect& bounds) {
- // We test the bounds by rasterizing the path into a kRes by kRes grid. The bounds is
- // mapped to the range kRes/4 to 3*kRes/4 in x and y. A difference clip is used to avoid
- // rendering within the bounds (with a tolerance). Then we render the path and check that
- // everything got clipped out.
- static constexpr int kRes = 2000;
- // This tolerance is in units of 1/kRes fractions of the bounds width/height.
- static constexpr int kTol = 2;
- GR_STATIC_ASSERT(kRes % 4 == 0);
- SkImageInfo info = SkImageInfo::MakeA8(kRes, kRes);
- sk_sp<SkSurface> surface = SkSurface::MakeRaster(info);
- surface->getCanvas()->clear(0x0);
- SkRect clip = SkRect::MakeXYWH(kRes/4, kRes/4, kRes/2, kRes/2);
- SkMatrix matrix;
- matrix.setRectToRect(bounds, clip, SkMatrix::kFill_ScaleToFit);
- clip.outset(SkIntToScalar(kTol), SkIntToScalar(kTol));
- surface->getCanvas()->clipRect(clip, kDifference_SkClipOp);
- surface->getCanvas()->concat(matrix);
- SkPaint whitePaint;
- whitePaint.setColor(SK_ColorWHITE);
- surface->getCanvas()->drawPath(path, whitePaint);
- SkPixmap pixmap;
- surface->getCanvas()->peekPixels(&pixmap);
- #if defined(SK_BUILD_FOR_WIN)
- // The static constexpr version in #else causes cl.exe to crash.
- const uint8_t* kZeros = reinterpret_cast<uint8_t*>(calloc(kRes, 1));
- #else
- static constexpr uint8_t kZeros[kRes] = {0};
- #endif
- for (int y = 0; y < kRes; ++y) {
- const uint8_t* row = pixmap.addr8(0, y);
- if (0 != memcmp(kZeros, row, kRes)) {
- return false;
- }
- }
- #ifdef SK_BUILD_FOR_WIN
- free(const_cast<uint8_t*>(kZeros));
- #endif
- return true;
- }
- static bool can_interchange_winding_and_even_odd_fill(const GrShape& shape) {
- SkPath path;
- shape.asPath(&path);
- if (shape.style().hasNonDashPathEffect()) {
- return false;
- }
- const SkStrokeRec::Style strokeRecStyle = shape.style().strokeRec().getStyle();
- return strokeRecStyle == SkStrokeRec::kStroke_Style ||
- strokeRecStyle == SkStrokeRec::kHairline_Style ||
- (shape.style().isSimpleFill() && path.isConvex());
- }
- static void check_equivalence(skiatest::Reporter* r, const GrShape& a, const GrShape& b,
- const Key& keyA, const Key& keyB) {
- // GrShape only respects the input winding direction and start point for rrect shapes
- // when there is a path effect. Thus, if there are two GrShapes representing the same rrect
- // but one has a path effect in its style and the other doesn't then asPath() and the unstyled
- // key will differ. GrShape will have canonicalized the direction and start point for the shape
- // without the path effect. If *both* have path effects then they should have both preserved
- // the direction and starting point.
- // The asRRect() output params are all initialized just to silence compiler warnings about
- // uninitialized variables.
- 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, &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);
- // GrShape will close paths with simple fill style.
- bool allowedClosednessDiff = (a.style().isSimpleFill() != b.style().isSimpleFill());
- SkPath pathA, pathB;
- a.asPath(&pathA);
- b.asPath(&pathB);
- // Having a dash path effect can allow 'a' but not 'b' to turn a inverse fill type into a
- // non-inverse fill type (or vice versa).
- bool ignoreInversenessDifference = false;
- if (pathA.isInverseFillType() != pathB.isInverseFillType()) {
- const GrShape* s1 = pathA.isInverseFillType() ? &a : &b;
- const GrShape* s2 = pathA.isInverseFillType() ? &b : &a;
- bool canDropInverse1 = s1->style().isDashed();
- bool canDropInverse2 = s2->style().isDashed();
- ignoreInversenessDifference = (canDropInverse1 != canDropInverse2);
- }
- bool ignoreWindingVsEvenOdd = false;
- if (SkPath::ConvertToNonInverseFillType(pathA.getFillType()) !=
- SkPath::ConvertToNonInverseFillType(pathB.getFillType())) {
- bool aCanChange = can_interchange_winding_and_even_odd_fill(a);
- bool bCanChange = can_interchange_winding_and_even_odd_fill(b);
- if (aCanChange != bCanChange) {
- ignoreWindingVsEvenOdd = true;
- }
- }
- if (allowSameRRectButDiffStartAndDir) {
- REPORTER_ASSERT(r, rrectA == rrectB);
- REPORTER_ASSERT(r, paths_fill_same(pathA, pathB));
- REPORTER_ASSERT(r, ignoreInversenessDifference || invertedA == invertedB);
- } else {
- SkPath pA = pathA;
- SkPath pB = pathB;
- REPORTER_ASSERT(r, a.inverseFilled() == pA.isInverseFillType());
- REPORTER_ASSERT(r, b.inverseFilled() == pB.isInverseFillType());
- if (ignoreInversenessDifference) {
- pA.setFillType(SkPath::ConvertToNonInverseFillType(pathA.getFillType()));
- pB.setFillType(SkPath::ConvertToNonInverseFillType(pathB.getFillType()));
- }
- if (ignoreWindingVsEvenOdd) {
- pA.setFillType(pA.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
- : SkPath::kEvenOdd_FillType);
- pB.setFillType(pB.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
- : SkPath::kEvenOdd_FillType);
- }
- if (!ignoreInversenessDifference && !ignoreWindingVsEvenOdd) {
- REPORTER_ASSERT(r, keyA == keyB);
- } else {
- REPORTER_ASSERT(r, keyA != keyB);
- }
- if (allowedClosednessDiff) {
- // GrShape will close paths with simple fill style. Make the non-filled path closed
- // so that the comparision will succeed. Make sure both are closed before comparing.
- pA.close();
- pB.close();
- }
- 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());
- REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeClosed() == b.knownToBeClosed());
- // closedness can affect convexity.
- REPORTER_ASSERT(r, allowedClosednessDiff || a.knownToBeConvex() == b.knownToBeConvex());
- if (a.knownToBeConvex()) {
- REPORTER_ASSERT(r, pathA.isConvex());
- }
- if (b.knownToBeConvex()) {
- REPORTER_ASSERT(r, pathB.isConvex());
- }
- REPORTER_ASSERT(r, a.bounds() == b.bounds());
- REPORTER_ASSERT(r, a.segmentMask() == b.segmentMask());
- // Init these to suppress warnings.
- SkPoint pts[4] {{0, 0,}, {0, 0}, {0, 0}, {0, 0}} ;
- bool invertedLine[2] {true, true};
- REPORTER_ASSERT(r, a.asLine(pts, &invertedLine[0]) == b.asLine(pts + 2, &invertedLine[1]));
- // mayBeInverseFilledAfterStyling() is allowed to differ if one has a arbitrary PE and the other
- // doesn't (since the PE can set any fill type on its output path).
- // Moreover, dash style explicitly ignores inverseness. So if one is dashed but not the other
- // then they may disagree about inverseness.
- if (a.style().hasNonDashPathEffect() == b.style().hasNonDashPathEffect() &&
- a.style().isDashed() == b.style().isDashed()) {
- REPORTER_ASSERT(r, a.mayBeInverseFilledAfterStyling() ==
- b.mayBeInverseFilledAfterStyling());
- }
- if (a.asLine(nullptr, nullptr)) {
- REPORTER_ASSERT(r, pts[2] == pts[0] && pts[3] == pts[1]);
- REPORTER_ASSERT(r, ignoreInversenessDifference || invertedLine[0] == invertedLine[1]);
- REPORTER_ASSERT(r, invertedLine[0] == a.inverseFilled());
- REPORTER_ASSERT(r, invertedLine[1] == b.inverseFilled());
- }
- REPORTER_ASSERT(r, ignoreInversenessDifference || a.inverseFilled() == b.inverseFilled());
- }
- static void check_original_path_ids(skiatest::Reporter* r, const GrShape& base, const GrShape& pe,
- const GrShape& peStroke, const GrShape& full) {
- bool baseIsNonVolatilePath = base.testingOnly_isNonVolatilePath();
- bool peIsPath = pe.testingOnly_isPath();
- bool peStrokeIsPath = peStroke.testingOnly_isPath();
- bool fullIsPath = full.testingOnly_isPath();
- REPORTER_ASSERT(r, peStrokeIsPath == fullIsPath);
- uint32_t baseID = base.testingOnly_getOriginalGenerationID();
- uint32_t peID = pe.testingOnly_getOriginalGenerationID();
- uint32_t peStrokeID = peStroke.testingOnly_getOriginalGenerationID();
- uint32_t fullID = full.testingOnly_getOriginalGenerationID();
- // All empty paths have the same gen ID
- uint32_t emptyID = SkPath().getGenerationID();
- // If we started with a real path, then our genID should match that path's gen ID (and not be
- // empty). If we started with a simple shape or a volatile path, our original path should have
- // been reset.
- REPORTER_ASSERT(r, baseIsNonVolatilePath == (baseID != emptyID));
- // For the derived shapes, if they're simple types, their original paths should have been reset
- REPORTER_ASSERT(r, peIsPath || (peID == emptyID));
- REPORTER_ASSERT(r, peStrokeIsPath || (peStrokeID == emptyID));
- REPORTER_ASSERT(r, fullIsPath || (fullID == emptyID));
- if (!peIsPath) {
- // If the path effect produces a simple shape, then there are no unbroken chains to test
- return;
- }
- // From here on, we know that the path effect produced a shape that was a "real" path
- if (baseIsNonVolatilePath) {
- REPORTER_ASSERT(r, baseID == peID);
- }
- if (peStrokeIsPath) {
- REPORTER_ASSERT(r, peID == peStrokeID);
- REPORTER_ASSERT(r, peStrokeID == fullID);
- }
- if (baseIsNonVolatilePath && peStrokeIsPath) {
- REPORTER_ASSERT(r, baseID == peStrokeID);
- REPORTER_ASSERT(r, baseID == fullID);
- }
- }
- void test_inversions(skiatest::Reporter* r, const GrShape& shape, const Key& shapeKey) {
- GrShape preserve = GrShape::MakeFilled(shape, GrShape::FillInversion::kPreserve);
- Key preserveKey;
- make_key(&preserveKey, preserve);
- GrShape flip = GrShape::MakeFilled(shape, GrShape::FillInversion::kFlip);
- Key flipKey;
- make_key(&flipKey, flip);
- GrShape inverted = GrShape::MakeFilled(shape, GrShape::FillInversion::kForceInverted);
- Key invertedKey;
- make_key(&invertedKey, inverted);
- GrShape noninverted = GrShape::MakeFilled(shape, GrShape::FillInversion::kForceNoninverted);
- Key noninvertedKey;
- make_key(&noninvertedKey, noninverted);
- if (invertedKey.count() || noninvertedKey.count()) {
- REPORTER_ASSERT(r, invertedKey != noninvertedKey);
- }
- if (shape.style().isSimpleFill()) {
- check_equivalence(r, shape, preserve, shapeKey, preserveKey);
- }
- if (shape.inverseFilled()) {
- check_equivalence(r, preserve, inverted, preserveKey, invertedKey);
- check_equivalence(r, flip, noninverted, flipKey, noninvertedKey);
- } else {
- check_equivalence(r, preserve, noninverted, preserveKey, noninvertedKey);
- check_equivalence(r, flip, inverted, flipKey, invertedKey);
- }
- GrShape doubleFlip = GrShape::MakeFilled(flip, GrShape::FillInversion::kFlip);
- Key doubleFlipKey;
- make_key(&doubleFlipKey, doubleFlip);
- // It can be the case that the double flip has no key but preserve does. This happens when the
- // original shape has an inherited style key. That gets dropped on the first inversion flip.
- if (preserveKey.count() && !doubleFlipKey.count()) {
- preserveKey.reset();
- }
- check_equivalence(r, preserve, doubleFlip, preserveKey, doubleFlipKey);
- }
- 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 ArcGeo : public Geo {
- public:
- ArcGeo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool useCenter)
- : fOval(oval)
- , fStartAngle(startAngle)
- , fSweepAngle(sweepAngle)
- , fUseCenter(useCenter) {}
- SkPath path() const override {
- SkPath path;
- SkPathPriv::CreateDrawArcPath(&path, fOval, fStartAngle, fSweepAngle, fUseCenter, false);
- return path;
- }
- GrShape makeShape(const SkPaint& paint) const override {
- return GrShape::MakeArc(fOval, fStartAngle, fSweepAngle, fUseCenter, GrStyle(paint));
- }
- // GrShape specializes when created from arc params but it doesn't recognize arcs from SkPath.
- bool isNonPath(const SkPaint& paint) const override { return false; }
- private:
- SkRect fOval;
- SkScalar fStartAngle;
- SkScalar fSweepAngle;
- bool fUseCenter;
- };
- 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);
- }
- 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:
- TestCase(const Geo& geo, const SkPaint& paint, skiatest::Reporter* r,
- SkScalar scale = SK_Scalar1)
- : fBase(new GrShape(geo.makeShape(paint))) {
- this->init(r, scale);
- }
- template <typename... ShapeArgs>
- TestCase(skiatest::Reporter* r, ShapeArgs... shapeArgs) : fBase(new GrShape(shapeArgs...)) {
- this->init(r, SK_Scalar1);
- }
- TestCase(const GrShape& shape, skiatest::Reporter* r, SkScalar scale = SK_Scalar1)
- : fBase(new GrShape(shape)) {
- this->init(r, scale);
- }
- struct SelfExpectations {
- bool fPEHasEffect;
- bool fPEHasValidKey;
- bool fStrokeApplies;
- };
- void testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const;
- enum ComparisonExpecation {
- kAllDifferent_ComparisonExpecation,
- kSameUpToPE_ComparisonExpecation,
- kSameUpToStroke_ComparisonExpecation,
- kAllSame_ComparisonExpecation,
- };
- void compare(skiatest::Reporter*, const TestCase& that, ComparisonExpecation) const;
- const GrShape& baseShape() const { return *fBase; }
- const GrShape& appliedPathEffectShape() const { return *fAppliedPE; }
- const GrShape& appliedFullStyleShape() const { return *fAppliedFull; }
- // The returned array's count will be 0 if the key shape has no key.
- const Key& baseKey() const { return fBaseKey; }
- const Key& appliedPathEffectKey() const { return fAppliedPEKey; }
- const Key& appliedFullStyleKey() const { return fAppliedFullKey; }
- const Key& appliedPathEffectThenStrokeKey() const { return fAppliedPEThenStrokeKey; }
- private:
- static void CheckBounds(skiatest::Reporter* r, const GrShape& shape, const SkRect& bounds) {
- SkPath path;
- shape.asPath(&path);
- // If the bounds are empty, the path ought to be as well.
- if (bounds.fLeft > bounds.fRight || bounds.fTop > bounds.fBottom) {
- REPORTER_ASSERT(r, path.isEmpty());
- return;
- }
- if (path.isEmpty()) {
- return;
- }
- // 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) {
- fAppliedPE.reset(new GrShape);
- fAppliedPEThenStroke.reset(new GrShape);
- fAppliedFull.reset(new GrShape);
- *fAppliedPE = fBase->applyStyle(GrStyle::Apply::kPathEffectOnly, scale);
- *fAppliedPEThenStroke =
- fAppliedPE->applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
- *fAppliedFull = fBase->applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, scale);
- make_key(&fBaseKey, *fBase);
- make_key(&fAppliedPEKey, *fAppliedPE);
- make_key(&fAppliedPEThenStrokeKey, *fAppliedPEThenStroke);
- make_key(&fAppliedFullKey, *fAppliedFull);
- // All shapes should report the same "original" path, so that path renderers can get to it
- // if necessary.
- check_original_path_ids(r, *fBase, *fAppliedPE, *fAppliedPEThenStroke, *fAppliedFull);
- // Applying the path effect and then the stroke should always be the same as applying
- // both in one go.
- REPORTER_ASSERT(r, fAppliedPEThenStrokeKey == fAppliedFullKey);
- SkPath a, b;
- fAppliedPEThenStroke->asPath(&a);
- fAppliedFull->asPath(&b);
- // If the output of the path effect is a rrect then it is possible for a and b to be
- // different paths that fill identically. The reason is that fAppliedFull will do this:
- // base -> apply path effect -> rrect_as_path -> stroke -> stroked_rrect_as_path
- // 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, nullptr)) {
- REPORTER_ASSERT(r, paths_fill_same(a, b));
- } else {
- REPORTER_ASSERT(r, a == b);
- }
- REPORTER_ASSERT(r, fAppliedFull->isEmpty() == fAppliedPEThenStroke->isEmpty());
- SkPath path;
- fBase->asPath(&path);
- REPORTER_ASSERT(r, path.isEmpty() == fBase->isEmpty());
- REPORTER_ASSERT(r, path.getSegmentMasks() == fBase->segmentMask());
- fAppliedPE->asPath(&path);
- REPORTER_ASSERT(r, path.isEmpty() == fAppliedPE->isEmpty());
- REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedPE->segmentMask());
- fAppliedFull->asPath(&path);
- REPORTER_ASSERT(r, path.isEmpty() == fAppliedFull->isEmpty());
- REPORTER_ASSERT(r, path.getSegmentMasks() == fAppliedFull->segmentMask());
- CheckBounds(r, *fBase, fBase->bounds());
- CheckBounds(r, *fAppliedPE, fAppliedPE->bounds());
- CheckBounds(r, *fAppliedPEThenStroke, fAppliedPEThenStroke->bounds());
- CheckBounds(r, *fAppliedFull, fAppliedFull->bounds());
- SkRect styledBounds = fBase->styledBounds();
- CheckBounds(r, *fAppliedFull, styledBounds);
- styledBounds = fAppliedPE->styledBounds();
- CheckBounds(r, *fAppliedFull, styledBounds);
- // Check that the same path is produced when style is applied by GrShape and GrStyle.
- SkPath preStyle;
- SkPath postPathEffect;
- SkPath postAllStyle;
- fBase->asPath(&preStyle);
- SkStrokeRec postPEStrokeRec(SkStrokeRec::kFill_InitStyle);
- if (fBase->style().applyPathEffectToPath(&postPathEffect, &postPEStrokeRec, preStyle,
- scale)) {
- // run postPathEffect through GrShape to get any geometry reductions that would have
- // occurred to fAppliedPE.
- GrShape(postPathEffect, GrStyle(postPEStrokeRec, nullptr)).asPath(&postPathEffect);
- SkPath testPath;
- fAppliedPE->asPath(&testPath);
- REPORTER_ASSERT(r, testPath == postPathEffect);
- REPORTER_ASSERT(r, postPEStrokeRec.hasEqualEffect(fAppliedPE->style().strokeRec()));
- }
- SkStrokeRec::InitStyle fillOrHairline;
- if (fBase->style().applyToPath(&postAllStyle, &fillOrHairline, preStyle, scale)) {
- SkPath testPath;
- fAppliedFull->asPath(&testPath);
- if (fBase->style().hasPathEffect()) {
- // Because GrShape always does two-stage application when there is a path effect
- // there may be a reduction/canonicalization step between the path effect and
- // strokerec not reflected in postAllStyle since it applied both the path effect
- // and strokerec without analyzing the intermediate path.
- REPORTER_ASSERT(r, paths_fill_same(postAllStyle, testPath));
- } else {
- // Make sure that postAllStyle sees any reductions/canonicalizations that GrShape
- // would apply.
- GrShape(postAllStyle, GrStyle(fillOrHairline)).asPath(&postAllStyle);
- REPORTER_ASSERT(r, testPath == postAllStyle);
- }
- if (fillOrHairline == SkStrokeRec::kFill_InitStyle) {
- REPORTER_ASSERT(r, fAppliedFull->style().isSimpleFill());
- } else {
- REPORTER_ASSERT(r, fAppliedFull->style().isSimpleHairline());
- }
- }
- test_inversions(r, *fBase, fBaseKey);
- test_inversions(r, *fAppliedPE, fAppliedPEKey);
- test_inversions(r, *fAppliedFull, fAppliedFullKey);
- }
- std::unique_ptr<GrShape> fBase;
- std::unique_ptr<GrShape> fAppliedPE;
- std::unique_ptr<GrShape> fAppliedPEThenStroke;
- std::unique_ptr<GrShape> fAppliedFull;
- Key fBaseKey;
- Key fAppliedPEKey;
- Key fAppliedPEThenStrokeKey;
- Key fAppliedFullKey;
- };
- void TestCase::testExpectations(skiatest::Reporter* reporter, SelfExpectations expectations) const {
- // The base's key should always be valid (unless the path is volatile)
- REPORTER_ASSERT(reporter, fBaseKey.count());
- if (expectations.fPEHasEffect) {
- REPORTER_ASSERT(reporter, fBaseKey != fAppliedPEKey);
- REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedPEKey.count()));
- REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
- REPORTER_ASSERT(reporter, expectations.fPEHasValidKey == SkToBool(fAppliedFullKey.count()));
- if (expectations.fStrokeApplies && expectations.fPEHasValidKey) {
- REPORTER_ASSERT(reporter, fAppliedPEKey != fAppliedFullKey);
- REPORTER_ASSERT(reporter, SkToBool(fAppliedFullKey.count()));
- }
- } else {
- REPORTER_ASSERT(reporter, fBaseKey == fAppliedPEKey);
- SkPath a, b;
- fBase->asPath(&a);
- fAppliedPE->asPath(&b);
- REPORTER_ASSERT(reporter, a == b);
- if (expectations.fStrokeApplies) {
- REPORTER_ASSERT(reporter, fBaseKey != fAppliedFullKey);
- } else {
- REPORTER_ASSERT(reporter, fBaseKey == fAppliedFullKey);
- }
- }
- }
- void TestCase::compare(skiatest::Reporter* r, const TestCase& that,
- ComparisonExpecation expectation) const {
- SkPath a, b;
- switch (expectation) {
- case kAllDifferent_ComparisonExpecation:
- REPORTER_ASSERT(r, fBaseKey != that.fBaseKey);
- REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
- REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
- break;
- case kSameUpToPE_ComparisonExpecation:
- check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
- REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
- REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
- break;
- case kSameUpToStroke_ComparisonExpecation:
- check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
- check_equivalence(r, *fAppliedPE, *that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
- REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
- break;
- case kAllSame_ComparisonExpecation:
- check_equivalence(r, *fBase, *that.fBase, fBaseKey, that.fBaseKey);
- check_equivalence(r, *fAppliedPE, *that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
- check_equivalence(r, *fAppliedFull, *that.fAppliedFull, fAppliedFullKey,
- that.fAppliedFullKey);
- break;
- }
- }
- } // namespace
- static sk_sp<SkPathEffect> make_dash() {
- static const SkScalar kIntervals[] = { 0.25, 3.f, 0.5, 2.f };
- static const SkScalar kPhase = 0.75;
- return SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), kPhase);
- }
- static sk_sp<SkPathEffect> make_null_dash() {
- static const SkScalar kNullIntervals[] = {0, 0, 0, 0, 0, 0};
- return SkDashPathEffect::Make(kNullIntervals, SK_ARRAY_COUNT(kNullIntervals), 0.f);
- }
- // We make enough TestCases, and they're large enough, that on Google3 builds we exceed
- // the maximum stack frame limit. make_TestCase() moves those temporaries over to the heap.
- template <typename... Args>
- static std::unique_ptr<TestCase> make_TestCase(Args&&... args) {
- return std::unique_ptr<TestCase>{ new TestCase(std::forward<Args>(args)...) };
- }
- static void test_basic(skiatest::Reporter* reporter, const Geo& geo) {
- sk_sp<SkPathEffect> dashPE = make_dash();
- TestCase::SelfExpectations expectations;
- SkPaint fill;
- TestCase fillCase(geo, fill, reporter);
- expectations.fPEHasEffect = false;
- expectations.fPEHasValidKey = false;
- expectations.fStrokeApplies = false;
- fillCase.testExpectations(reporter, expectations);
- // Test that another GrShape instance built from the same primitive is the same.
- make_TestCase(geo, fill, reporter)
- ->compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
- SkPaint stroke2RoundBevel;
- stroke2RoundBevel.setStyle(SkPaint::kStroke_Style);
- stroke2RoundBevel.setStrokeCap(SkPaint::kRound_Cap);
- stroke2RoundBevel.setStrokeJoin(SkPaint::kBevel_Join);
- stroke2RoundBevel.setStrokeWidth(2.f);
- TestCase stroke2RoundBevelCase(geo, stroke2RoundBevel, reporter);
- expectations.fPEHasValidKey = true;
- expectations.fPEHasEffect = false;
- expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
- stroke2RoundBevelCase.testExpectations(reporter, expectations);
- make_TestCase(geo, stroke2RoundBevel, reporter)
- ->compare(reporter, stroke2RoundBevelCase, TestCase::kAllSame_ComparisonExpecation);
- SkPaint stroke2RoundBevelDash = stroke2RoundBevel;
- stroke2RoundBevelDash.setPathEffect(make_dash());
- TestCase stroke2RoundBevelDashCase(geo, stroke2RoundBevelDash, reporter);
- expectations.fPEHasValidKey = true;
- expectations.fPEHasEffect = true;
- expectations.fStrokeApplies = true;
- stroke2RoundBevelDashCase.testExpectations(reporter, expectations);
- make_TestCase(geo, stroke2RoundBevelDash, reporter)
- ->compare(reporter, stroke2RoundBevelDashCase, TestCase::kAllSame_ComparisonExpecation);
- if (geo.fillChangesGeom() || geo.strokeIsConvertedToFill()) {
- fillCase.compare(reporter, stroke2RoundBevelCase,
- TestCase::kAllDifferent_ComparisonExpecation);
- fillCase.compare(reporter, stroke2RoundBevelDashCase,
- TestCase::kAllDifferent_ComparisonExpecation);
- } else {
- fillCase.compare(reporter, stroke2RoundBevelCase,
- TestCase::kSameUpToStroke_ComparisonExpecation);
- fillCase.compare(reporter, stroke2RoundBevelDashCase,
- TestCase::kSameUpToPE_ComparisonExpecation);
- }
- if (geo.strokeIsConvertedToFill()) {
- stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
- TestCase::kAllDifferent_ComparisonExpecation);
- } else {
- stroke2RoundBevelCase.compare(reporter, stroke2RoundBevelDashCase,
- TestCase::kSameUpToPE_ComparisonExpecation);
- }
- // Stroke and fill cases
- SkPaint stroke2RoundBevelAndFill = stroke2RoundBevel;
- stroke2RoundBevelAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
- TestCase stroke2RoundBevelAndFillCase(geo, stroke2RoundBevelAndFill, reporter);
- expectations.fPEHasValidKey = true;
- expectations.fPEHasEffect = false;
- expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
- stroke2RoundBevelAndFillCase.testExpectations(reporter, expectations);
- make_TestCase(geo, stroke2RoundBevelAndFill, reporter)->compare(
- reporter, stroke2RoundBevelAndFillCase, TestCase::kAllSame_ComparisonExpecation);
- SkPaint stroke2RoundBevelAndFillDash = stroke2RoundBevelDash;
- stroke2RoundBevelAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
- TestCase stroke2RoundBevelAndFillDashCase(geo, stroke2RoundBevelAndFillDash, reporter);
- expectations.fPEHasValidKey = true;
- expectations.fPEHasEffect = false;
- expectations.fStrokeApplies = !geo.strokeIsConvertedToFill();
- stroke2RoundBevelAndFillDashCase.testExpectations(reporter, expectations);
- make_TestCase(geo, stroke2RoundBevelAndFillDash, reporter)->compare(
- reporter, stroke2RoundBevelAndFillDashCase, TestCase::kAllSame_ComparisonExpecation);
- stroke2RoundBevelAndFillDashCase.compare(reporter, stroke2RoundBevelAndFillCase,
- TestCase::kAllSame_ComparisonExpecation);
- SkPaint hairline;
- hairline.setStyle(SkPaint::kStroke_Style);
- hairline.setStrokeWidth(0.f);
- 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 (geo.fillChangesGeom()) {
- hairlineCase.compare(reporter, fillCase, TestCase::kAllDifferent_ComparisonExpecation);
- } else {
- hairlineCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
- }
- REPORTER_ASSERT(reporter, hairlineCase.baseShape().style().isSimpleHairline());
- REPORTER_ASSERT(reporter, hairlineCase.appliedFullStyleShape().style().isSimpleHairline());
- REPORTER_ASSERT(reporter, hairlineCase.appliedPathEffectShape().style().isSimpleHairline());
- }
- static void test_scale(skiatest::Reporter* reporter, const Geo& geo) {
- sk_sp<SkPathEffect> dashPE = make_dash();
- static const SkScalar kS1 = 1.f;
- static const SkScalar kS2 = 2.f;
- SkPaint fill;
- TestCase fillCase1(geo, fill, reporter, kS1);
- TestCase fillCase2(geo, fill, reporter, kS2);
- // Scale doesn't affect fills.
- fillCase1.compare(reporter, fillCase2, TestCase::kAllSame_ComparisonExpecation);
- SkPaint hairline;
- hairline.setStyle(SkPaint::kStroke_Style);
- hairline.setStrokeWidth(0.f);
- TestCase hairlineCase1(geo, hairline, reporter, kS1);
- TestCase hairlineCase2(geo, hairline, reporter, kS2);
- // Scale doesn't affect hairlines.
- hairlineCase1.compare(reporter, hairlineCase2, TestCase::kAllSame_ComparisonExpecation);
- SkPaint stroke;
- stroke.setStyle(SkPaint::kStroke_Style);
- stroke.setStrokeWidth(2.f);
- TestCase strokeCase1(geo, stroke, reporter, kS1);
- TestCase strokeCase2(geo, stroke, reporter, kS2);
- // Scale affects the stroke
- if (geo.strokeIsConvertedToFill()) {
- REPORTER_ASSERT(reporter, !strokeCase1.baseShape().style().applies());
- strokeCase1.compare(reporter, strokeCase2, TestCase::kAllSame_ComparisonExpecation);
- } else {
- strokeCase1.compare(reporter, strokeCase2, TestCase::kSameUpToStroke_ComparisonExpecation);
- }
- SkPaint strokeDash = stroke;
- strokeDash.setPathEffect(make_dash());
- TestCase strokeDashCase1(geo, strokeDash, reporter, kS1);
- TestCase strokeDashCase2(geo, strokeDash, reporter, kS2);
- // Scale affects the dash and the stroke.
- strokeDashCase1.compare(reporter, strokeDashCase2,
- TestCase::kSameUpToPE_ComparisonExpecation);
- // Stroke and fill cases
- SkPaint strokeAndFill = stroke;
- strokeAndFill.setStyle(SkPaint::kStrokeAndFill_Style);
- TestCase strokeAndFillCase1(geo, strokeAndFill, reporter, kS1);
- TestCase strokeAndFillCase2(geo, strokeAndFill, reporter, kS2);
- SkPaint strokeAndFillDash = strokeDash;
- strokeAndFillDash.setStyle(SkPaint::kStrokeAndFill_Style);
- // Dash is ignored for stroke and fill
- TestCase strokeAndFillDashCase1(geo, strokeAndFillDash, reporter, kS1);
- TestCase strokeAndFillDashCase2(geo, strokeAndFillDash, reporter, kS2);
- // 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 (geo.strokeAndFillIsConvertedToFill(strokeAndFillDash)) {
- REPORTER_ASSERT(reporter, !strokeAndFillCase1.baseShape().style().applies());
- strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
- TestCase::kAllSame_ComparisonExpecation);
- strokeAndFillDashCase1.compare(reporter, strokeAndFillDashCase2,
- TestCase::kAllSame_ComparisonExpecation);
- } else {
- strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
- TestCase::kSameUpToStroke_ComparisonExpecation);
- }
- strokeAndFillDashCase1.compare(reporter, strokeAndFillCase1,
- TestCase::kAllSame_ComparisonExpecation);
- strokeAndFillDashCase2.compare(reporter, strokeAndFillCase2,
- TestCase::kAllSame_ComparisonExpecation);
- }
- template <typename T>
- static void test_stroke_param_impl(skiatest::Reporter* reporter, const Geo& geo,
- std::function<void(SkPaint*, T)> setter, T a, T b,
- bool paramAffectsStroke,
- bool paramAffectsDashAndStroke) {
- // Set the stroke width so that we don't get hairline. However, call the setter afterward so
- // that it can override the stroke width.
- SkPaint strokeA;
- strokeA.setStyle(SkPaint::kStroke_Style);
- strokeA.setStrokeWidth(2.f);
- setter(&strokeA, a);
- SkPaint strokeB;
- strokeB.setStyle(SkPaint::kStroke_Style);
- strokeB.setStrokeWidth(2.f);
- setter(&strokeB, b);
- TestCase strokeACase(geo, strokeA, reporter);
- TestCase strokeBCase(geo, strokeB, reporter);
- if (paramAffectsStroke) {
- // If stroking is immediately incorporated into a geometric transformation then the base
- // shapes will differ.
- if (geo.strokeIsConvertedToFill()) {
- strokeACase.compare(reporter, strokeBCase,
- TestCase::kAllDifferent_ComparisonExpecation);
- } else {
- strokeACase.compare(reporter, strokeBCase,
- TestCase::kSameUpToStroke_ComparisonExpecation);
- }
- } else {
- strokeACase.compare(reporter, strokeBCase, TestCase::kAllSame_ComparisonExpecation);
- }
- SkPaint strokeAndFillA = strokeA;
- SkPaint strokeAndFillB = strokeB;
- strokeAndFillA.setStyle(SkPaint::kStrokeAndFill_Style);
- strokeAndFillB.setStyle(SkPaint::kStrokeAndFill_Style);
- TestCase strokeAndFillACase(geo, strokeAndFillA, reporter);
- TestCase strokeAndFillBCase(geo, strokeAndFillB, reporter);
- if (paramAffectsStroke) {
- // If stroking is immediately incorporated into a geometric transformation then the base
- // shapes will differ.
- if (geo.strokeAndFillIsConvertedToFill(strokeAndFillA) ||
- geo.strokeAndFillIsConvertedToFill(strokeAndFillB)) {
- strokeAndFillACase.compare(reporter, strokeAndFillBCase,
- TestCase::kAllDifferent_ComparisonExpecation);
- } else {
- strokeAndFillACase.compare(reporter, strokeAndFillBCase,
- TestCase::kSameUpToStroke_ComparisonExpecation);
- }
- } else {
- strokeAndFillACase.compare(reporter, strokeAndFillBCase,
- TestCase::kAllSame_ComparisonExpecation);
- }
- // Make sure stroking params don't affect fill style.
- SkPaint fillA = strokeA, fillB = strokeB;
- fillA.setStyle(SkPaint::kFill_Style);
- fillB.setStyle(SkPaint::kFill_Style);
- TestCase fillACase(geo, fillA, reporter);
- TestCase fillBCase(geo, fillB, reporter);
- fillACase.compare(reporter, fillBCase, TestCase::kAllSame_ComparisonExpecation);
- // Make sure just applying the dash but not stroke gives the same key for both stroking
- // variations.
- SkPaint dashA = strokeA, dashB = strokeB;
- dashA.setPathEffect(make_dash());
- dashB.setPathEffect(make_dash());
- TestCase dashACase(geo, dashA, reporter);
- TestCase dashBCase(geo, dashB, reporter);
- if (paramAffectsDashAndStroke) {
- dashACase.compare(reporter, dashBCase, TestCase::kSameUpToStroke_ComparisonExpecation);
- } else {
- dashACase.compare(reporter, dashBCase, TestCase::kAllSame_ComparisonExpecation);
- }
- }
- template <typename T>
- static void test_stroke_param(skiatest::Reporter* reporter, const Geo& geo,
- std::function<void(SkPaint*, T)> setter, T a, T b) {
- test_stroke_param_impl(reporter, geo, setter, a, b, true, true);
- };
- 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<SkPaint::Cap>(
- reporter,
- geo,
- [](SkPaint* p, SkPaint::Cap c) { p->setStrokeCap(c);},
- SkPaint::kButt_Cap, SkPaint::kRound_Cap,
- affectsStroke,
- affectsDashAndStroke);
- };
- static bool shape_known_not_to_have_joins(const GrShape& shape) {
- return shape.asLine(nullptr, nullptr) || shape.isEmpty();
- }
- 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<SkPaint::Join>(
- reporter,
- geo,
- [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);},
- SkPaint::kRound_Join, SkPaint::kBevel_Join,
- affectsStroke, true);
- };
- 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);
- };
- auto setOtherJoinAndLimit = [](SkPaint* p, SkScalar miter) {
- p->setStrokeJoin(SkPaint::kRound_Join);
- p->setStrokeMiter(miter);
- };
- 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<SkScalar>(
- reporter,
- geo,
- setMiterJoinAndLimit,
- 0.5f, 0.75f,
- mayHaveJoins,
- true);
- // The miter limit should not affect stroked and dashed-stroked cases when the join type is
- // not miter.
- test_stroke_param_impl<SkScalar>(
- reporter,
- geo,
- setOtherJoinAndLimit,
- 0.5f, 0.75f,
- false,
- false);
- }
- static void test_dash_fill(skiatest::Reporter* reporter, const Geo& geo) {
- // A dash with no stroke should have no effect
- using DashFactoryFn = sk_sp<SkPathEffect>(*)();
- for (DashFactoryFn md : {&make_dash, &make_null_dash}) {
- SkPaint dashFill;
- dashFill.setPathEffect((*md)());
- TestCase dashFillCase(geo, dashFill, reporter);
- TestCase fillCase(geo, SkPaint(), reporter);
- dashFillCase.compare(reporter, fillCase, TestCase::kAllSame_ComparisonExpecation);
- }
- }
- void test_null_dash(skiatest::Reporter* reporter, const Geo& geo) {
- SkPaint fill;
- SkPaint stroke;
- stroke.setStyle(SkPaint::kStroke_Style);
- stroke.setStrokeWidth(1.f);
- SkPaint dash;
- dash.setStyle(SkPaint::kStroke_Style);
- dash.setStrokeWidth(1.f);
- dash.setPathEffect(make_dash());
- SkPaint nullDash;
- nullDash.setStyle(SkPaint::kStroke_Style);
- nullDash.setStrokeWidth(1.f);
- nullDash.setPathEffect(make_null_dash());
- TestCase fillCase(geo, fill, reporter);
- TestCase strokeCase(geo, stroke, reporter);
- TestCase dashCase(geo, dash, reporter);
- TestCase nullDashCase(geo, nullDash, reporter);
- // We expect the null dash to be ignored so nullDashCase should match strokeCase, always.
- 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 (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 (geo.strokeIsConvertedToFill()) {
- nullDashCase.compare(reporter, dashCase, TestCase::kAllDifferent_ComparisonExpecation);
- } else {
- nullDashCase.compare(reporter, dashCase, TestCase::kSameUpToPE_ComparisonExpecation);
- }
- }
- 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.
- */
- class RRectPathEffect : SkPathEffect {
- public:
- static const SkRRect& RRect() {
- static const SkRRect kRRect = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 3, 5);
- return kRRect;
- }
- static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new RRectPathEffect); }
- Factory getFactory() const override { return nullptr; }
- const char* getTypeName() const override { return nullptr; }
- protected:
- bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
- const SkRect* cullR) const override {
- dst->reset();
- dst->addRRect(RRect());
- return true;
- }
- SkRect onComputeFastBounds(const SkRect& src) const override {
- return RRect().getBounds();
- }
- private:
- RRectPathEffect() {}
- };
- SkPaint fill;
- TestCase fillGeoCase(geo, fill, reporter);
- SkPaint pe;
- pe.setPathEffect(RRectPathEffect::Make());
- TestCase geoPECase(geo, pe, reporter);
- SkPaint peStroke;
- peStroke.setPathEffect(RRectPathEffect::Make());
- peStroke.setStrokeWidth(2.f);
- peStroke.setStyle(SkPaint::kStroke_Style);
- TestCase geoPEStrokeCase(geo, peStroke, reporter);
- // 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 (geo.fillChangesGeom()) {
- fillGeoCase.compare(reporter, geoPECase, TestCase::kAllDifferent_ComparisonExpecation);
- fillGeoCase.compare(reporter, geoPEStrokeCase,
- TestCase::kAllDifferent_ComparisonExpecation);
- } else {
- fillGeoCase.compare(reporter, geoPECase, TestCase::kSameUpToPE_ComparisonExpecation);
- fillGeoCase.compare(reporter, geoPEStrokeCase, TestCase::kSameUpToPE_ComparisonExpecation);
- }
- geoPECase.compare(reporter, geoPEStrokeCase,
- TestCase::kSameUpToStroke_ComparisonExpecation);
- TestCase rrectFillCase(reporter, RRectPathEffect::RRect(), fill);
- SkPaint stroke = peStroke;
- stroke.setPathEffect(nullptr);
- TestCase rrectStrokeCase(reporter, RRectPathEffect::RRect(), stroke);
- 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,
- nullptr));
- REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
- REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey());
- 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, nullptr));
- REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
- REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey());
- REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr,
- nullptr, nullptr));
- REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() ==
- rrectStrokeCase.appliedFullStyleKey());
- }
- void test_unknown_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
- /**
- * This path effect just adds two lineTos to the input path.
- */
- class AddLineTosPathEffect : SkPathEffect {
- public:
- static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new AddLineTosPathEffect); }
- Factory getFactory() const override { return nullptr; }
- const char* getTypeName() const override { return nullptr; }
- protected:
- bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
- const SkRect* cullR) const override {
- *dst = src;
- // To avoid triggering data-based keying of paths with few verbs we add many segments.
- for (int i = 0; i < 100; ++i) {
- dst->lineTo(SkIntToScalar(i), SkIntToScalar(i));
- }
- return true;
- }
- SkRect onComputeFastBounds(const SkRect& src) const override {
- SkRect dst = src;
- SkRectPriv::GrowToInclude(&dst, {0, 0});
- SkRectPriv::GrowToInclude(&dst, {100, 100});
- return dst;
- }
- private:
- AddLineTosPathEffect() {}
- };
- // This path effect should make the keys invalid when it is applied. We only produce a path
- // effect key for dash path effects. So the only way another arbitrary path effect can produce
- // a styled result with a key is to produce a non-path shape that has a purely geometric key.
- SkPaint peStroke;
- peStroke.setPathEffect(AddLineTosPathEffect::Make());
- peStroke.setStrokeWidth(2.f);
- peStroke.setStyle(SkPaint::kStroke_Style);
- TestCase geoPEStrokeCase(geo, peStroke, reporter);
- TestCase::SelfExpectations expectations;
- expectations.fPEHasEffect = true;
- expectations.fPEHasValidKey = false;
- expectations.fStrokeApplies = true;
- geoPEStrokeCase.testExpectations(reporter, expectations);
- }
- void test_make_hairline_path_effect(skiatest::Reporter* reporter, const Geo& geo) {
- /**
- * This path effect just changes the stroke rec to hairline.
- */
- class MakeHairlinePathEffect : SkPathEffect {
- public:
- static sk_sp<SkPathEffect> Make() {
- return sk_sp<SkPathEffect>(new MakeHairlinePathEffect);
- }
- Factory getFactory() const override { return nullptr; }
- const char* getTypeName() const override { return nullptr; }
- protected:
- bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* strokeRec,
- const SkRect* cullR) const override {
- *dst = src;
- strokeRec->setHairlineStyle();
- return true;
- }
- private:
- MakeHairlinePathEffect() {}
- };
- SkPaint fill;
- SkPaint pe;
- pe.setPathEffect(MakeHairlinePathEffect::Make());
- TestCase peCase(geo, pe, reporter);
- SkPath a, b, c;
- peCase.baseShape().asPath(&a);
- peCase.appliedPathEffectShape().asPath(&b);
- peCase.appliedFullStyleShape().asPath(&c);
- 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.
- // Instead we just check that the paths would fill the same both before and after styling.
- REPORTER_ASSERT(reporter, paths_fill_same(a, b));
- REPORTER_ASSERT(reporter, paths_fill_same(a, c));
- } else {
- // The base shape cannot perform canonicalization on the path's fill type because of an
- // unknown path effect. However, after the path effect is applied the resulting hairline
- // shape will canonicalize the path fill type since hairlines (and stroking in general)
- // don't distinguish between even/odd and non-zero winding.
- a.setFillType(b.getFillType());
- REPORTER_ASSERT(reporter, a == b);
- REPORTER_ASSERT(reporter, a == c);
- // If the resulting path is small enough then it will have a key.
- REPORTER_ASSERT(reporter, paths_fill_same(a, b));
- REPORTER_ASSERT(reporter, paths_fill_same(a, c));
- REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty());
- REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty());
- }
- REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline());
- REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline());
- }
- 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(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 (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(reporter, geo.path(), dashAndStroke);
- volatileCase.compare(reporter, nonVolatileCase, TestCase::kAllSame_ComparisonExpecation);
- } else {
- // None of the keys should be valid.
- REPORTER_ASSERT(reporter, !SkToBool(volatileCase.baseKey().count()));
- REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectKey().count()));
- REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedFullStyleKey().count()));
- REPORTER_ASSERT(reporter, !SkToBool(volatileCase.appliedPathEffectThenStrokeKey().count()));
- }
- }
- void test_path_effect_makes_empty_shape(skiatest::Reporter* reporter, const Geo& geo) {
- /**
- * This path effect returns an empty path (possibly inverted)
- */
- class EmptyPathEffect : SkPathEffect {
- public:
- static sk_sp<SkPathEffect> Make(bool invert) {
- return sk_sp<SkPathEffect>(new EmptyPathEffect(invert));
- }
- Factory getFactory() const override { return nullptr; }
- const char* getTypeName() const override { return nullptr; }
- protected:
- bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
- const SkRect* cullR) const override {
- dst->reset();
- if (fInvert) {
- dst->toggleInverseFillType();
- }
- return true;
- }
- SkRect onComputeFastBounds(const SkRect& src) const override {
- return { 0, 0, 0, 0 };
- }
- private:
- bool fInvert;
- EmptyPathEffect(bool invert) : fInvert(invert) {}
- };
- SkPath emptyPath;
- GrShape emptyShape(emptyPath);
- Key emptyKey;
- make_key(&emptyKey, emptyShape);
- REPORTER_ASSERT(reporter, emptyShape.isEmpty());
- emptyPath.toggleInverseFillType();
- GrShape invertedEmptyShape(emptyPath);
- Key invertedEmptyKey;
- make_key(&invertedEmptyKey, invertedEmptyShape);
- REPORTER_ASSERT(reporter, invertedEmptyShape.isEmpty());
- REPORTER_ASSERT(reporter, invertedEmptyKey != emptyKey);
- SkPaint pe;
- pe.setPathEffect(EmptyPathEffect::Make(false));
- TestCase geoPECase(geo, pe, reporter);
- REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == emptyKey);
- REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == emptyKey);
- REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectThenStrokeKey() == emptyKey);
- REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().isEmpty());
- REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().isEmpty());
- REPORTER_ASSERT(reporter, !geoPECase.appliedPathEffectShape().inverseFilled());
- REPORTER_ASSERT(reporter, !geoPECase.appliedFullStyleShape().inverseFilled());
- SkPaint peStroke;
- peStroke.setPathEffect(EmptyPathEffect::Make(false));
- peStroke.setStrokeWidth(2.f);
- peStroke.setStyle(SkPaint::kStroke_Style);
- TestCase geoPEStrokeCase(geo, peStroke, reporter);
- REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() == emptyKey);
- REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == emptyKey);
- REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectThenStrokeKey() == emptyKey);
- REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().isEmpty());
- REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleShape().isEmpty());
- REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedPathEffectShape().inverseFilled());
- REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().inverseFilled());
- pe.setPathEffect(EmptyPathEffect::Make(true));
- TestCase geoPEInvertCase(geo, pe, reporter);
- REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleKey() == invertedEmptyKey);
- REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectKey() == invertedEmptyKey);
- REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
- REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().isEmpty());
- REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().isEmpty());
- REPORTER_ASSERT(reporter, geoPEInvertCase.appliedPathEffectShape().inverseFilled());
- REPORTER_ASSERT(reporter, geoPEInvertCase.appliedFullStyleShape().inverseFilled());
- peStroke.setPathEffect(EmptyPathEffect::Make(true));
- TestCase geoPEInvertStrokeCase(geo, peStroke, reporter);
- REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleKey() == invertedEmptyKey);
- REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectKey() == invertedEmptyKey);
- REPORTER_ASSERT(reporter,
- geoPEInvertStrokeCase.appliedPathEffectThenStrokeKey() == invertedEmptyKey);
- REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().isEmpty());
- REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().isEmpty());
- REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedPathEffectShape().inverseFilled());
- REPORTER_ASSERT(reporter, geoPEInvertStrokeCase.appliedFullStyleShape().inverseFilled());
- }
- void test_path_effect_fails(skiatest::Reporter* reporter, const Geo& geo) {
- /**
- * This path effect always fails to apply.
- */
- class FailurePathEffect : SkPathEffect {
- public:
- static sk_sp<SkPathEffect> Make() { return sk_sp<SkPathEffect>(new FailurePathEffect); }
- Factory getFactory() const override { return nullptr; }
- const char* getTypeName() const override { return nullptr; }
- protected:
- bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*,
- const SkRect* cullR) const override {
- return false;
- }
- private:
- FailurePathEffect() {}
- };
- SkPaint fill;
- TestCase fillCase(geo, fill, reporter);
- SkPaint pe;
- pe.setPathEffect(FailurePathEffect::Make());
- TestCase peCase(geo, pe, reporter);
- SkPaint stroke;
- stroke.setStrokeWidth(2.f);
- stroke.setStyle(SkPaint::kStroke_Style);
- TestCase strokeCase(geo, stroke, reporter);
- SkPaint peStroke = stroke;
- peStroke.setPathEffect(FailurePathEffect::Make());
- TestCase peStrokeCase(geo, peStroke, reporter);
- // In general the path effect failure can cause some of the TestCase::compare() tests to fail
- // for at least two reasons: 1) We will initially treat the shape as unkeyable because of the
- // path effect, but then when the path effect fails we can key it. 2) GrShape will change its
- // mind about whether a unclosed rect is actually rect. The path effect initially bars us from
- // closing it but after the effect fails we can (for the fill+pe case). This causes different
- // routes through GrShape to have equivalent but different representations of the path (closed
- // or not) but that fill the same.
- SkPath a;
- SkPath b;
- fillCase.appliedPathEffectShape().asPath(&a);
- peCase.appliedPathEffectShape().asPath(&b);
- REPORTER_ASSERT(reporter, paths_fill_same(a, b));
- fillCase.appliedFullStyleShape().asPath(&a);
- peCase.appliedFullStyleShape().asPath(&b);
- REPORTER_ASSERT(reporter, paths_fill_same(a, b));
- strokeCase.appliedPathEffectShape().asPath(&a);
- peStrokeCase.appliedPathEffectShape().asPath(&b);
- REPORTER_ASSERT(reporter, paths_fill_same(a, b));
- strokeCase.appliedFullStyleShape().asPath(&a);
- peStrokeCase.appliedFullStyleShape().asPath(&b);
- REPORTER_ASSERT(reporter, paths_fill_same(a, b));
- }
- DEF_TEST(GrShape_empty_shape, reporter) {
- SkPath emptyPath;
- SkPath invertedEmptyPath;
- invertedEmptyPath.toggleInverseFillType();
- SkPaint fill;
- TestCase fillEmptyCase(reporter, emptyPath, fill);
- REPORTER_ASSERT(reporter, fillEmptyCase.baseShape().isEmpty());
- REPORTER_ASSERT(reporter, fillEmptyCase.appliedPathEffectShape().isEmpty());
- REPORTER_ASSERT(reporter, fillEmptyCase.appliedFullStyleShape().isEmpty());
- REPORTER_ASSERT(reporter, !fillEmptyCase.baseShape().inverseFilled());
- REPORTER_ASSERT(reporter, !fillEmptyCase.appliedPathEffectShape().inverseFilled());
- REPORTER_ASSERT(reporter, !fillEmptyCase.appliedFullStyleShape().inverseFilled());
- TestCase fillInvertedEmptyCase(reporter, invertedEmptyPath, fill);
- REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().isEmpty());
- REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().isEmpty());
- REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().isEmpty());
- REPORTER_ASSERT(reporter, fillInvertedEmptyCase.baseShape().inverseFilled());
- REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedPathEffectShape().inverseFilled());
- REPORTER_ASSERT(reporter, fillInvertedEmptyCase.appliedFullStyleShape().inverseFilled());
- Key emptyKey(fillEmptyCase.baseKey());
- REPORTER_ASSERT(reporter, emptyKey.count());
- Key inverseEmptyKey(fillInvertedEmptyCase.baseKey());
- REPORTER_ASSERT(reporter, inverseEmptyKey.count());
- TestCase::SelfExpectations expectations;
- expectations.fStrokeApplies = false;
- expectations.fPEHasEffect = false;
- // This will test whether applying style preserves emptiness
- fillEmptyCase.testExpectations(reporter, expectations);
- fillInvertedEmptyCase.testExpectations(reporter, expectations);
- // Stroking an empty path should have no effect
- SkPaint stroke;
- stroke.setStrokeWidth(2.f);
- stroke.setStyle(SkPaint::kStroke_Style);
- stroke.setStrokeJoin(SkPaint::kRound_Join);
- stroke.setStrokeCap(SkPaint::kRound_Cap);
- TestCase strokeEmptyCase(reporter, emptyPath, stroke);
- strokeEmptyCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
- TestCase strokeInvertedEmptyCase(reporter, invertedEmptyPath, stroke);
- strokeInvertedEmptyCase.compare(reporter, fillInvertedEmptyCase,
- TestCase::kAllSame_ComparisonExpecation);
- // Dashing and stroking an empty path should have no effect
- SkPaint dashAndStroke;
- dashAndStroke.setPathEffect(make_dash());
- dashAndStroke.setStrokeWidth(2.f);
- dashAndStroke.setStyle(SkPaint::kStroke_Style);
- TestCase dashAndStrokeEmptyCase(reporter, emptyPath, dashAndStroke);
- dashAndStrokeEmptyCase.compare(reporter, fillEmptyCase,
- TestCase::kAllSame_ComparisonExpecation);
- TestCase dashAndStrokeInvertexEmptyCase(reporter, invertedEmptyPath, dashAndStroke);
- // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
- dashAndStrokeInvertexEmptyCase.compare(reporter, fillEmptyCase,
- TestCase::kAllSame_ComparisonExpecation);
- // A shape made from an empty rrect should behave the same as an empty path when filled but not
- // when stroked. However, dashing an empty rrect produces an empty path leaving nothing to
- // stroke - so equivalent to filling an empty path.
- SkRRect emptyRRect = SkRRect::MakeEmpty();
- REPORTER_ASSERT(reporter, emptyRRect.getType() == SkRRect::kEmpty_Type);
- TestCase fillEmptyRRectCase(reporter, emptyRRect, fill);
- fillEmptyRRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
- TestCase strokeEmptyRRectCase(reporter, emptyRRect, stroke);
- strokeEmptyRRectCase.compare(reporter, strokeEmptyCase,
- TestCase::kAllDifferent_ComparisonExpecation);
- TestCase dashAndStrokeEmptyRRectCase(reporter, emptyRRect, dashAndStroke);
- dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase,
- TestCase::kAllSame_ComparisonExpecation);
- static constexpr SkPath::Direction kDir = SkPath::kCCW_Direction;
- static constexpr int kStart = 0;
- TestCase fillInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true, GrStyle(fill));
- fillInvertedEmptyRRectCase.compare(reporter, fillInvertedEmptyCase,
- TestCase::kAllSame_ComparisonExpecation);
- TestCase strokeInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true,
- GrStyle(stroke));
- strokeInvertedEmptyRRectCase.compare(reporter, strokeInvertedEmptyCase,
- TestCase::kAllDifferent_ComparisonExpecation);
- TestCase dashAndStrokeEmptyInvertedRRectCase(reporter, emptyRRect, kDir, kStart, true,
- GrStyle(dashAndStroke));
- dashAndStrokeEmptyInvertedRRectCase.compare(reporter, fillEmptyCase,
- TestCase::kAllSame_ComparisonExpecation);
- // Same for a rect.
- SkRect emptyRect = SkRect::MakeEmpty();
- TestCase fillEmptyRectCase(reporter, emptyRect, fill);
- fillEmptyRectCase.compare(reporter, fillEmptyCase, TestCase::kAllSame_ComparisonExpecation);
- TestCase dashAndStrokeEmptyRectCase(reporter, emptyRect, dashAndStroke);
- dashAndStrokeEmptyRectCase.compare(reporter, fillEmptyCase,
- TestCase::kAllSame_ComparisonExpecation);
- TestCase dashAndStrokeEmptyInvertedRectCase(reporter, SkRRect::MakeRect(emptyRect), kDir,
- kStart, true, GrStyle(dashAndStroke));
- // Dashing ignores inverseness so this is equivalent to the non-inverted empty fill.
- dashAndStrokeEmptyInvertedRectCase.compare(reporter, fillEmptyCase,
- TestCase::kAllSame_ComparisonExpecation);
- }
- // rect and oval types have rrect start indices that collapse to the same point. Here we select the
- // 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 Style {
- 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);
- // Use a bevel join to avoid complications of stroke+filled rects becoming filled rects before
- // applyStyle() is called.
- strokeRecs[kStrokeAndFill].setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 1.f);
- sk_sp<SkPathEffect> dashEffect = make_dash();
- static constexpr Style kStyleCnt = static_cast<Style>(SK_ARRAY_COUNT(strokeRecs));
- auto index = [](bool inverted,
- SkPath::Direction dir,
- unsigned start,
- Style style,
- bool dash) -> int {
- return inverted * (2 * 8 * kStyleCnt * 2) +
- dir * ( 8 * kStyleCnt * 2) +
- start * ( kStyleCnt * 2) +
- style * ( 2) +
- dash;
- };
- static const SkPath::Direction kSecondDirection = static_cast<SkPath::Direction>(1);
- const int cnt = index(true, kSecondDirection, 7, static_cast<Style>(kStyleCnt - 1), true) + 1;
- SkAutoTArray<GrShape> shapes(cnt);
- for (bool inverted : {false, true}) {
- for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
- for (unsigned start = 0; start < 8; ++start) {
- for (Style style : {kFill, kStroke, kHairline, kStrokeAndFill}) {
- for (bool dash : {false, true}) {
- sk_sp<SkPathEffect> pe = dash ? dashEffect : nullptr;
- shapes[index(inverted, dir, start, style, dash)] =
- GrShape(rrect, dir, start, SkToBool(inverted),
- GrStyle(strokeRecs[style], std::move(pe)));
- }
- }
- }
- }
- }
- // Get the keys for some example shape instances that we'll use for comparision against the
- // rest.
- static constexpr SkPath::Direction kExamplesDir = SkPath::kCW_Direction;
- static constexpr unsigned kExamplesStart = 0;
- const GrShape& exampleFillCase = shapes[index(false, kExamplesDir, kExamplesStart, kFill,
- false)];
- Key exampleFillCaseKey;
- make_key(&exampleFillCaseKey, exampleFillCase);
- const GrShape& exampleStrokeAndFillCase = shapes[index(false, kExamplesDir, kExamplesStart,
- kStrokeAndFill, false)];
- Key exampleStrokeAndFillCaseKey;
- make_key(&exampleStrokeAndFillCaseKey, exampleStrokeAndFillCase);
- const GrShape& exampleInvFillCase = shapes[index(true, kExamplesDir, kExamplesStart, kFill,
- false)];
- Key exampleInvFillCaseKey;
- make_key(&exampleInvFillCaseKey, exampleInvFillCase);
- const GrShape& exampleInvStrokeAndFillCase = shapes[index(true, kExamplesDir, kExamplesStart,
- kStrokeAndFill, false)];
- Key exampleInvStrokeAndFillCaseKey;
- make_key(&exampleInvStrokeAndFillCaseKey, exampleInvStrokeAndFillCase);
- const GrShape& exampleStrokeCase = shapes[index(false, kExamplesDir, kExamplesStart, kStroke,
- false)];
- Key exampleStrokeCaseKey;
- make_key(&exampleStrokeCaseKey, exampleStrokeCase);
- const GrShape& exampleInvStrokeCase = shapes[index(true, kExamplesDir, kExamplesStart, kStroke,
- false)];
- Key exampleInvStrokeCaseKey;
- make_key(&exampleInvStrokeCaseKey, exampleInvStrokeCase);
- const GrShape& exampleHairlineCase = shapes[index(false, kExamplesDir, kExamplesStart,
- kHairline, false)];
- Key exampleHairlineCaseKey;
- make_key(&exampleHairlineCaseKey, exampleHairlineCase);
- const GrShape& exampleInvHairlineCase = shapes[index(true, kExamplesDir, kExamplesStart,
- kHairline, false)];
- Key exampleInvHairlineCaseKey;
- make_key(&exampleInvHairlineCaseKey, exampleInvHairlineCase);
- // These are dummy initializations to suppress warnings.
- SkRRect queryRR = SkRRect::MakeEmpty();
- SkPath::Direction queryDir = SkPath::kCW_Direction;
- unsigned queryStart = ~0U;
- bool queryInverted = true;
- REPORTER_ASSERT(r, exampleFillCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
- REPORTER_ASSERT(r, queryRR == rrect);
- REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
- REPORTER_ASSERT(r, 0 == queryStart);
- REPORTER_ASSERT(r, !queryInverted);
- REPORTER_ASSERT(r, exampleInvFillCase.asRRect(&queryRR, &queryDir, &queryStart,
- &queryInverted));
- REPORTER_ASSERT(r, queryRR == rrect);
- REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
- REPORTER_ASSERT(r, 0 == queryStart);
- REPORTER_ASSERT(r, queryInverted);
- REPORTER_ASSERT(r, exampleStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
- &queryInverted));
- REPORTER_ASSERT(r, queryRR == rrect);
- REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
- REPORTER_ASSERT(r, 0 == queryStart);
- REPORTER_ASSERT(r, !queryInverted);
- REPORTER_ASSERT(r, exampleInvStrokeAndFillCase.asRRect(&queryRR, &queryDir, &queryStart,
- &queryInverted));
- REPORTER_ASSERT(r, queryRR == rrect);
- REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
- REPORTER_ASSERT(r, 0 == queryStart);
- REPORTER_ASSERT(r, queryInverted);
- REPORTER_ASSERT(r, exampleHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
- &queryInverted));
- REPORTER_ASSERT(r, queryRR == rrect);
- REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
- REPORTER_ASSERT(r, 0 == queryStart);
- REPORTER_ASSERT(r, !queryInverted);
- REPORTER_ASSERT(r, exampleInvHairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
- &queryInverted));
- REPORTER_ASSERT(r, queryRR == rrect);
- REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
- REPORTER_ASSERT(r, 0 == queryStart);
- REPORTER_ASSERT(r, queryInverted);
- REPORTER_ASSERT(r, exampleStrokeCase.asRRect(&queryRR, &queryDir, &queryStart, &queryInverted));
- REPORTER_ASSERT(r, queryRR == rrect);
- REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
- REPORTER_ASSERT(r, 0 == queryStart);
- REPORTER_ASSERT(r, !queryInverted);
- REPORTER_ASSERT(r, exampleInvStrokeCase.asRRect(&queryRR, &queryDir, &queryStart,
- &queryInverted));
- REPORTER_ASSERT(r, queryRR == rrect);
- REPORTER_ASSERT(r, SkPath::kCW_Direction == queryDir);
- REPORTER_ASSERT(r, 0 == queryStart);
- REPORTER_ASSERT(r, queryInverted);
- // 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 != exampleInvStrokeCaseKey);
- REPORTER_ASSERT(r, exampleFillCaseKey == exampleHairlineCaseKey);
- REPORTER_ASSERT(r, exampleFillCaseKey != exampleInvHairlineCaseKey);
- REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvFillCaseKey);
- REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvStrokeCaseKey);
- REPORTER_ASSERT(r, exampleInvStrokeAndFillCaseKey == exampleInvHairlineCaseKey);
- for (bool inverted : {false, true}) {
- for (SkPath::Direction dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) {
- for (unsigned start = 0; start < 8; ++start) {
- for (bool dash : {false, true}) {
- const GrShape& fillCase = shapes[index(inverted, dir, start, kFill, dash)];
- Key fillCaseKey;
- make_key(&fillCaseKey, fillCase);
- const GrShape& strokeAndFillCase = shapes[index(inverted, dir, start,
- 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[index(inverted, dir, start, kStroke, dash)];
- const GrShape& hairlineCase = shapes[index(inverted, dir, start, kHairline,
- dash)];
- TestCase e(strokeCase, r);
- TestCase g(hairlineCase, r);
- // Both hairline and stroke shapes must respect the dashing.
- if (dash) {
- // Dashing always ignores the inverseness. skbug.com/5421
- TestCase f(exampleStrokeCase, r);
- TestCase h(exampleHairlineCase, r);
- unsigned expectedStart = canonicalize_rrect_start(start, rrect);
- REPORTER_ASSERT(r, strokeCase.style().pathEffect());
- REPORTER_ASSERT(r, hairlineCase.style().pathEffect());
- REPORTER_ASSERT(r, strokeCase.asRRect(&queryRR, &queryDir, &queryStart,
- &queryInverted));
- REPORTER_ASSERT(r, queryRR == rrect);
- REPORTER_ASSERT(r, queryDir == dir);
- REPORTER_ASSERT(r, queryStart == expectedStart);
- REPORTER_ASSERT(r, !queryInverted);
- REPORTER_ASSERT(r, hairlineCase.asRRect(&queryRR, &queryDir, &queryStart,
- &queryInverted));
- REPORTER_ASSERT(r, queryRR == rrect);
- REPORTER_ASSERT(r, queryDir == dir);
- REPORTER_ASSERT(r, queryStart == expectedStart);
- REPORTER_ASSERT(r, !queryInverted);
- // 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 && SkPath::kCW_Direction == dir) {
- 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 {
- TestCase f(inverted ? exampleInvStrokeCase : exampleStrokeCase, r);
- TestCase h(inverted ? exampleInvHairlineCase : exampleHairlineCase, r);
- 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_lines, r) {
- static constexpr SkPoint kA { 1, 1};
- static constexpr SkPoint kB { 5, -9};
- static constexpr SkPoint kC {-3, 17};
- SkPath lineAB;
- lineAB.moveTo(kA);
- lineAB.lineTo(kB);
- SkPath lineBA;
- lineBA.moveTo(kB);
- lineBA.lineTo(kA);
- SkPath lineAC;
- lineAC.moveTo(kB);
- lineAC.lineTo(kC);
- SkPath invLineAB = lineAB;
- invLineAB.setFillType(SkPath::kInverseEvenOdd_FillType);
- SkPaint fill;
- SkPaint stroke;
- stroke.setStyle(SkPaint::kStroke_Style);
- stroke.setStrokeWidth(2.f);
- SkPaint hairline;
- hairline.setStyle(SkPaint::kStroke_Style);
- hairline.setStrokeWidth(0.f);
- SkPaint dash = stroke;
- dash.setPathEffect(make_dash());
- 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));
- SkPath path;
- path.toggleInverseFillType();
- TestCase fillEmptyInverted(r, path, fill);
- TestCase fillABInverted(r, invLineAB, fill);
- fillABInverted.compare(r, fillEmptyInverted, TestCase::kAllSame_ComparisonExpecation);
- REPORTER_ASSERT(r, !fillABInverted.baseShape().asLine(nullptr, nullptr));
- TestCase strokeAB(r, lineAB, stroke);
- TestCase strokeBA(r, lineBA, stroke);
- TestCase strokeAC(r, lineAC, stroke);
- TestCase hairlineAB(r, lineAB, hairline);
- TestCase hairlineBA(r, lineBA, hairline);
- TestCase hairlineAC(r, lineAC, hairline);
- TestCase dashAB(r, lineAB, dash);
- TestCase dashBA(r, lineBA, dash);
- TestCase dashAC(r, lineAC, dash);
- strokeAB.compare(r, fillAB, TestCase::kAllDifferent_ComparisonExpecation);
- strokeAB.compare(r, strokeBA, TestCase::kAllSame_ComparisonExpecation);
- strokeAB.compare(r, strokeAC, TestCase::kAllDifferent_ComparisonExpecation);
- hairlineAB.compare(r, hairlineBA, TestCase::kAllSame_ComparisonExpecation);
- hairlineAB.compare(r, hairlineAC, TestCase::kAllDifferent_ComparisonExpecation);
- dashAB.compare(r, dashBA, TestCase::kAllDifferent_ComparisonExpecation);
- dashAB.compare(r, dashAC, TestCase::kAllDifferent_ComparisonExpecation);
- strokeAB.compare(r, hairlineAB, TestCase::kSameUpToStroke_ComparisonExpecation);
- // One of dashAB or dashBA should have the same line as strokeAB. It depends upon how
- // GrShape canonicalizes line endpoints (when it can, i.e. when not dashed).
- bool canonicalizeAsAB;
- SkPoint canonicalPts[2] {kA, kB};
- // Init these to suppress warnings.
- bool inverted = true;
- SkPoint pts[2] {{0, 0}, {0, 0}};
- REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted);
- if (pts[0] == kA && pts[1] == kB) {
- canonicalizeAsAB = true;
- } else if (pts[1] == kA && pts[0] == kB) {
- canonicalizeAsAB = false;
- using std::swap;
- swap(canonicalPts[0], canonicalPts[1]);
- } else {
- ERRORF(r, "Should return pts (a,b) or (b, a)");
- return;
- }
- strokeAB.compare(r, canonicalizeAsAB ? dashAB : dashBA,
- TestCase::kSameUpToPE_ComparisonExpecation);
- REPORTER_ASSERT(r, strokeAB.baseShape().asLine(pts, &inverted) && !inverted &&
- pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
- REPORTER_ASSERT(r, hairlineAB.baseShape().asLine(pts, &inverted) && !inverted &&
- pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
- REPORTER_ASSERT(r, dashAB.baseShape().asLine(pts, &inverted) && !inverted &&
- pts[0] == kA && pts[1] == kB);
- REPORTER_ASSERT(r, dashBA.baseShape().asLine(pts, &inverted) && !inverted &&
- pts[0] == kB && pts[1] == kA);
- 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.
- dashInvAB.compare(r, dashAB, TestCase::kAllSame_ComparisonExpecation);
- REPORTER_ASSERT(r, strokeInvAB.baseShape().asLine(pts, &inverted) && inverted &&
- pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
- REPORTER_ASSERT(r, hairlineInvAB.baseShape().asLine(pts, &inverted) && inverted &&
- pts[0] == canonicalPts[0] && pts[1] == canonicalPts[1]);
- // Dashing ignores inverse.
- REPORTER_ASSERT(r, dashInvAB.baseShape().asLine(pts, &inverted) && !inverted &&
- pts[0] == kA && pts[1] == kB);
- }
- DEF_TEST(GrShape_stroked_lines, r) {
- static constexpr SkScalar kIntervals1[] = {1.f, 0.f};
- auto dash1 = SkDashPathEffect::Make(kIntervals1, SK_ARRAY_COUNT(kIntervals1), 0.f);
- REPORTER_ASSERT(r, dash1);
- static constexpr SkScalar kIntervals2[] = {10.f, 0.f, 5.f, 0.f};
- auto dash2 = SkDashPathEffect::Make(kIntervals2, SK_ARRAY_COUNT(kIntervals2), 10.f);
- REPORTER_ASSERT(r, dash2);
- sk_sp<SkPathEffect> pathEffects[] = {nullptr, std::move(dash1), std::move(dash2)};
- for (const auto& pe : pathEffects) {
- // Paints to try
- SkPaint buttCap;
- buttCap.setStyle(SkPaint::kStroke_Style);
- buttCap.setStrokeWidth(4);
- buttCap.setStrokeCap(SkPaint::kButt_Cap);
- buttCap.setPathEffect(pe);
- SkPaint squareCap = buttCap;
- squareCap.setStrokeCap(SkPaint::kSquare_Cap);
- squareCap.setPathEffect(pe);
- SkPaint roundCap = buttCap;
- roundCap.setStrokeCap(SkPaint::kRound_Cap);
- roundCap.setPathEffect(pe);
- // vertical
- SkPath linePath;
- linePath.moveTo(4, 4);
- linePath.lineTo(4, 5);
- SkPaint fill;
- make_TestCase(r, linePath, buttCap)->compare(
- r, TestCase(r, SkRect::MakeLTRB(2, 4, 6, 5), fill),
- TestCase::kAllSame_ComparisonExpecation);
- make_TestCase(r, linePath, squareCap)->compare(
- r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 7), fill),
- TestCase::kAllSame_ComparisonExpecation);
- make_TestCase(r, linePath, roundCap)->compare(r,
- TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 6, 7), 2, 2), fill),
- TestCase::kAllSame_ComparisonExpecation);
- // horizontal
- linePath.reset();
- linePath.moveTo(4, 4);
- linePath.lineTo(5, 4);
- make_TestCase(r, linePath, buttCap)->compare(
- r, TestCase(r, SkRect::MakeLTRB(4, 2, 5, 6), fill),
- TestCase::kAllSame_ComparisonExpecation);
- make_TestCase(r, linePath, squareCap)->compare(
- r, TestCase(r, SkRect::MakeLTRB(2, 2, 7, 6), fill),
- TestCase::kAllSame_ComparisonExpecation);
- make_TestCase(r, linePath, roundCap)->compare(
- r, TestCase(r, SkRRect::MakeRectXY(SkRect::MakeLTRB(2, 2, 7, 6), 2, 2), fill),
- TestCase::kAllSame_ComparisonExpecation);
- // point
- linePath.reset();
- linePath.moveTo(4, 4);
- linePath.lineTo(4, 4);
- make_TestCase(r, linePath, buttCap)->compare(
- r, TestCase(r, SkRect::MakeEmpty(), fill),
- TestCase::kAllSame_ComparisonExpecation);
- make_TestCase(r, linePath, squareCap)->compare(
- r, TestCase(r, SkRect::MakeLTRB(2, 2, 6, 6), fill),
- TestCase::kAllSame_ComparisonExpecation);
- make_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_short_path_keys, r) {
- SkPaint paints[4];
- paints[1].setStyle(SkPaint::kStroke_Style);
- paints[1].setStrokeWidth(5.f);
- paints[2].setStyle(SkPaint::kStroke_Style);
- paints[2].setStrokeWidth(0.f);
- paints[3].setStyle(SkPaint::kStrokeAndFill_Style);
- paints[3].setStrokeWidth(5.f);
- auto compare = [r, &paints] (const SkPath& pathA, const SkPath& pathB,
- TestCase::ComparisonExpecation expectation) {
- SkPath volatileA = pathA;
- SkPath volatileB = pathB;
- volatileA.setIsVolatile(true);
- volatileB.setIsVolatile(true);
- for (const SkPaint& paint : paints) {
- REPORTER_ASSERT(r, !GrShape(volatileA, paint).hasUnstyledKey());
- REPORTER_ASSERT(r, !GrShape(volatileB, paint).hasUnstyledKey());
- for (PathGeo::Invert invert : {PathGeo::Invert::kNo, PathGeo::Invert::kYes}) {
- TestCase caseA(PathGeo(pathA, invert), paint, r);
- TestCase caseB(PathGeo(pathB, invert), paint, r);
- caseA.compare(r, caseB, expectation);
- }
- }
- };
- SkPath pathA;
- SkPath pathB;
- // Two identical paths
- pathA.lineTo(10.f, 10.f);
- pathA.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
- pathB.lineTo(10.f, 10.f);
- pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
- compare(pathA, pathB, TestCase::kAllSame_ComparisonExpecation);
- // Give path b a different point
- pathB.reset();
- pathB.lineTo(10.f, 10.f);
- pathB.conicTo(21.f, 20.f, 20.f, 30.f, 0.7f);
- compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
- // Give path b a different conic weight
- pathB.reset();
- pathB.lineTo(10.f, 10.f);
- pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
- compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
- // Give path b an extra lineTo verb
- pathB.reset();
- pathB.lineTo(10.f, 10.f);
- pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.6f);
- pathB.lineTo(50.f, 50.f);
- compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
- // Give path b a close
- pathB.reset();
- pathB.lineTo(10.f, 10.f);
- pathB.conicTo(20.f, 20.f, 20.f, 30.f, 0.7f);
- pathB.close();
- compare(pathA, pathB, TestCase::kAllDifferent_ComparisonExpecation);
- }
- DEF_TEST(GrShape, reporter) {
- SkTArray<std::unique_ptr<Geo>> geos;
- SkTArray<std::unique_ptr<RRectPathGeo>> rrectPathGeos;
- for (auto r : { SkRect::MakeWH(10, 20),
- SkRect::MakeWH(-10, -20),
- SkRect::MakeWH(-10, 20),
- SkRect::MakeWH(10, -20)}) {
- 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))}) {
- geos.emplace_back(new RRectGeo(rr));
- test_rrect(reporter, rr);
- 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));
- }
- // Arcs
- geos.emplace_back(new ArcGeo(SkRect::MakeWH(200, 100), 12.f, 110.f, false));
- geos.emplace_back(new ArcGeo(SkRect::MakeWH(200, 100), 12.f, 110.f, true));
- {
- SkPath openRectPath;
- openRectPath.moveTo(0, 0);
- openRectPath.lineTo(10, 0);
- openRectPath.lineTo(10, 10);
- openRectPath.lineTo(0, 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);
- geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kNo));
- geos.emplace_back(new PathGeo(quadPath, PathGeo::Invert::kYes));
- }
- {
- SkPath linePath;
- linePath.lineTo(10, 10);
- 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);
- geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kNo));
- geos.emplace_back(new PathGeo(vLinePath, PathGeo::Invert::kYes));
- }
- {
- SkPath hLinePath;
- hLinePath.lineTo(10, 0);
- geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kNo));
- geos.emplace_back(new PathGeo(hLinePath, PathGeo::Invert::kYes));
- }
- 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<SkScalar>(
- reporter, *geos[i],
- [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
- SkIntToScalar(2), SkIntToScalar(4));
- 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 (int i = 0; i < rrectPathGeos.count(); ++i) {
- const RRectPathGeo& rrgeo = *rrectPathGeos[i];
- SkPaint fillPaint;
- TestCase fillPathCase(reporter, rrgeo.path(), fillPaint);
- SkRRect rrect;
- REPORTER_ASSERT(reporter, rrgeo.isNonPath(fillPaint) ==
- fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
- nullptr));
- 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(reporter, rrgeo.path(), strokePaint);
- if (rrgeo.isNonPath(strokePaint)) {
- REPORTER_ASSERT(reporter, strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr,
- nullptr));
- 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, PathGeo(SkPath(), PathGeo::Invert::kNo));
- }
- DEF_TEST(GrShape_arcs, reporter) {
- SkStrokeRec roundStroke(SkStrokeRec::kFill_InitStyle);
- roundStroke.setStrokeStyle(2.f);
- roundStroke.setStrokeParams(SkPaint::kRound_Cap, SkPaint::kRound_Join, 1.f);
- SkStrokeRec squareStroke(roundStroke);
- squareStroke.setStrokeParams(SkPaint::kSquare_Cap, SkPaint::kRound_Join, 1.f);
- SkStrokeRec roundStrokeAndFill(roundStroke);
- roundStrokeAndFill.setStrokeStyle(2.f, true);
- static constexpr SkScalar kIntervals[] = {1, 2};
- auto dash = SkDashPathEffect::Make(kIntervals, SK_ARRAY_COUNT(kIntervals), 1.5f);
- SkTArray<GrStyle> styles;
- styles.push_back(GrStyle::SimpleFill());
- styles.push_back(GrStyle::SimpleHairline());
- styles.push_back(GrStyle(roundStroke, nullptr));
- styles.push_back(GrStyle(squareStroke, nullptr));
- styles.push_back(GrStyle(roundStrokeAndFill, nullptr));
- styles.push_back(GrStyle(roundStroke, dash));
- for (const auto& style : styles) {
- // An empty rect never draws anything according to SkCanvas::drawArc() docs.
- TestCase emptyArc(GrShape::MakeArc(SkRect::MakeEmpty(), 0, 90.f, false, style), reporter);
- TestCase emptyPath(reporter, SkPath(), style);
- emptyArc.compare(reporter, emptyPath, TestCase::kAllSame_ComparisonExpecation);
- static constexpr SkRect kOval1{0, 0, 50, 50};
- static constexpr SkRect kOval2{50, 0, 100, 50};
- // Test that swapping starting and ending angle doesn't change the shape unless the arc
- // has a path effect. Also test that different ovals produce different shapes.
- TestCase arc1CW(GrShape::MakeArc(kOval1, 0, 90.f, false, style), reporter);
- TestCase arc1CCW(GrShape::MakeArc(kOval1, 90.f, -90.f, false, style), reporter);
- TestCase arc1CWWithCenter(GrShape::MakeArc(kOval1, 0, 90.f, true, style), reporter);
- TestCase arc1CCWWithCenter(GrShape::MakeArc(kOval1, 90.f, -90.f, true, style), reporter);
- TestCase arc2CW(GrShape::MakeArc(kOval2, 0, 90.f, false, style), reporter);
- TestCase arc2CWWithCenter(GrShape::MakeArc(kOval2, 0, 90.f, true, style), reporter);
- auto reversedExepectations = style.hasPathEffect()
- ? TestCase::kAllDifferent_ComparisonExpecation
- : TestCase::kAllSame_ComparisonExpecation;
- arc1CW.compare(reporter, arc1CCW, reversedExepectations);
- arc1CWWithCenter.compare(reporter, arc1CCWWithCenter, reversedExepectations);
- arc1CW.compare(reporter, arc2CW, TestCase::kAllDifferent_ComparisonExpecation);
- arc1CW.compare(reporter, arc1CWWithCenter, TestCase::kAllDifferent_ComparisonExpecation);
- arc1CWWithCenter.compare(reporter, arc2CWWithCenter,
- TestCase::kAllDifferent_ComparisonExpecation);
- // Test that two arcs that start at the same angle but specified differently are equivalent.
- TestCase arc3A(GrShape::MakeArc(kOval1, 224.f, 73.f, false, style), reporter);
- TestCase arc3B(GrShape::MakeArc(kOval1, 224.f - 360.f, 73.f, false, style), reporter);
- arc3A.compare(reporter, arc3B, TestCase::kAllDifferent_ComparisonExpecation);
- // Test that an arc that traverses the entire oval (and then some) is equivalent to the
- // oval itself unless there is a path effect.
- TestCase ovalArc(GrShape::MakeArc(kOval1, 150.f, -790.f, false, style), reporter);
- TestCase oval(GrShape(SkRRect::MakeOval(kOval1)), reporter);
- auto ovalExpectations = style.hasPathEffect() ? TestCase::kAllDifferent_ComparisonExpecation
- : TestCase::kAllSame_ComparisonExpecation;
- if (style.strokeRec().getWidth() >= 0 && style.strokeRec().getCap() != SkPaint::kButt_Cap) {
- ovalExpectations = TestCase::kAllDifferent_ComparisonExpecation;
- }
- ovalArc.compare(reporter, oval, ovalExpectations);
- // If the the arc starts/ends at the center then it is then equivalent to the oval only for
- // simple fills.
- TestCase ovalArcWithCenter(GrShape::MakeArc(kOval1, 304.f, 1225.f, true, style), reporter);
- ovalExpectations = style.isSimpleFill() ? TestCase::kAllSame_ComparisonExpecation
- : TestCase::kAllDifferent_ComparisonExpecation;
- ovalArcWithCenter.compare(reporter, oval, ovalExpectations);
- }
- }
|