Add effect-based clip for nine-patch SkRRects.

BUG=skia:2181
R=jvanverth@google.com, robertphillips@google.com, rmistry@google.com

Author: bsalomon@google.com

Review URL: https://codereview.chromium.org/194703011

git-svn-id: http://skia.googlecode.com/svn/trunk@13794 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gm/rrects.cpp b/gm/rrects.cpp
index 6c63af9..b89c79f 100644
--- a/gm/rrects.cpp
+++ b/gm/rrects.cpp
@@ -185,7 +185,7 @@
     static const int kTileY = 40;
 
     static const int kNumSimpleCases = 7;
-    static const int kNumComplexCases = 27;
+    static const int kNumComplexCases = 29;
     static const SkVector gRadii[kNumComplexCases][4];
 
     static const int kNumRRects = kNumSimpleCases + kNumComplexCases;
@@ -240,6 +240,10 @@
     { { 0, 0 }, { 0, 0 }, { 15, 15 }, { 0, 0 } },
     { { 0, 0 }, { 15, 15 }, { 0, 0 }, { 0, 0 } },
     { { 15, 15 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
+
+    // nine patch elliptical
+    { { 5, 7 }, { 8, 7 }, { 8, 12 }, { 5, 12 } },
+    { { 0, 7 }, { 8, 7 }, { 8, 12 }, { 0, 12 } },
 };
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/include/core/SkRRect.h b/include/core/SkRRect.h
index 9878b54..c09d2d4 100644
--- a/include/core/SkRRect.h
+++ b/include/core/SkRRect.h
@@ -103,6 +103,17 @@
 
     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(); }
 
diff --git a/src/gpu/effects/GrRRectEffect.cpp b/src/gpu/effects/GrRRectEffect.cpp
index 51404be..324d02d 100644
--- a/src/gpu/effects/GrRRectEffect.cpp
+++ b/src/gpu/effects/GrRRectEffect.cpp
@@ -375,22 +375,27 @@
 
 class GLEllipticalRRectEffect;
 
-/**
- * Currently this effect only supports "simple" elliptical round rects (i.e.
- * the corners all have a common x/y radii pair).
- */
 class EllipticalRRectEffect : public GrEffect {
 public:
-    // This effect only supports rrects where the radii are >= kRadiusMin
+    // This effect currently works for these two classifications of SkRRects
+    enum RRectType {
+        kSimple_RRectType,       // SkRRect::kSimple_Type
+        kNinePatch_RRectType,    // The two left x radii are the same, the two
+                                 // top y radii are the same, etc.
+    };
+
+    // This effect only supports rrects where the radii are >= kRadiusMin.
     static const SkScalar kRadiusMin;
 
-    static GrEffectRef* Create(GrEffectEdgeType, const SkRRect&);
+    static GrEffectRef* Create(GrEffectEdgeType, RRectType, const SkRRect&);
 
     virtual ~EllipticalRRectEffect() {};
     static const char* Name() { return "EllipticalRRect"; }
 
     const SkRRect& getRRect() const { return fRRect; }
 
+    RRectType getRRectType() const { return fRRectType; }
+
     GrEffectEdgeType getEdgeType() const { return fEdgeType; }
 
     typedef GLEllipticalRRectEffect GLEffect;
@@ -400,11 +405,12 @@
     virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE;
 
 private:
-    EllipticalRRectEffect(GrEffectEdgeType, const SkRRect&);
+    EllipticalRRectEffect(GrEffectEdgeType, RRectType, const SkRRect&);
 
     virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE;
 
     SkRRect             fRRect;
+    RRectType           fRRectType;
     GrEffectEdgeType    fEdgeType;
 
     GR_DECLARE_EFFECT_TEST;
@@ -415,9 +421,11 @@
 const SkScalar EllipticalRRectEffect::kRadiusMin = 0.5f;
 
 GrEffectRef* EllipticalRRectEffect::Create(GrEffectEdgeType edgeType,
+                                           RRectType rrType,
                                            const SkRRect& rrect) {
-//    SkASSERT(kFillAA_GrEffectEdgeType == edgeType || kInverseFillAA_GrEffectEdgeType == edgeType);
-    return CreateEffectRef(AutoEffectUnref(SkNEW_ARGS(EllipticalRRectEffect, (edgeType, rrect))));
+    SkASSERT(kFillAA_GrEffectEdgeType == edgeType || kInverseFillAA_GrEffectEdgeType == edgeType);
+    return CreateEffectRef(AutoEffectUnref(SkNEW_ARGS(EllipticalRRectEffect, (edgeType, rrType,
+                                                                              rrect))));
 }
 
 void EllipticalRRectEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
@@ -428,14 +436,17 @@
     return GrTBackendEffectFactory<EllipticalRRectEffect>::getInstance();
 }
 
