SkRRect.cpp revision eb221268ab1067af7c48e04a75147d4bcca87191
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: assert that all the x&y radii are already W/2 & H/2
40    }
41
42    SkDEBUGCODE(this->validate();)
43}
44
45void SkRRect::setRectRadii(const SkRect& rect, const SkVector radii[4]) {
46    if (rect.isEmpty()) {
47        this->setEmpty();
48        return;
49    }
50
51    fRect = rect;
52    memcpy(fRadii, radii, sizeof(fRadii));
53
54    bool allCornersSquare = true;
55
56    // Clamp negative radii to zero
57    for (int i = 0; i < 4; ++i) {
58        if (fRadii[i].fX <= 0 || fRadii[i].fY <= 0) {
59            // In this case we are being a little fast & loose. Since one of
60            // the radii is 0 the corner is square. However, the other radii
61            // could still be non-zero and play in the global scale factor
62            // computation.
63            fRadii[i].fX = 0;
64            fRadii[i].fY = 0;
65        } else {
66            allCornersSquare = false;
67        }
68    }
69
70    if (allCornersSquare) {
71        this->setRect(rect);
72        return;
73    }
74
75    // Proportionally scale down all radii to fit. Find the minimum ratio
76    // of a side and the radii on that side (for all four sides) and use
77    // that to scale down _all_ the radii. This algorithm is from the
78    // W3 spec (http://www.w3.org/TR/css3-background/) section 5.5 - Overlapping
79    // Curves:
80    // "Let f = min(Li/Si), where i is one of { top, right, bottom, left },
81    //   Si is the sum of the two corresponding radii of the corners on side i,
82    //   and Ltop = Lbottom = the width of the box,
83    //   and Lleft = Lright = the height of the box.
84    // If f < 1, then all corner radii are reduced by multiplying them by f."
85    SkScalar scale = SK_Scalar1;
86
87    if (fRadii[0].fX + fRadii[1].fX > rect.width()) {
88        scale = SkMinScalar(scale,
89                            SkScalarDiv(rect.width(), fRadii[0].fX + fRadii[1].fX));
90    }
91    if (fRadii[1].fY + fRadii[2].fY > rect.height()) {
92        scale = SkMinScalar(scale,
93                            SkScalarDiv(rect.height(), fRadii[1].fY + fRadii[2].fY));
94    }
95    if (fRadii[2].fX + fRadii[3].fX > rect.width()) {
96        scale = SkMinScalar(scale,
97                            SkScalarDiv(rect.width(), fRadii[2].fX + fRadii[3].fX));
98    }
99    if (fRadii[3].fY + fRadii[0].fY > rect.height()) {
100        scale = SkMinScalar(scale,
101                            SkScalarDiv(rect.height(), fRadii[3].fY + fRadii[0].fY));
102    }
103
104    if (scale < SK_Scalar1) {
105        for (int i = 0; i < 4; ++i) {
106            fRadii[i].fX = SkScalarMul(fRadii[i].fX, scale);
107            fRadii[i].fY = SkScalarMul(fRadii[i].fY, scale);
108        }
109    }
110
111    // At this point we're either oval, simple, or complex (not empty or rect)
112    // but we lazily resolve the type to avoid the work if the information
113    // isn't required.
114    fType = (SkRRect::Type) kUnknown_Type;
115
116    SkDEBUGCODE(this->validate();)
117}
118
119// This method determines if a point known to be inside the RRect's bounds is
120// inside all the corners.
121bool SkRRect::checkCornerContainment(SkScalar x, SkScalar y) const {
122    SkPoint canonicalPt; // (x,y) translated to one of the quadrants
123    int index;
124
125    if (kOval_Type == this->type()) {
126        canonicalPt.set(x - fRect.centerX(), y - fRect.centerY());
127        index = kUpperLeft_Corner;  // any corner will do in this case
128    } else {
129        if (x < fRect.fLeft + fRadii[kUpperLeft_Corner].fX &&
130            y < fRect.fTop + fRadii[kUpperLeft_Corner].fY) {
131            // UL corner
132            index = kUpperLeft_Corner;
133            canonicalPt.set(x - (fRect.fLeft + fRadii[kUpperLeft_Corner].fX),
134                            y - (fRect.fTop + fRadii[kUpperLeft_Corner].fY));
135            SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY < 0);
136        } else if (x < fRect.fLeft + fRadii[kLowerLeft_Corner].fX &&
137                   y > fRect.fBottom - fRadii[kLowerLeft_Corner].fY) {
138            // LL corner
139            index = kLowerLeft_Corner;
140            canonicalPt.set(x - (fRect.fLeft + fRadii[kLowerLeft_Corner].fX),
141                            y - (fRect.fBottom - fRadii[kLowerLeft_Corner].fY));
142            SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY > 0);
143        } else if (x > fRect.fRight - fRadii[kUpperRight_Corner].fX &&
144                   y < fRect.fTop + fRadii[kUpperRight_Corner].fY) {
145            // UR corner
146            index = kUpperRight_Corner;
147            canonicalPt.set(x - (fRect.fRight - fRadii[kUpperRight_Corner].fX),
148                            y - (fRect.fTop + fRadii[kUpperRight_Corner].fY));
149            SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY < 0);
150        } else if (x > fRect.fRight - fRadii[kLowerRight_Corner].fX &&
151                   y > fRect.fBottom - fRadii[kLowerRight_Corner].fY) {
152            // LR corner
153            index = kLowerRight_Corner;
154            canonicalPt.set(x - (fRect.fRight - fRadii[kLowerRight_Corner].fX),
155                            y - (fRect.fBottom - fRadii[kLowerRight_Corner].fY));
156            SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY > 0);
157        } else {
158            // not in any of the corners
159            return true;
160        }
161    }
162
163    // A point is in an ellipse (in standard position) if:
164    //      x^2     y^2
165    //     ----- + ----- <= 1
166    //      a^2     b^2
167    // or :
168    //     b^2*x^2 + a^2*y^2 <= (ab)^2
169    SkScalar dist =  SkScalarMul(SkScalarSquare(canonicalPt.fX), SkScalarSquare(fRadii[index].fY)) +
170                     SkScalarMul(SkScalarSquare(canonicalPt.fY), SkScalarSquare(fRadii[index].fX));
171    return dist <= SkScalarSquare(SkScalarMul(fRadii[index].fX, fRadii[index].fY));
172}
173
174bool SkRRect::contains(const SkRect& rect) const {
175    if (!this->getBounds().contains(rect)) {
176        // If 'rect' isn't contained by the RR's bounds then the
177        // RR definitely doesn't contain it
178        return false;
179    }
180
181    if (this->isRect()) {
182        // the prior test was sufficient
183        return true;
184    }
185
186    // At this point we know all four corners of 'rect' are inside the
187    // bounds of of this RR. Check to make sure all the corners are inside
188    // all the curves
189    return this->checkCornerContainment(rect.fLeft, rect.fTop) &&
190           this->checkCornerContainment(rect.fRight, rect.fTop) &&
191           this->checkCornerContainment(rect.fRight, rect.fBottom) &&
192           this->checkCornerContainment(rect.fLeft, rect.fBottom);
193}
194
195// There is a simplified version of this method in setRectXY
196void SkRRect::computeType() const {
197    SkDEBUGCODE(this->validate();)
198
199    if (fRect.isEmpty()) {
200        fType = kEmpty_Type;
201        return;
202    }
203
204    bool allRadiiEqual = true; // are all x radii equal and all y radii?
205    bool allCornersSquare = 0 == fRadii[0].fX || 0 == fRadii[0].fY;
206
207    for (int i = 1; i < 4; ++i) {
208        if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
209            // if either radius is zero the corner is square so both have to
210            // be non-zero to have a rounded corner
211            allCornersSquare = false;
212        }
213        if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
214            allRadiiEqual = false;
215        }
216    }
217
218    if (allCornersSquare) {
219        fType = kRect_Type;
220        return;
221    }
222
223    if (allRadiiEqual) {
224        if (fRadii[0].fX >= SkScalarHalf(fRect.width()) &&
225            fRadii[0].fY >= SkScalarHalf(fRect.height())) {
226            fType = kOval_Type;
227        } else {
228            fType = kSimple_Type;
229        }
230        return;
231    }
232
233    fType = kComplex_Type;
234}
235
236///////////////////////////////////////////////////////////////////////////////
237
238void SkRRect::inset(SkScalar dx, SkScalar dy, SkRRect* dst) const {
239    SkRect r = fRect;
240
241    r.inset(dx, dy);
242    if (r.isEmpty()) {
243        dst->setEmpty();
244        return;
245    }
246
247    SkVector radii[4];
248    memcpy(radii, fRadii, sizeof(radii));
249    for (int i = 0; i < 4; ++i) {
250        if (radii[i].fX) {
251            radii[i].fX -= dx;
252        }
253        if (radii[i].fY) {
254            radii[i].fY -= dy;
255        }
256    }
257    dst->setRectRadii(r, radii);
258}
259
260///////////////////////////////////////////////////////////////////////////////
261
262uint32_t SkRRect::writeToMemory(void* buffer) const {
263    SkASSERT(kSizeInMemory == sizeof(SkRect) + sizeof(fRadii));
264
265    memcpy(buffer, &fRect, sizeof(SkRect));
266    memcpy((char*)buffer + sizeof(SkRect), fRadii, sizeof(fRadii));
267    return kSizeInMemory;
268}
269
270uint32_t SkRRect::readFromMemory(const void* buffer) {
271    SkScalar storage[12];
272    SkASSERT(sizeof(storage) == kSizeInMemory);
273
274    // we make a local copy, to ensure alignment before we cast
275    memcpy(storage, buffer, kSizeInMemory);
276
277    this->setRectRadii(*(const SkRect*)&storage[0],
278                       (const SkVector*)&storage[4]);
279    return kSizeInMemory;
280}
281
282///////////////////////////////////////////////////////////////////////////////
283
284#ifdef SK_DEBUG
285void SkRRect::validate() const {
286    bool allRadiiZero = (0 == fRadii[0].fX && 0 == fRadii[0].fY);
287    bool allCornersSquare = (0 == fRadii[0].fX || 0 == fRadii[0].fY);
288    bool allRadiiSame = true;
289
290    for (int i = 1; i < 4; ++i) {
291        if (0 != fRadii[i].fX || 0 != fRadii[i].fY) {
292            allRadiiZero = false;
293        }
294
295        if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
296            allRadiiSame = false;
297        }
298
299        if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
300            allCornersSquare = false;
301        }
302    }
303
304    switch (fType) {
305        case kEmpty_Type:
306            SkASSERT(fRect.isEmpty());
307            SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare);
308
309            SkASSERT(0 == fRect.fLeft && 0 == fRect.fTop &&
310                     0 == fRect.fRight && 0 == fRect.fBottom);
311            break;
312        case kRect_Type:
313            SkASSERT(!fRect.isEmpty());
314            SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare);
315            break;
316        case kOval_Type:
317            SkASSERT(!fRect.isEmpty());
318            SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare);
319
320            for (int i = 0; i < 4; ++i) {
321                SkASSERT(SkScalarNearlyEqual(fRadii[i].fX, SkScalarHalf(fRect.width())));
322                SkASSERT(SkScalarNearlyEqual(fRadii[i].fY, SkScalarHalf(fRect.height())));
323            }
324            break;
325        case kSimple_Type:
326            SkASSERT(!fRect.isEmpty());
327            SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare);
328            break;
329        case kComplex_Type:
330            SkASSERT(!fRect.isEmpty());
331            SkASSERT(!allRadiiZero && !allRadiiSame && !allCornersSquare);
332            break;
333        case kUnknown_Type:
334            // no limits on this
335            break;
336    }
337}
338#endif // SK_DEBUG
339
340///////////////////////////////////////////////////////////////////////////////
341