From f338d7c860bf0bca82cac793069522311a3dbb1a Mon Sep 17 00:00:00 2001 From: "commit-bot@chromium.org" Date: Mon, 17 Mar 2014 21:17:30 +0000 Subject: Add nine patch type to SkRRect. BUG=skia:2181 Committed: http://code.google.com/p/skia/source/detail?r=13833 R=robertphillips@google.com, reed@google.com Author: bsalomon@google.com Review URL: https://codereview.chromium.org/196343015 git-svn-id: http://skia.googlecode.com/svn/trunk@13839 2bbb7eff-a529-9590-31e7-b0007b416f81 --- include/core/SkRRect.h | 26 +++++++----- src/core/SkRRect.cpp | 75 ++++++++++++++++++++++++++++++++++- src/effects/SkBlurMaskFilter.cpp | 4 +- src/gpu/effects/GrRRectEffect.cpp | 2 +- src/utils/SkLua.cpp | 1 + src/utils/debugger/SkObjectParser.cpp | 2 + tests/RoundRectTest.cpp | 39 +++++++++++++++--- 7 files changed, 129 insertions(+), 20 deletions(-) diff --git a/include/core/SkRRect.h b/include/core/SkRRect.h index c09d2d46fe..5223ae7072 100644 --- a/include/core/SkRRect.h +++ b/include/core/SkRRect.h @@ -71,6 +71,14 @@ public: //!< the curves) nor a rect (i.e., both radii are non-zero) kSimple_Type, + //!< The RR is non-empty and the two left x radii are equal, the two top + //!< y radii are equal, and the same for the right and bottom but it is + //!< neither an rect, oval, nor a simple RR. It is called "nine patch" + //!< because the centers of the corner ellipses form an axis aligned + //!< rect with edges that divide the RR into an 9 rectangular patches: + //!< an interior patch, four edge patches, and four corner patches. + kNinePatch_Type, + //!< A fully general (non-empty) RR. Some of the x and/or y radii are //!< different from the others and there must be one corner where //!< both radii are non-zero. @@ -99,21 +107,11 @@ public: inline bool isSimpleCircular() const { return this->isSimple() && fRadii[0].fX == fRadii[0].fY; } + inline bool isNinePatch() const { return kNinePatch_Type == this->getType(); } inline bool isComplex() const { return kComplex_Type == this->getType(); } bool allCornersCircular() const; - /** - * Are both x-radii the same on the two left corners, and similar for the top, right, and - * bottom. When this is the case the four ellipse centers form a rectangle. - */ - bool isNinePatch() const { - return fRadii[kUpperLeft_Corner].fX == fRadii[kLowerLeft_Corner].fX && - fRadii[kUpperRight_Corner].fX == fRadii[kLowerRight_Corner].fX && - fRadii[kUpperLeft_Corner].fY == fRadii[kUpperRight_Corner].fY && - fRadii[kLowerLeft_Corner].fY == fRadii[kLowerRight_Corner].fY; - } - SkScalar width() const { return fRect.width(); } SkScalar height() const { return fRect.height(); } @@ -171,6 +169,12 @@ public: */ void setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad); + /** + * Initialize the rr with one radius per-side. + */ + void setNinePatch(const SkRect& rect, SkScalar leftRad, SkScalar topRad, + SkScalar rightRad, SkScalar bottomRad); + /** * Initialize the RR with potentially different radii for all four corners. */ diff --git a/src/core/SkRRect.cpp b/src/core/SkRRect.cpp index 915ed75327..082646c2a7 100644 --- a/src/core/SkRRect.cpp +++ b/src/core/SkRRect.cpp @@ -43,6 +43,61 @@ void SkRRect::setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) { SkDEBUGCODE(this->validate();) } +void SkRRect::setNinePatch(const SkRect& rect, SkScalar leftRad, SkScalar topRad, + SkScalar rightRad, SkScalar bottomRad) { + if (rect.isEmpty()) { + this->setEmpty(); + return; + } + + leftRad = SkMaxScalar(leftRad, 0); + topRad = SkMaxScalar(topRad, 0); + rightRad = SkMaxScalar(rightRad, 0); + bottomRad = SkMaxScalar(bottomRad, 0); + + SkScalar scale = SK_Scalar1; + if (leftRad + rightRad > rect.width()) { + scale = SkScalarDiv(rect.width(), leftRad + rightRad); + } + if (topRad + bottomRad > rect.height()) { + scale = SkMinScalar(scale, SkScalarDiv(rect.width(), leftRad + rightRad)); + } + + if (scale < SK_Scalar1) { + leftRad = SkScalarMul(leftRad, scale); + topRad = SkScalarMul(topRad, scale); + rightRad = SkScalarMul(rightRad, scale); + bottomRad = SkScalarMul(bottomRad, scale); + } + + if (leftRad == rightRad && topRad == bottomRad) { + if (leftRad >= SkScalarHalf(rect.width()) && topRad >= SkScalarHalf(rect.height())) { + fType = kOval_Type; + } else if (0 == leftRad || 0 == topRad) { + // If the left and (by equality check above) right radii are zero then it is a rect. + // Same goes for top/bottom. + fType = kRect_Type; + leftRad = 0; + topRad = 0; + rightRad = 0; + bottomRad = 0; + } else { + fType = kSimple_Type; + } + } else { + fType = kNinePatch_Type; + } + + fRect = rect; + fRadii[kUpperLeft_Corner].set(leftRad, topRad); + fRadii[kUpperRight_Corner].set(rightRad, topRad); + fRadii[kLowerRight_Corner].set(rightRad, bottomRad); + fRadii[kLowerLeft_Corner].set(leftRad, bottomRad); + + SkDEBUGCODE(this->validate();) +} + + void SkRRect::setRectRadii(const SkRect& rect, const SkVector radii[4]) { if (rect.isEmpty()) { this->setEmpty(); @@ -200,6 +255,13 @@ bool SkRRect::contains(const SkRect& rect) const { this->checkCornerContainment(rect.fLeft, rect.fBottom); } +static bool radii_are_nine_patch(const SkVector radii[4]) { + return radii[SkRRect::kUpperLeft_Corner].fX == radii[SkRRect::kLowerLeft_Corner].fX && + radii[SkRRect::kUpperLeft_Corner].fY == radii[SkRRect::kUpperRight_Corner].fY && + radii[SkRRect::kUpperRight_Corner].fX == radii[SkRRect::kLowerRight_Corner].fX && + radii[SkRRect::kLowerLeft_Corner].fY == radii[SkRRect::kLowerRight_Corner].fY; +} + // There is a simplified version of this method in setRectXY void SkRRect::computeType() const { SkDEBUGCODE(this->validate();) @@ -238,7 +300,11 @@ void SkRRect::computeType() const { return; } - fType = kComplex_Type; + if (radii_are_nine_patch(fRadii)) { + fType = kNinePatch_Type; + } else { + fType = kComplex_Type; + } } static bool matrix_only_scale_and_translate(const SkMatrix& matrix) { @@ -391,6 +457,7 @@ void SkRRect::validate() const { allCornersSquare = false; } } + bool patchesOfNine = radii_are_nine_patch(fRadii); switch (fType) { case kEmpty_Type: @@ -417,9 +484,15 @@ void SkRRect::validate() const { SkASSERT(!fRect.isEmpty()); SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare); break; + case kNinePatch_Type: + SkASSERT(!fRect.isEmpty()); + SkASSERT(!allRadiiZero && !allRadiiSame && !allCornersSquare); + SkASSERT(patchesOfNine); + break; case kComplex_Type: SkASSERT(!fRect.isEmpty()); SkASSERT(!allRadiiZero && !allRadiiSame && !allCornersSquare); + SkASSERT(!patchesOfNine); break; case kUnknown_Type: // no limits on this diff --git a/src/effects/SkBlurMaskFilter.cpp b/src/effects/SkBlurMaskFilter.cpp index a5cdc4f703..ff98daf048 100644 --- a/src/effects/SkBlurMaskFilter.cpp +++ b/src/effects/SkBlurMaskFilter.cpp @@ -288,10 +288,10 @@ SkBlurMaskFilterImpl::filterRRectToNine(const SkRRect& rrect, const SkMatrix& ma // already have code for rectangles. return kUnimplemented_FilterReturn; + // These three can take advantage of this fast path. case SkRRect::kSimple_Type: - // Fall through. + case SkRRect::kNinePatch_Type: case SkRRect::kComplex_Type: - // These can take advantage of this fast path. break; } diff --git a/src/gpu/effects/GrRRectEffect.cpp b/src/gpu/effects/GrRRectEffect.cpp index 9d83785810..61652c1745 100644 --- a/src/gpu/effects/GrRRectEffect.cpp +++ b/src/gpu/effects/GrRRectEffect.cpp @@ -658,7 +658,7 @@ GrEffectRef* GrRRectEffect::Create(GrEffectEdgeType edgeType, const SkRRect& rre return EllipticalRRectEffect::Create(edgeType, EllipticalRRectEffect::kSimple_RRectType, rrect); } - } else if (rrect.isComplex()) { + } else if (rrect.isComplex() || rrect.isNinePatch()) { // Check for the "tab" cases - two adjacent circular corners and two square corners. SkScalar radius = 0; cornerFlags = 0; diff --git a/src/utils/SkLua.cpp b/src/utils/SkLua.cpp index 4b87458923..44f2211b8f 100644 --- a/src/utils/SkLua.cpp +++ b/src/utils/SkLua.cpp @@ -1208,6 +1208,7 @@ static const char* rrect_type(const SkRRect& rr) { case SkRRect::kRect_Type: return "rect"; case SkRRect::kOval_Type: return "oval"; case SkRRect::kSimple_Type: return "simple"; + case SkRRect::kNinePatch_Type: return "nine-patch"; case SkRRect::kComplex_Type: return "complex"; } SkDEBUGFAIL("never get here"); diff --git a/src/utils/debugger/SkObjectParser.cpp b/src/utils/debugger/SkObjectParser.cpp index 028300c5a5..a3b208544e 100644 --- a/src/utils/debugger/SkObjectParser.cpp +++ b/src/utils/debugger/SkObjectParser.cpp @@ -241,6 +241,8 @@ SkString* SkObjectParser::RRectToString(const SkRRect& rrect, const char* title) mRRect->append("oval"); } else if (rrect.isSimple()) { mRRect->append("simple"); + } else if (rrect.isNinePatch()) { + mRRect->append("nine-patch"); } else { SkASSERT(rrect.isComplex()); mRRect->append("complex"); diff --git a/tests/RoundRectTest.cpp b/tests/RoundRectTest.cpp index 9b2eecd19c..0a528a6535 100644 --- a/tests/RoundRectTest.cpp +++ b/tests/RoundRectTest.cpp @@ -60,6 +60,13 @@ static void test_round_rect_basic(skiatest::Reporter* reporter) { for (int i = 0; i < 4; ++i) { REPORTER_ASSERT(reporter, zeroPt == rr1.radii((SkRRect::Corner) i)); } + 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); + REPORTER_ASSERT(reporter, rr1_2 == rr1 && rr1_2.getType() == rr1.getType()); + SkRRect rr1_3; // construct the same RR using the nine patch set function + rr1_3.setNinePatch(rect, 0, 0, 0, 0); + REPORTER_ASSERT(reporter, rr1_3 == rr1 && rr1_3.getType() == rr1.getType()); //---- SkPoint halfPoint = { SkScalarHalf(kWidth), SkScalarHalf(kHeight) }; @@ -73,6 +80,14 @@ static void test_round_rect_basic(skiatest::Reporter* reporter) { REPORTER_ASSERT(reporter, rr2.radii((SkRRect::Corner) i).equalsWithinTolerance(halfPoint)); } + 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); + REPORTER_ASSERT(reporter, rr2_2 == rr2 && rr2_2.getType() == rr2.getType()); + 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); + REPORTER_ASSERT(reporter, rr2_3 == rr2 && rr2_3.getType() == rr2.getType()); //---- SkPoint p = { 5, 5 }; @@ -85,19 +100,33 @@ static void test_round_rect_basic(skiatest::Reporter* reporter) { for (int i = 0; i < 4; ++i) { REPORTER_ASSERT(reporter, p == rr3.radii((SkRRect::Corner) i)); } + 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); + REPORTER_ASSERT(reporter, rr3_2 == rr3 && rr3_2.getType() == rr3.getType()); + SkRRect rr3_3; // construct the same RR using the nine patch set function + rr3_3.setNinePatch(rect, 5, 5, 5, 5); + REPORTER_ASSERT(reporter, rr3_3 == rr3 && rr3_3.getType() == rr3.getType()); //---- - SkPoint radii[4] = { { 5, 5 }, { 5, 5 }, { 5, 5 }, { 5, 5 } }; + SkRect ninePatchRadii = { 10, 9, 8, 7 }; SkRRect rr4; - rr4.setRectRadii(rect, radii); + rr4.setNinePatch(rect, ninePatchRadii.fLeft, ninePatchRadii.fTop, ninePatchRadii.fRight, + ninePatchRadii.fBottom); - REPORTER_ASSERT(reporter, SkRRect::kSimple_Type == rr4.type()); + REPORTER_ASSERT(reporter, SkRRect::kNinePatch_Type == rr4.type()); REPORTER_ASSERT(reporter, rr4.rect() == rect); + SkPoint rquad[4]; + ninePatchRadii.toQuad(rquad); for (int i = 0; i < 4; ++i) { - REPORTER_ASSERT(reporter, radii[i] == rr4.radii((SkRRect::Corner) i)); + REPORTER_ASSERT(reporter, rquad[i] == rr4.radii((SkRRect::Corner) i)); } + SkRRect rr4_2; // construct the same RR using the most general set function + SkVector rr4_2_radii[4] = { { 10, 9 }, { 8, 9 }, {8, 7 }, { 10, 7 } }; + rr4_2.setRectRadii(rect, rr4_2_radii); + REPORTER_ASSERT(reporter, rr4_2 == rr4 && rr4_2.getType() == rr4.getType()); //---- SkPoint radii2[4] = { { 0, 0 }, { 0, 0 }, { 50, 50 }, { 20, 50 } }; @@ -114,7 +143,7 @@ static void test_round_rect_basic(skiatest::Reporter* reporter) { // Test out == & != REPORTER_ASSERT(reporter, empty != rr3); - REPORTER_ASSERT(reporter, rr3 == rr4); + REPORTER_ASSERT(reporter, rr3 != rr4); REPORTER_ASSERT(reporter, rr4 != rr5); } -- cgit v1.2.3