-EllipticalRRectEffect::EllipticalRRectEffect(GrEffectEdgeType edgeType, const SkRRect& rrect)
+EllipticalRRectEffect::EllipticalRRectEffect(GrEffectEdgeType edgeType, RRectType rrType,
+                                             const SkRRect& rrect)
     : fRRect(rrect)
+    , fRRectType(rrType)
     , fEdgeType(edgeType){
     this->setWillReadFragmentPosition();
 }
 
 bool EllipticalRRectEffect::onIsEqual(const GrEffect& other) const {
     const EllipticalRRectEffect& erre = CastEffect<EllipticalRRectEffect>(other);
+    // No need to check fRRectType as it is derived from fRRect.
     return fEdgeType == erre.fEdgeType && fRRect == erre.fRRect;
 }
 
@@ -449,15 +460,34 @@
                                                GrTexture*[]) {
     SkScalar w = random->nextRangeScalar(20.f, 1000.f);
     SkScalar h = random->nextRangeScalar(20.f, 1000.f);
-    SkScalar rx = random->nextRangeF(kRadiusMin, 9.f);
-    SkScalar ry = random->nextRangeF(kRadiusMin, 9.f);
+    SkVector r[4];
+    r[SkRRect::kUpperLeft_Corner].fX = random->nextRangeF(kRadiusMin, 9.f);
+    // ensure at least one corner really is elliptical
+    do {
+        r[SkRRect::kUpperLeft_Corner].fY = random->nextRangeF(kRadiusMin, 9.f);
+    } while (r[SkRRect::kUpperLeft_Corner].fY == r[SkRRect::kUpperLeft_Corner].fX);
+
     SkRRect rrect;
-    rrect.setRectXY(SkRect::MakeWH(w, h), rx, ry);
+    if (random->nextBool()) {
+        // half the time create a four-radii rrect.
+        r[SkRRect::kLowerRight_Corner].fX = random->nextRangeF(kRadiusMin, 9.f);
+        r[SkRRect::kLowerRight_Corner].fY = random->nextRangeF(kRadiusMin, 9.f);
+
+        r[SkRRect::kUpperRight_Corner].fX = r[SkRRect::kLowerRight_Corner].fX;
+        r[SkRRect::kUpperRight_Corner].fY = r[SkRRect::kUpperLeft_Corner].fY;
+
+        r[SkRRect::kLowerLeft_Corner].fX = r[SkRRect::kUpperLeft_Corner].fX;
+        r[SkRRect::kLowerLeft_Corner].fY = r[SkRRect::kLowerRight_Corner].fY;
+
+        rrect.setRectRadii(SkRect::MakeWH(w, h), r);
+    } else {
+        rrect.setRectXY(SkRect::MakeWH(w, h), r[SkRRect::kUpperLeft_Corner].fX,
+                                              r[SkRRect::kUpperLeft_Corner].fY);
+    }
     GrEffectRef* effect;
     do {
-        GrEffectEdgeType et = random->nextBool() ? kFillAA_GrEffectEdgeType :
-                                                   kInverseFillAA_GrEffectEdgeType;
-        effect = EllipticalRRectEffect::Create(et, rrect);
+        GrEffectEdgeType et = (GrEffectEdgeType)random->nextULessThan(kGrEffectEdgeTypeCnt);
+        effect = GrRRectEffect::Create(et, rrect);
     } while (NULL == effect);
     return effect;
 }
@@ -482,7 +512,7 @@
 
 private:
     GrGLUniformManager::UniformHandle   fInnerRectUniform;
