SkRRect.cpp revision 0da23a5184cf8ee658c4f1ac45b798ddf7e73002
1/*
2 * Copyright 2012 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "SkRRect.h"
9
10///////////////////////////////////////////////////////////////////////////////
11
12void SkRRect::setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) {
13    if (rect.isEmpty()) {
14        this->setEmpty();
15        return;
16    }
17
18    if (xRad <= 0 || yRad <= 0) {
19        // all corners are square in this case
20        this->setRect(rect);
21        return;
22    }
23
24    if (rect.width() < xRad+xRad || rect.height() < yRad+yRad) {
25        SkScalar scale = SkMinScalar(SkScalarDiv(rect.width(), xRad + xRad),
26                                     SkScalarDiv(rect.height(), yRad + yRad));
27        SkASSERT(scale < SK_Scalar1);
28        xRad = SkScalarMul(xRad, scale);
29        yRad = SkScalarMul(yRad, scale);
30    }
31
32    fRect = rect;
33    for (int i = 0; i < 4; ++i) {
34        fRadii[i].set(xRad, yRad);
35    }
36    fType = kSimple_Type;
37    if (xRad >= SkScalarHalf(fRect.width()) && yRad >= SkScalarHalf(fRect.height())) {
38        fType = kOval_Type;
39        // TODO: try asserting they are already W/2 & H/2 already
40        xRad = SkScalarHalf(fRect.width());
41        yRad = SkScalarHalf(fRect.height());
42    }
43
44    SkDEBUGCODE(this->validate();)
45}
46
47void SkRRect::setRectRadii(const SkRect& rect, const SkVector radii[4]) {
48    if (rect.isEmpty()) {
49        this->setEmpty();
50        return;
51    }
52
53    fRect = rect;
54    memcpy(fRadii, radii, sizeof(fRadii));
55
56    bool allCornersSquare = true;
57
58    // Clamp negative radii to zero
59    for (int i = 0; i < 4; ++i) {
60        if (fRadii[i].fX <= 0 || fRadii[i].fY <= 0) {
61            // In this case we are being a little fast & loose. Since one of
62            // the radii is 0 the corner is square. However, the other radii
63            // could still be non-zero and play in the global scale factor
64            // computation.
65            fRadii[i].fX = 0;
66            fRadii[i].fY = 0;
67        } else {
68            allCornersSquare = false;
69        }
70    }
71
72    if (allCornersSquare) {
73        this->setRect(rect);
74        return;
75    }
76
77    // Proportionally scale down all radii to fit. Find the minimum ratio
78    // of a side and the radii on that side (for all four sides) and use
79    // that to scale down _all_ the radii. This algorithm is from the
80    // W3 spec (http://www.w3.org/TR/css3-background/) section 5.5 - Overlapping
81    // Curves:
82    // "Let f = min(Li/Si), where i is one of { top, right, bottom, left },
83    //   Si is the sum of the two corresponding radii of the corners on side i,
84    //   and Ltop = Lbottom = the width of the box,
85    //   and Lleft = Lright = the height of the box.
86    // If f < 1, then all corner radii are reduced by multiplying them by f."
87    SkScalar scale = SK_Scalar1;
88
89    if (fRadii[0].fX + fRadii[1].fX > rect.width()) {
90        scale = SkMinScalar(scale,
91                            SkScalarDiv(rect.width(), fRadii[0].fX + fRadii[1].fX));
92    }
93    if (fRadii[1].fY + fRadii[2].fY > rect.height()) {
94        scale = SkMinScalar(scale,
95                            SkScalarDiv(rect.height(), fRadii[1].fY + fRadii[2].fY));
96    }
97    if (fRadii[2].fX + fRadii[3].fX > rect.width()) {
98        scale = SkMinScalar(scale,
99                            SkScalarDiv(rect.width(), fRadii[2].fX + fRadii[3].fX));
100    }
101    if (fRadii[3].fY + fRadii[0].fY > rect.height()) {
102        scale = SkMinScalar(scale,
103                            SkScalarDiv(rect.height(), fRadii[3].fY + fRadii[0].fY));
104    }
105
106    if (scale < SK_Scalar1) {
107        for (int i = 0; i < 4; ++i) {
108            fRadii[i].fX = SkScalarMul(fRadii[i].fX, scale);
109            fRadii[i].fY = SkScalarMul(fRadii[i].fY, scale);
110        }
111    }
112
113    // At this point we're either oval, simple, or complex (not empty or rect)
114    // but we lazily resolve the type to avoid the work if the information
115    // isn't required.
116    fType = (SkRRect::Type) kUnknown_Type;
117
118    SkDEBUGCODE(this->validate();)
119}
120
121bool SkRRect::contains(SkScalar x, SkScalar y) const {
122    SkDEBUGCODE(this->validate();)
123
124    if (kEmpty_Type == this->type()) {
125        return false;
126    }
127
128    if (!fRect.contains(x, y)) {
129        return false;
130    }
131
132    if (kRect_Type == this->type()) {
133        // the 'fRect' test above was sufficient
134        return true;
135    }
136
137    // We know the point is inside the RR's bounds. The only way it can
138    // be out is if it outside one of the corners
139    SkPoint canonicalPt; // (x,y) translated to one of the quadrants
140    int index;
141
142    if (kOval_Type == this->type()) {
143        canonicalPt.set(x - fRect.centerX(), y - fRect.centerY());
144        index = kUpperLeft_Corner;  // any corner will do in this case
145    } else {
146        if (x < fRect.fLeft + fRadii[kUpperLeft_Corner].fX &&
147            y < fRect.fTop + fRadii[kUpperLeft_Corner].fY) {
148            // UL corner
149            index = kUpperLeft_Corner;
150            canonicalPt.set(x - (fRect.fLeft + fRadii[kUpperLeft_Corner].fX),
151                            y - (fRect.fTop + fRadii[kUpperLeft_Corner].fY));
152            SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY < 0);
153        } else if (x < fRect.fLeft + fRadii[kLowerLeft_Corner].fX &&
154                   y > fRect.fBottom - fRadii[kLowerLeft_Corner].fY) {
155            // LL corner
156            index = kLowerLeft_Corner;
157            canonicalPt.set(x - (fRect.fLeft + fRadii[kLowerLeft_Corner].fX),
158                            y - (fRect.fBottom - fRadii[kLowerLeft_Corner].fY));
159            SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY > 0);
160        } else if (x > fRect.fRight - fRadii[kUpperRight_Corner].fX &&
161                   y < fRect.fTop + fRadii[kUpperRight_Corner].fY) {
162            // UR corner
163            index = kUpperRight_Corner;
164            canonicalPt.set(x - (fRect.fRight - fRadii[kUpperRight_Corner].fX),
165                            y - (fRect.fTop + fRadii[kUpperRight_Corner].fY));
166            SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY < 0);
167        } else if (x > fRect.fRight - fRadii[kLowerRight_Corner].fX &&
168                   y > fRect.fBottom - fRadii[kLowerRight_Corner].fY) {
169            // LR corner
170            index = kLowerRight_Corner;
171            canonicalPt.set(x - (fRect.fRight - fRadii[kLowerRight_Corner].fX),
172                            y - (fRect.fBottom - fRadii[kLowerRight_Corner].fY));
173            SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY > 0);
174        } else {
175            // not in any of the corners
176            return true;
177        }
178    }
179
180    // A point is in an ellipse (in standard position) if:
181    //      x^2     y^2
182    //     ----- + ----- <= 1
183    //      a^2     b^2
184    SkScalar dist =  SkScalarDiv(SkScalarSquare(canonicalPt.fX), SkScalarSquare(fRadii[index].fX)) +
185                     SkScalarDiv(SkScalarSquare(canonicalPt.fY), SkScalarSquare(fRadii[index].fY));
186    return dist <= SK_Scalar1;
187}
188
189// There is a simplified version of this method in setRectXY
190void SkRRect::computeType() const {
191    SkDEBUGCODE(this->validate();)
192
193    if (fRect.isEmpty()) {
194        fType = kEmpty_Type;
195        return;
196    }
197
198    bool allRadiiEqual = true; // are all x radii equal and all y radii?
199    bool allCornersSquare = 0 == fRadii[0].fX || 0 == fRadii[0].fY;
200
201    for (int i = 1; i < 4; ++i) {
202        if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
203            // if either radius is zero the corner is square so both have to
204            // be non-zero to have a rounded corner
205            allCornersSquare = false;
206        }
207        if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
208            allRadiiEqual = false;
209        }
210    }
211
212    if (allCornersSquare) {
213        fType = kRect_Type;
214        return;
215    }
216
217    if (allRadiiEqual) {
218        if (fRadii[0].fX >= SkScalarHalf(fRect.width()) &&
219            fRadii[0].fY >= SkScalarHalf(fRect.height())) {
220            fType = kOval_Type;
221        } else {
222            fType = kSimple_Type;
223        }
224        return;
225    }
226
227    fType = kComplex_Type;
228}
229
230#ifdef SK_DEBUG
231void SkRRect::validate() const {
232    bool allRadiiZero = (0 == fRadii[0].fX && 0 == fRadii[0].fY);
233    bool allCornersSquare = (0 == fRadii[0].fX || 0 == fRadii[0].fY);
234    bool allRadiiSame = true;
235
236    for (int i = 1; i < 4; ++i) {
237        if (0 != fRadii[i].fX || 0 != fRadii[i].fY) {
238            allRadiiZero = false;
239        }
240
241        if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
242            allRadiiSame = false;
243        }
244
245        if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
246            allCornersSquare = false;
247        }
248    }
249
250    switch (fType) {
251        case kEmpty_Type:
252            SkASSERT(fRect.isEmpty());
253            SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare);
254
255            SkASSERT(0 == fRect.fLeft && 0 == fRect.fTop &&
256                     0 == fRect.fRight && 0 == fRect.fBottom);
257            break;
258        case kRect_Type:
259            SkASSERT(!fRect.isEmpty());
260            SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare);
261            break;
262        case kOval_Type:
263            SkASSERT(!fRect.isEmpty());
264            SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare);
265
266            for (int i = 0; i < 4; ++i) {
267                SkASSERT(SkScalarNearlyEqual(fRadii[i].fX, SkScalarHalf(fRect.width())));
268                SkASSERT(SkScalarNearlyEqual(fRadii[i].fY, SkScalarHalf(fRect.height())));
269            }
270            break;
271        case kSimple_Type:
272            SkASSERT(!fRect.isEmpty());
273            SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare);
274            break;
275        case kComplex_Type:
276            SkASSERT(!fRect.isEmpty());
277            SkASSERT(!allRadiiZero && !allRadiiSame && !allCornersSquare);
278            break;
279        case kUnknown_Type:
280            // no limits on this
281            break;
282    }
283}
284#endif // SK_DEBUG
285
286///////////////////////////////////////////////////////////////////////////////
287