diff options
-rw-r--r-- | include/core/SkPath.h | 36 | ||||
-rw-r--r-- | include/core/SkPathRef.h | 65 | ||||
-rw-r--r-- | src/core/SkPath.cpp | 73 | ||||
-rw-r--r-- | src/core/SkPathRef.cpp | 127 | ||||
-rw-r--r-- | tests/RRectInPathTest.cpp | 388 |
5 files changed, 600 insertions, 89 deletions
diff --git a/include/core/SkPath.h b/include/core/SkPath.h index 3ff0af15a2..3c68ff7d84 100644 --- a/include/core/SkPath.h +++ b/include/core/SkPath.h @@ -154,6 +154,17 @@ public: */ bool isOval(SkRect* rect) const { return fPathRef->isOval(rect); } + /** Returns true if the path is a round rect. + * + * @param rrect Returns the bounding rect and radii of this round rect. + * + * @return true if this path is a round rect. + * Tracking whether a path is a round rect is considered an + * optimization for performance and so some paths that are in + * fact round rects can report false. + */ + bool isRRect(SkRRect* rrect) const { return fPathRef->isRRect(rrect); } + /** Clear any lines and curves from the path, making it empty. This frees up internal storage associated with those segments. On Android, does not change fSourcePath. @@ -918,10 +929,14 @@ public: */ class SK_API RawIter { public: - RawIter(); - RawIter(const SkPath&); + RawIter() {} + RawIter(const SkPath& path) { + setPath(path); + } - void setPath(const SkPath&); + void setPath(const SkPath& path) { + fRawIter.setPathRef(*path.fPathRef.get()); + } /** Return the next verb in this iteration of the path. When all segments have been visited, return kDone_Verb. @@ -930,15 +945,17 @@ public: This must not be NULL. @return The verb for the current segment */ - Verb next(SkPoint pts[4]); + Verb next(SkPoint pts[4]) { + return (Verb) fRawIter.next(pts); + } - SkScalar conicWeight() const { return *fConicWeights; } + SkScalar conicWeight() const { + return fRawIter.conicWeight(); + } private: - const SkPoint* fPts; - const uint8_t* fVerbs; - const uint8_t* fVerbStop; - const SkScalar* fConicWeights; + SkPathRef::Iter fRawIter; + friend class SkPath; }; /** @@ -1066,6 +1083,7 @@ private: friend class SkAutoDisableDirectionCheck; friend class SkBench_AddPathTest; // perf test reversePathTo friend class PathTest_Private; // unit test reversePathTo + friend class ForceIsRRect_Private; // unit test isRRect }; #endif diff --git a/include/core/SkPathRef.h b/include/core/SkPathRef.h index c09f6e87e4..86f55c9bca 100644 --- a/include/core/SkPathRef.h +++ b/include/core/SkPathRef.h @@ -11,6 +11,7 @@ #include "SkMatrix.h" #include "SkPoint.h" +#include "SkRRect.h" #include "SkRect.h" #include "SkRefCnt.h" #include "SkTDArray.h" @@ -100,12 +101,39 @@ public: void setIsOval(bool isOval) { fPathRef->setIsOval(isOval); } + void setIsRRect(bool isRRect) { fPathRef->setIsRRect(isRRect); } + void setBounds(const SkRect& rect) { fPathRef->setBounds(rect); } private: SkPathRef* fPathRef; }; + class SK_API Iter { + public: + Iter(); + Iter(const SkPathRef&); + + void setPathRef(const SkPathRef&); + + /** Return the next verb in this iteration of the path. When all + segments have been visited, return kDone_Verb. + + @param pts The points representing the current verb and/or segment + This must not be NULL. + @return The verb for the current segment + */ + uint8_t next(SkPoint pts[4]); + + SkScalar conicWeight() const { return *fConicWeights; } + + private: + const SkPoint* fPts; + const uint8_t* fVerbs; + const uint8_t* fVerbStop; + const SkScalar* fConicWeights; + }; + public: /** * Gets a path ref with no verbs or points. @@ -142,12 +170,20 @@ public: */ bool isOval(SkRect* rect) const { if (fIsOval && rect) { - *rect = getBounds(); + *rect = this->getBounds(); } return SkToBool(fIsOval); } + bool isRRect(SkRRect* rrect) const { + if (fIsRRect && rrect) { + *rrect = this->getRRect(); + } + return SkToBool(fIsRRect); + } + + bool hasComputedBounds() const { return !fBoundsIsDirty; } @@ -164,6 +200,8 @@ public: return fBounds; } + SkRRect getRRect() const; + /** * Transforms a path ref by a matrix, allocating a new one only if necessary. */ @@ -250,6 +288,7 @@ public: private: enum SerializationOffsets { + kIsRRect_SerializationShift = 26, // requires 1 bit kIsFinite_SerializationShift = 25, // requires 1 bit kIsOval_SerializationShift = 24, // requires 1 bit kSegmentMask_SerializationShift = 0 // requires 4 bits @@ -265,6 +304,7 @@ private: fGenerationID = kEmptyGenID; fSegmentMask = 0; fIsOval = false; + fIsRRect = false; SkDEBUGCODE(fEditorsAttached = 0;) SkDEBUGCODE(this->validate();) } @@ -312,6 +352,7 @@ private: fSegmentMask = 0; fIsOval = false; + fIsRRect = false; size_t newSize = sizeof(uint8_t) * verbCount + sizeof(SkPoint) * pointCount; size_t newReserve = sizeof(uint8_t) * reserveVerbs + sizeof(SkPoint) * reservePoints; @@ -411,9 +452,18 @@ private: void setIsOval(bool isOval) { fIsOval = isOval; } + void setIsRRect(bool isRRect) { fIsRRect = isRRect; } + + // called only by the editor. Note that this is not a const function. SkPoint* getPoints() { SkDEBUGCODE(this->validate();) fIsOval = false; + fIsRRect = false; + return fPoints; + } + + const SkPoint* getPoints() const { + SkDEBUGCODE(this->validate();) return fPoints; } @@ -424,11 +474,6 @@ private: }; mutable SkRect fBounds; - mutable uint8_t fBoundsIsDirty; - mutable SkBool8 fIsFinite; // only meaningful if bounds are valid - - SkBool8 fIsOval; - uint8_t fSegmentMask; SkPoint* fPoints; // points to begining of the allocation uint8_t* fVerbs; // points just past the end of the allocation (verbs grow backwards) @@ -445,7 +490,15 @@ private: SkTDArray<GenIDChangeListener*> fGenIDChangeListeners; // pointers are owned + mutable uint8_t fBoundsIsDirty; + mutable SkBool8 fIsFinite; // only meaningful if bounds are valid + + SkBool8 fIsOval; + SkBool8 fIsRRect; + uint8_t fSegmentMask; + friend class PathRefTest_Private; + friend class ForceIsRRect_Private; // unit test isRRect typedef SkRefCnt INHERITED; }; diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp index a3f5e13d7f..2d4976a82a 100644 --- a/src/core/SkPath.cpp +++ b/src/core/SkPath.cpp @@ -1072,6 +1072,7 @@ void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) return; } + bool isRRect = hasOnlyMoveTos(); const SkRect& bounds = rrect.getBounds(); if (rrect.isRect()) { @@ -1119,6 +1120,9 @@ void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) } this->close(); + SkPathRef::Editor ed(&fPathRef); + ed.setIsRRect(isRRect); + SkASSERT(this->countVerbs() == initialVerbCount + kVerbs); } @@ -1861,75 +1865,6 @@ SkPath::Verb SkPath::Iter::doNext(SkPoint ptsParam[4]) { /////////////////////////////////////////////////////////////////////////////// -SkPath::RawIter::RawIter() { -#ifdef SK_DEBUG - fPts = nullptr; - fConicWeights = nullptr; -#endif - // need to init enough to make next() harmlessly return kDone_Verb - fVerbs = nullptr; - fVerbStop = nullptr; -} - -SkPath::RawIter::RawIter(const SkPath& path) { - this->setPath(path); -} - -void SkPath::RawIter::setPath(const SkPath& path) { - fPts = path.fPathRef->points(); - fVerbs = path.fPathRef->verbs(); - fVerbStop = path.fPathRef->verbsMemBegin(); - fConicWeights = path.fPathRef->conicWeights() - 1; // begin one behind -} - -SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) { - SkASSERT(pts); - if (fVerbs == fVerbStop) { - return kDone_Verb; - } - - // fVerbs points one beyond next verb so decrement first. - unsigned verb = *(--fVerbs); - const SkPoint* srcPts = fPts; - - switch (verb) { - case kMove_Verb: - pts[0] = srcPts[0]; - srcPts += 1; - break; - case kLine_Verb: - pts[0] = srcPts[-1]; - pts[1] = srcPts[0]; - srcPts += 1; - break; - case kConic_Verb: - fConicWeights += 1; - // fall-through - case kQuad_Verb: - pts[0] = srcPts[-1]; - pts[1] = srcPts[0]; - pts[2] = srcPts[1]; - srcPts += 2; - break; - case kCubic_Verb: - pts[0] = srcPts[-1]; - pts[1] = srcPts[0]; - pts[2] = srcPts[1]; - pts[3] = srcPts[2]; - srcPts += 3; - break; - case kClose_Verb: - break; - case kDone_Verb: - SkASSERT(fVerbs == fVerbStop); - break; - } - fPts = srcPts; - return (Verb)verb; -} - -/////////////////////////////////////////////////////////////////////////////// - /* Format in compressed buffer: [ptCount, verbCount, pts[], verbs[]] */ diff --git a/src/core/SkPathRef.cpp b/src/core/SkPathRef.cpp index 119711381f..12429aecfc 100644 --- a/src/core/SkPathRef.cpp +++ b/src/core/SkPathRef.cpp @@ -113,15 +113,15 @@ void SkPathRef::CreateTransformedCopy(SkAutoTUnref<SkPathRef>* dst, (*dst)->fSegmentMask = src.fSegmentMask; // It's an oval only if it stays a rect. - (*dst)->fIsOval = src.fIsOval && matrix.rectStaysRect(); + bool rectStaysRect = matrix.rectStaysRect(); + (*dst)->fIsOval = src.fIsOval && rectStaysRect; + (*dst)->fIsRRect = src.fIsRRect && rectStaysRect; SkDEBUGCODE((*dst)->validate();) } SkPathRef* SkPathRef::CreateFromBuffer(SkRBuffer* buffer) { SkPathRef* ref = new SkPathRef; - bool isOval; - uint8_t segmentMask; int32_t packed; if (!buffer->readS32(&packed)) { @@ -130,8 +130,9 @@ SkPathRef* SkPathRef::CreateFromBuffer(SkRBuffer* buffer) { } ref->fIsFinite = (packed >> kIsFinite_SerializationShift) & 1; - segmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF; - isOval = (packed >> kIsOval_SerializationShift) & 1; + uint8_t segmentMask = (packed >> kSegmentMask_SerializationShift) & 0xF; + bool isOval = (packed >> kIsOval_SerializationShift) & 1; + bool isRRect = (packed >> kIsRRect_SerializationShift) & 1; int32_t verbCount, pointCount, conicCount; if (!buffer->readU32(&(ref->fGenerationID)) || @@ -159,6 +160,7 @@ SkPathRef* SkPathRef::CreateFromBuffer(SkRBuffer* buffer) { // resetToSize clears fSegmentMask and fIsOval ref->fSegmentMask = segmentMask; ref->fIsOval = isOval; + ref->fIsRRect = isRRect; return ref; } @@ -174,6 +176,7 @@ void SkPathRef::Rewind(SkAutoTUnref<SkPathRef>* pathRef) { (*pathRef)->fConicWeights.rewind(); (*pathRef)->fSegmentMask = 0; (*pathRef)->fIsOval = false; + (*pathRef)->fIsRRect = false; SkDEBUGCODE((*pathRef)->validate();) } else { int oldVCnt = (*pathRef)->countVerbs(); @@ -240,6 +243,7 @@ void SkPathRef::writeToBuffer(SkWBuffer* buffer) const { int32_t packed = ((fIsFinite & 1) << kIsFinite_SerializationShift) | ((fIsOval & 1) << kIsOval_SerializationShift) | + ((fIsRRect & 1) << kIsRRect_SerializationShift) | (fSegmentMask << kSegmentMask_SerializationShift); buffer->write32(packed); @@ -281,6 +285,7 @@ void SkPathRef::copy(const SkPathRef& ref, } fSegmentMask = ref.fSegmentMask; fIsOval = ref.fIsOval; + fIsRRect = ref.fIsRRect; SkDEBUGCODE(this->validate();) } @@ -352,6 +357,7 @@ SkPoint* SkPathRef::growForRepeatedVerb(int /*SkPath::Verb*/ verb, fBoundsIsDirty = true; // this also invalidates fIsFinite if (dirtyAfterEdit) { fIsOval = false; + fIsRRect = false; } if (SkPath::kConic_Verb == verb) { @@ -410,6 +416,7 @@ SkPoint* SkPathRef::growForVerb(int /* SkPath::Verb*/ verb, SkScalar weight) { fBoundsIsDirty = true; // this also invalidates fIsFinite if (dirtyAfterEdit) { fIsOval = false; + fIsRRect = false; } if (SkPath::kConic_Verb == verb) { @@ -456,6 +463,116 @@ void SkPathRef::callGenIDChangeListeners() { fGenIDChangeListeners.deleteAll(); } +SkRRect SkPathRef::getRRect() const { + const SkRect& bounds = this->getBounds(); + SkVector radii[4] = {{0, 0}, {0, 0}, {0, 0}, {0, 0}}; + Iter iter(*this); + SkPoint pts[4]; + uint8_t verb = iter.next(pts); + SkASSERT(SkPath::kMove_Verb == verb); + while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { + if (SkPath::kConic_Verb == verb) { + SkVector v1_0 = pts[1] - pts[0]; + SkVector v2_1 = pts[2] - pts[1]; + SkVector dxdy; + if (v1_0.fX) { + SkASSERT(!v2_1.fX && !v1_0.fY); + dxdy.set(SkScalarAbs(v1_0.fX), SkScalarAbs(v2_1.fY)); + } else if (!v1_0.fY) { + SkASSERT(!v2_1.fX || !v2_1.fY); + dxdy.set(SkScalarAbs(v2_1.fX), SkScalarAbs(v2_1.fY)); + } else { + SkASSERT(!v2_1.fY); + dxdy.set(SkScalarAbs(v2_1.fX), SkScalarAbs(v1_0.fY)); + } + SkRRect::Corner corner = + pts[1].fX == bounds.fLeft ? + pts[1].fY == bounds.fTop ? + SkRRect::kUpperLeft_Corner : SkRRect::kLowerLeft_Corner : + pts[1].fY == bounds.fTop ? + SkRRect::kUpperRight_Corner : SkRRect::kLowerRight_Corner; + SkASSERT(!radii[corner].fX && !radii[corner].fY); + radii[corner] = dxdy; + } else { + SkASSERT((verb == SkPath::kLine_Verb + && (!(pts[1].fX - pts[0].fX) || !(pts[1].fY - pts[0].fY))) + || verb == SkPath::kClose_Verb); + } + } + SkRRect rrect; + rrect.setRectRadii(bounds, radii); + return rrect; +} + +/////////////////////////////////////////////////////////////////////////////// + +SkPathRef::Iter::Iter() { +#ifdef SK_DEBUG + fPts = nullptr; + fConicWeights = nullptr; +#endif + // need to init enough to make next() harmlessly return kDone_Verb + fVerbs = nullptr; + fVerbStop = nullptr; +} + +SkPathRef::Iter::Iter(const SkPathRef& path) { + this->setPathRef(path); +} + +void SkPathRef::Iter::setPathRef(const SkPathRef& path) { + fPts = path.points(); + fVerbs = path.verbs(); + fVerbStop = path.verbsMemBegin(); + fConicWeights = path.conicWeights() - 1; // begin one behind +} + +uint8_t SkPathRef::Iter::next(SkPoint pts[4]) { + SkASSERT(pts); + if (fVerbs == fVerbStop) { + return (uint8_t) SkPath::kDone_Verb; + } + + // fVerbs points one beyond next verb so decrement first. + unsigned verb = *(--fVerbs); + const SkPoint* srcPts = fPts; + + switch (verb) { + case SkPath::kMove_Verb: + pts[0] = srcPts[0]; + srcPts += 1; + break; + case SkPath::kLine_Verb: + pts[0] = srcPts[-1]; + pts[1] = srcPts[0]; + srcPts += 1; + break; + case SkPath::kConic_Verb: + fConicWeights += 1; + // fall-through + case SkPath::kQuad_Verb: + pts[0] = srcPts[-1]; + pts[1] = srcPts[0]; + pts[2] = srcPts[1]; + srcPts += 2; + break; + case SkPath::kCubic_Verb: + pts[0] = srcPts[-1]; + pts[1] = srcPts[0]; + pts[2] = srcPts[1]; + pts[3] = srcPts[2]; + srcPts += 3; + break; + case SkPath::kClose_Verb: + break; + case SkPath::kDone_Verb: + SkASSERT(fVerbs == fVerbStop); + break; + } + fPts = srcPts; + return (uint8_t) verb; +} + #ifdef SK_DEBUG void SkPathRef::validate() const { this->INHERITED::validate(); diff --git a/tests/RRectInPathTest.cpp b/tests/RRectInPathTest.cpp new file mode 100644 index 0000000000..c6b1d9c15e --- /dev/null +++ b/tests/RRectInPathTest.cpp @@ -0,0 +1,388 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkMatrix.h" +#include "SkPath.h" +#include "SkPathRef.h" +#include "SkPathOps.h" +#include "SkRRect.h" +#include "Test.h" + +static SkRRect path_contains_rrect(skiatest::Reporter* reporter, const SkPath& path) { + SkRRect out; + REPORTER_ASSERT(reporter, path.isRRect(&out)); + SkPath path2, xorBoth; + path2.addRRect(out); + if (path == path2) { + return out; + } + Op(path, path2, SkPathOp::kXOR_SkPathOp, &xorBoth); + REPORTER_ASSERT(reporter, xorBoth.isEmpty()); + return out; +} + +static SkRRect inner_path_contains_rrect(skiatest::Reporter* reporter, const SkRRect& in) { + switch (in.getType()) { + case SkRRect::kEmpty_Type: + case SkRRect::kRect_Type: + case SkRRect::kOval_Type: + return in; + default: + break; + } + SkPath path; + path.addRRect(in); + return path_contains_rrect(reporter, path); +} + +static void path_contains_rrect_check(skiatest::Reporter* reporter, const SkRRect& in) { + SkRRect out = inner_path_contains_rrect(reporter, in); + if (in != out) { + SkDebugf(""); + } + REPORTER_ASSERT(reporter, in == out); +} + +static void path_contains_rrect_nocheck(skiatest::Reporter* reporter, const SkRRect& in) { + SkRRect out = inner_path_contains_rrect(reporter, in); + if (in == out) { + SkDebugf(""); + } +} + +static void path_contains_rrect_check(skiatest::Reporter* reporter, const SkRect& r, + SkVector v[4]) { + SkRRect rrect; + rrect.setRectRadii(r, v); + path_contains_rrect_check(reporter, rrect); +} + +class ForceIsRRect_Private { +public: + ForceIsRRect_Private(SkPath* path) { + path->fPathRef->setIsRRect(true); + } +}; + +static void force_path_contains_rrect(skiatest::Reporter* reporter, SkPath& path) { + ForceIsRRect_Private force_rrect(&path); + path_contains_rrect(reporter, path); +} + +static void test_undetected_paths(skiatest::Reporter* reporter) { + SkPath path; + path.moveTo(0, 62.5f); + path.lineTo(0, 3.5f); + path.conicTo(0, 0, 3.5f, 0, 0.70710677f); + path.lineTo(196.5f, 0); + path.conicTo(200, 0, 200, 3.5f, 0.70710677f); + path.lineTo(200, 62.5f); + path.conicTo(200, 66, 196.5f, 66, 0.70710677f); + path.lineTo(3.5f, 66); + path.conicTo(0, 66, 0, 62.5, 0.70710677f); + path.close(); + force_path_contains_rrect(reporter, path); + + path.reset(); + path.moveTo(0, 81.5f); + path.lineTo(0, 3.5f); + path.conicTo(0, 0, 3.5f, 0, 0.70710677f); + path.lineTo(149.5, 0); + path.conicTo(153, 0, 153, 3.5f, 0.70710677f); + path.lineTo(153, 81.5f); + path.conicTo(153, 85, 149.5f, 85, 0.70710677f); + path.lineTo(3.5f, 85); + path.conicTo(0, 85, 0, 81.5f, 0.70710677f); + path.close(); + force_path_contains_rrect(reporter, path); + + path.reset(); + path.moveTo(14, 1189); + path.lineTo(14, 21); + path.conicTo(14, 14, 21, 14, 0.70710677f); + path.lineTo(1363, 14); + path.conicTo(1370, 14, 1370, 21, 0.70710677f); + path.lineTo(1370, 1189); + path.conicTo(1370, 1196, 1363, 1196, 0.70710677f); + path.lineTo(21, 1196); + path.conicTo(14, 1196, 14, 1189, 0.70710677f); + path.close(); + force_path_contains_rrect(reporter, path); + + path.reset(); + path.moveTo(14, 1743); + path.lineTo(14, 21); + path.conicTo(14, 14, 21, 14, 0.70710677f); + path.lineTo(1363, 14); + path.conicTo(1370, 14, 1370, 21, 0.70710677f); + path.lineTo(1370, 1743); + path.conicTo(1370, 1750, 1363, 1750, 0.70710677f); + path.lineTo(21, 1750); + path.conicTo(14, 1750, 14, 1743, 0.70710677f); + path.close(); + force_path_contains_rrect(reporter, path); +} + +static const SkScalar kWidth = 100.0f; +static const SkScalar kHeight = 100.0f; + +static void test_tricky_radii(skiatest::Reporter* reporter) { + { + // crbug.com/458522 + SkRRect rr; + const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 }; + const SkScalar rad = 12814; + const SkVector vec[] = { { rad, rad }, { 0, rad }, { rad, rad }, { 0, rad } }; + rr.setRectRadii(bounds, vec); + path_contains_rrect_check(reporter, rr); + } + + { + // crbug.com//463920 + SkRect r = SkRect::MakeLTRB(0, 0, 1009, 33554432.0); + SkVector radii[4] = { + { 13.0f, 8.0f }, { 170.0f, 2.0 }, { 256.0f, 33554432.0 }, { 110.0f, 5.0f } + }; + SkRRect rr; + rr.setRectRadii(r, radii); + path_contains_rrect_nocheck(reporter, rr); + } +} + +static void test_empty_crbug_458524(skiatest::Reporter* reporter) { + SkRRect rr; + const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 }; + const SkScalar rad = 40; + rr.setRectXY(bounds, rad, rad); + path_contains_rrect_check(reporter, rr); + + SkRRect other; + SkMatrix matrix; + matrix.setScale(0, 1); + rr.transform(matrix, &other); + path_contains_rrect_check(reporter, rr); +} + +static void test_inset(skiatest::Reporter* reporter) { + SkRRect rr, rr2; + SkRect r = { 0, 0, 100, 100 }; + + rr.setRect(r); + rr.inset(-20, -20, &rr2); + path_contains_rrect_check(reporter, rr); + + rr.inset(20, 20, &rr2); + path_contains_rrect_check(reporter, rr); + + rr.inset(r.width()/2, r.height()/2, &rr2); + path_contains_rrect_check(reporter, rr); + + rr.setRectXY(r, 20, 20); + rr.inset(19, 19, &rr2); + path_contains_rrect_check(reporter, rr); + rr.inset(20, 20, &rr2); + path_contains_rrect_check(reporter, rr); +} + + +static void test_9patch_rrect(skiatest::Reporter* reporter, + const SkRect& rect, + SkScalar l, SkScalar t, SkScalar r, SkScalar b, + bool checkRadii) { + SkRRect rr; + rr.setNinePatch(rect, l, t, r, b); + if (checkRadii) { + path_contains_rrect_check(reporter, rr); + } else { + path_contains_rrect_nocheck(reporter, rr); + } + + SkRRect rr2; // construct the same RR using the most general set function + SkVector radii[4] = { { l, t }, { r, t }, { r, b }, { l, b } }; + rr2.setRectRadii(rect, radii); + if (checkRadii) { + path_contains_rrect_check(reporter, rr); + } else { + path_contains_rrect_nocheck(reporter, rr); + } +} + +// Test out the basic API entry points +static void test_round_rect_basic(skiatest::Reporter* reporter) { + + //---- + SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight); + + SkRRect rr1; + rr1.setRect(rect); + path_contains_rrect_check(reporter, rr1); + + SkRRect rr1_2; // construct the same RR using the most general set function + SkVector rr1_2_radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }; + rr1_2.setRectRadii(rect, rr1_2_radii); + path_contains_rrect_check(reporter, rr1_2); + SkRRect rr1_3; // construct the same RR using the nine patch set function + rr1_3.setNinePatch(rect, 0, 0, 0, 0); + path_contains_rrect_check(reporter, rr1_2); + + //---- + SkPoint halfPoint = { SkScalarHalf(kWidth), SkScalarHalf(kHeight) }; + SkRRect rr2; + rr2.setOval(rect); + path_contains_rrect_check(reporter, rr2); + + SkRRect rr2_2; // construct the same RR using the most general set function + SkVector rr2_2_radii[4] = { { halfPoint.fX, halfPoint.fY }, { halfPoint.fX, halfPoint.fY }, + { halfPoint.fX, halfPoint.fY }, { halfPoint.fX, halfPoint.fY } }; + rr2_2.setRectRadii(rect, rr2_2_radii); + path_contains_rrect_check(reporter, rr2_2); + SkRRect rr2_3; // construct the same RR using the nine patch set function + rr2_3.setNinePatch(rect, halfPoint.fX, halfPoint.fY, halfPoint.fX, halfPoint.fY); + path_contains_rrect_check(reporter, rr2_3); + + //---- + SkPoint p = { 5, 5 }; + SkRRect rr3; + rr3.setRectXY(rect, p.fX, p.fY); + path_contains_rrect_check(reporter, rr3); + + SkRRect rr3_2; // construct the same RR using the most general set function + SkVector rr3_2_radii[4] = { { 5, 5 }, { 5, 5 }, { 5, 5 }, { 5, 5 } }; + rr3_2.setRectRadii(rect, rr3_2_radii); + path_contains_rrect_check(reporter, rr3_2); + SkRRect rr3_3; // construct the same RR using the nine patch set function + rr3_3.setNinePatch(rect, 5, 5, 5, 5); + path_contains_rrect_check(reporter, rr3_3); + + //---- + test_9patch_rrect(reporter, rect, 10, 9, 8, 7, true); + + { + // Test out the rrect from skia:3466 + SkRect rect2 = SkRect::MakeLTRB(0.358211994f, 0.755430222f, 0.872866154f, 0.806214333f); + + test_9patch_rrect(reporter, + rect2, + 0.926942348f, 0.642850280f, 0.529063463f, 0.587844372f, + false); + } + + //---- + SkPoint radii2[4] = { { 0, 0 }, { 0, 0 }, { 50, 50 }, { 20, 50 } }; + + SkRRect rr5; + rr5.setRectRadii(rect, radii2); + path_contains_rrect_check(reporter, rr5); +} + +// Test out the cases when the RR degenerates to a rect +static void test_round_rect_rects(skiatest::Reporter* reporter) { + + //---- + SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight); + SkRRect rr1; + rr1.setRectXY(rect, 0, 0); + + path_contains_rrect_check(reporter, rr1); + + //---- + SkPoint radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }; + + SkRRect rr2; + rr2.setRectRadii(rect, radii); + + path_contains_rrect_check(reporter, rr2); + + //---- + SkPoint radii2[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } }; + + SkRRect rr3; + rr3.setRectRadii(rect, radii2); + path_contains_rrect_check(reporter, rr3); +} + +// Test out the cases when the RR degenerates to an oval +static void test_round_rect_ovals(skiatest::Reporter* reporter) { + //---- + SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight); + SkRRect rr1; + rr1.setRectXY(rect, SkScalarHalf(kWidth), SkScalarHalf(kHeight)); + + path_contains_rrect_check(reporter, rr1); +} + +// Test out the non-degenerate RR cases +static void test_round_rect_general(skiatest::Reporter* reporter) { + //---- + SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight); + SkRRect rr1; + rr1.setRectXY(rect, 20, 20); + + path_contains_rrect_check(reporter, rr1); + + //---- + SkPoint radii[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } }; + + SkRRect rr2; + rr2.setRectRadii(rect, radii); + + path_contains_rrect_check(reporter, rr2); +} + +static void test_round_rect_iffy_parameters(skiatest::Reporter* reporter) { + SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight); + SkPoint radii[4] = { { 50, 100 }, { 100, 50 }, { 50, 100 }, { 100, 50 } }; + SkRRect rr1; + rr1.setRectRadii(rect, radii); + path_contains_rrect_nocheck(reporter, rr1); +} + +static void set_radii(SkVector radii[4], int index, float rad) { + sk_bzero(radii, sizeof(SkVector) * 4); + radii[index].set(rad, rad); +} + +static void test_skbug_3239(skiatest::Reporter* reporter) { + const float min = SkBits2Float(0xcb7f16c8); /* -16717512.000000 */ + const float max = SkBits2Float(0x4b7f1c1d); /* 16718877.000000 */ + const float big = SkBits2Float(0x4b7f1bd7); /* 16718807.000000 */ + + const float rad = 33436320; + + const SkRect rectx = SkRect::MakeLTRB(min, min, max, big); + const SkRect recty = SkRect::MakeLTRB(min, min, big, max); + + SkVector radii[4]; + for (int i = 0; i < 4; ++i) { + set_radii(radii, i, rad); + path_contains_rrect_check(reporter, rectx, radii); + path_contains_rrect_check(reporter, recty, radii); + } +} + +static void test_mix(skiatest::Reporter* reporter) { + // Test out mixed degenerate and non-degenerate geometry with Conics + const SkVector radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 100, 100 } }; + SkRect r = SkRect::MakeWH(100, 100); + SkRRect rr; + rr.setRectRadii(r, radii); + path_contains_rrect_check(reporter, rr); +} + +DEF_TEST(RoundRectInPath, reporter) { + test_tricky_radii(reporter); + test_empty_crbug_458524(reporter); + test_inset(reporter); + test_round_rect_basic(reporter); + test_round_rect_rects(reporter); + test_round_rect_ovals(reporter); + test_round_rect_general(reporter); + test_undetected_paths(reporter); + test_round_rect_iffy_parameters(reporter); + test_skbug_3239(reporter); + test_mix(reporter); +} |