-    GrGLUniformManager::UniformHandle   fInvRadiusXYSqdUniform;
+    GrGLUniformManager::UniformHandle   fInvRadiiSqdUniform;
     SkRRect                             fPrevRRect;
     typedef GrGLEffect INHERITED;
 };
@@ -502,16 +532,11 @@
                                        const TextureSamplerArray& samplers) {
     const EllipticalRRectEffect& erre = drawEffect.castEffect<EllipticalRRectEffect>();
     const char *rectName;
-    const char *invRadiusXYSqdName;
     // The inner rect is the rrect bounds inset by the x/y radii
     fInnerRectUniform = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
                                             kVec4f_GrSLType,
                                             "innerRect",
                                             &rectName);
-    fInvRadiusXYSqdUniform = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
-                                              kVec2f_GrSLType,
-                                              "invRadiusXY",
-                                              &invRadiusXYSqdName);
     const char* fragmentPos = builder->fragmentPosition();
     // At each quarter-ellipse corner we compute a vector that is the offset of the fragment pos
     // to the ellipse center. The vector is pinned in x and y to be in the quarter-plane relevant
@@ -526,9 +551,33 @@
     // need be computed to determine the min alpha.
     builder->fsCodeAppendf("\t\tvec2 dxy0 = %s.xy - %s.xy;\n", rectName, fragmentPos);
     builder->fsCodeAppendf("\t\tvec2 dxy1 = %s.xy - %s.zw;\n", fragmentPos, rectName);
-    builder->fsCodeAppend("\t\tvec2 dxy = max(max(dxy0, dxy1), 0.0);\n");
-    // Z is the x/y offsets divided by squared radii.
-    builder->fsCodeAppendf("\t\tvec2 Z = dxy * %s;\n", invRadiusXYSqdName);
+    switch (erre.getRRectType()) {
+        case EllipticalRRectEffect::kSimple_RRectType: {
+            const char *invRadiiXYSqdName;
+            fInvRadiiSqdUniform = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
+                                                      kVec2f_GrSLType,
+                                                      "invRadiiXY",
+                                                      &invRadiiXYSqdName);
+            builder->fsCodeAppend("\t\tvec2 dxy = max(max(dxy0, dxy1), 0.0);\n");
+            // Z is the x/y offsets divided by squared radii.
+            builder->fsCodeAppendf("\t\tvec2 Z = dxy * %s;\n", invRadiiXYSqdName);
+            break;
+        }
+        case EllipticalRRectEffect::kNinePatch_RRectType: {
+            const char *invRadiiLTRBSqdName;
+            fInvRadiiSqdUniform = builder->addUniform(GrGLShaderBuilder::kFragment_Visibility,
+                                                      kVec4f_GrSLType,
+                                                      "invRadiiLTRB",
+                                                      &invRadiiLTRBSqdName);
+            builder->fsCodeAppend("\t\tvec2 dxy = max(max(dxy0, dxy1), 0.0);\n");
+            // Z is the x/y offsets divided by squared radii. We only care about the (at most) one
+            // corner where both the x and y offsets are positive, hence the maxes. (The inverse
+            // squared radii will always be positive.)
+            builder->fsCodeAppendf("\t\tvec2 Z = max(max(dxy0 * %s.xy, dxy1 * %s.zw), 0.0);\n",
+                                   invRadiiLTRBSqdName, invRadiiLTRBSqdName);
+            break;
+       }
+    }
     // implicit is the evaluation of (x/a)^2 + (y/b)^2 - 1.
     builder->fsCodeAppend("\t\tfloat implicit = dot(Z, dxy) - 1.0;\n");
     // grad_dot is the squared length of the gradient of the implicit.
@@ -549,7 +598,8 @@
 GrGLEffect::EffectKey GLEllipticalRRectEffect::GenKey(const GrDrawEffect& drawEffect,
                                                       const GrGLCaps&) {
     const EllipticalRRectEffect& erre = drawEffect.castEffect<EllipticalRRectEffect>();
-    return erre.getEdgeType();
+    GR_STATIC_ASSERT(kLast_GrEffectEdgeType < (1 << 3));
+    return erre.getRRectType() | erre.getEdgeType() << 3;
 }
 
 void GLEllipticalRRectEffect::setData(const GrGLUniformManager& uman,
@@ -558,14 +608,31 @@
     const SkRRect& rrect = erre.getRRect();
     if (rrect != fPrevRRect) {
         SkRect rect = rrect.getBounds();
-        SkASSERT(rrect.isSimple());
-        const SkVector& radius = rrect.getSimpleRadii();
-        SkASSERT(radius.fX >= EllipticalRRectEffect::kRadiusMin);
-        SkASSERT(radius.fY >= EllipticalRRectEffect::kRadiusMin);
-        rect.inset(radius.fX, radius.fY);
+        const SkVector& r0 = rrect.radii(SkRRect::kUpperLeft_Corner);
+        SkASSERT(r0.fX >= EllipticalRRectEffect::kRadiusMin);
+        SkASSERT(r0.fY >= EllipticalRRectEffect::kRadiusMin);
+        switch (erre.getRRectType()) {
+            case EllipticalRRectEffect::kSimple_RRectType:
+                rect.inset(r0.fX, r0.fY);
+                uman.set2f(fInvRadiiSqdUniform, 1.f / (r0.fX * r0.fX),
+                                                1.f / (r0.fY * r0.fY));
+                break;
+            case EllipticalRRectEffect::kNinePatch_RRectType: {
+                const SkVector& r1 = rrect.radii(SkRRect::kLowerRight_Corner);
+                SkASSERT(r1.fX >= EllipticalRRectEffect::kRadiusMin);
+                SkASSERT(r1.fY >= EllipticalRRectEffect::kRadiusMin);
+                rect.fLeft += r0.fX;
+                rect.fTop += r0.fY;
+                rect.fRight -= r1.fX;
+                rect.fBottom -= r1.fY;
+                uman.set4f(fInvRadiiSqdUniform, 1.f / (r0.fX * r0.fX),
+                                                1.f / (r0.fY * r0.fY),
+                                                1.f / (r1.fX * r1.fX),
+                                                1.f / (r1.fY * r1.fY));
+                break;
+            }
+        }
         uman.set4f(fInnerRectUniform, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
-        uman.set2f(fInvRadiusXYSqdUniform, 1.f / (radius.fX * radius.fX),
-                                           1.f / (radius.fY * radius.fY));
         fPrevRRect = rrect;
     }
 }
@@ -588,7 +655,8 @@
                 rrect.getSimpleRadii().fY < EllipticalRRectEffect::kRadiusMin) {
                 return NULL;
             }
-            return EllipticalRRectEffect::Create(edgeType, rrect);
+            return EllipticalRRectEffect::Create(edgeType,
+                                                 EllipticalRRectEffect::kSimple_RRectType, rrect);
         }
     } else if (rrect.isComplex()) {
         // Check for the "tab" cases - two adjacent circular corners and two square corners.
@@ -601,17 +669,20 @@
                 continue;
             }
             if (r.fX != r.fY) {
-                return NULL;
+                cornerFlags = -1;
+                break;
             }
             if (!cornerFlags) {
                 radius = r.fX;
                 if (radius < CircularRRectEffect::kRadiusMin) {
-                    return NULL;
+                    cornerFlags = -1;
+                    break;
                 }
                 cornerFlags = 1 << c;
             } else {
                 if (r.fX != radius) {
-                    return NULL;
+                   cornerFlags = -1;
+                   break;
                 }
                 cornerFlags |= 1 << c;
             }
@@ -629,6 +700,18 @@
             case CircularRRectEffect::kAll_CornerFlags:
                 break;
             default:
+                if (rrect.isNinePatch()) {
+                    const SkVector& r0 = rrect.radii(SkRRect::kUpperLeft_Corner);
+                    const SkVector& r1 = rrect.radii(SkRRect::kLowerRight_Corner);
+                    if (r0.fX >= EllipticalRRectEffect::kRadiusMin &&
+                        r0.fY >= EllipticalRRectEffect::kRadiusMin &&
+                        r1.fX >= EllipticalRRectEffect::kRadiusMin &&
+                        r1.fY >= EllipticalRRectEffect::kRadiusMin) {
+                        return EllipticalRRectEffect::Create(edgeType,
+                                                        EllipticalRRectEffect::kNinePatch_RRectType,
+                                                        rrect);
+                    }
+                }
                 return NULL;
         }
     } else {