SkRRect.cpp revision 454fa71cc31bf45c14e4c0b25502d5332d76c51c
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#include "SkMatrix.h"
10
11///////////////////////////////////////////////////////////////////////////////
12
13void SkRRect::setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) {
14    if (rect.isEmpty() || !rect.isFinite()) {
15        this->setEmpty();
16        return;
17    }
18
19    if (!SkScalarsAreFinite(xRad, yRad)) {
20        xRad = yRad = 0;    // devolve into a simple rect
21    }
22    if (xRad <= 0 || yRad <= 0) {
23        // all corners are square in this case
24        this->setRect(rect);
25        return;
26    }
27
28    if (rect.width() < xRad+xRad || rect.height() < yRad+yRad) {
29        SkScalar scale = SkMinScalar(SkScalarDiv(rect.width(), xRad + xRad),
30                                     SkScalarDiv(rect.height(), yRad + yRad));
31        SkASSERT(scale < SK_Scalar1);
32        xRad = SkScalarMul(xRad, scale);
33        yRad = SkScalarMul(yRad, scale);
34    }
35
36    fRect = rect;
37    for (int i = 0; i < 4; ++i) {
38        fRadii[i].set(xRad, yRad);
39    }
40    fType = kSimple_Type;
41    if (xRad >= SkScalarHalf(fRect.width()) && yRad >= SkScalarHalf(fRect.height())) {
42        fType = kOval_Type;
43        // TODO: assert that all the x&y radii are already W/2 & H/2
44    }
45
46    SkDEBUGCODE(this->validate();)
47}
48
49void SkRRect::setNinePatch(const SkRect& rect, SkScalar leftRad, SkScalar topRad,
50                           SkScalar rightRad, SkScalar bottomRad) {
51    if (rect.isEmpty() || !rect.isFinite()) {
52        this->setEmpty();
53        return;
54    }
55
56    const SkScalar array[4] = { leftRad, topRad, rightRad, bottomRad };
57    if (!SkScalarsAreFinite(array, 4)) {
58        this->setRect(rect);    // devolve into a simple rect
59        return;
60    }
61
62    leftRad = SkMaxScalar(leftRad, 0);
63    topRad = SkMaxScalar(topRad, 0);
64    rightRad = SkMaxScalar(rightRad, 0);
65    bottomRad = SkMaxScalar(bottomRad, 0);
66
67    SkScalar scale = SK_Scalar1;
68    if (leftRad + rightRad > rect.width()) {
69        scale = SkScalarDiv(rect.width(), leftRad + rightRad);
70    }
71    if (topRad + bottomRad > rect.height()) {
72        scale = SkMinScalar(scale, SkScalarDiv(rect.width(), leftRad + rightRad));
73    }
74
75    if (scale < SK_Scalar1) {
76        leftRad = SkScalarMul(leftRad, scale);
77        topRad = SkScalarMul(topRad, scale);
78        rightRad = SkScalarMul(rightRad, scale);
79        bottomRad = SkScalarMul(bottomRad, scale);
80    }
81
82    if (leftRad == rightRad && topRad == bottomRad) {
83        if (leftRad >= SkScalarHalf(rect.width()) && topRad >= SkScalarHalf(rect.height())) {
84            fType = kOval_Type;
85        } else if (0 == leftRad || 0 == topRad) {
86            // If the left and (by equality check above) right radii are zero then it is a rect.
87            // Same goes for top/bottom.
88            fType = kRect_Type;
89            leftRad = 0;
90            topRad = 0;
91            rightRad = 0;
92            bottomRad = 0;
93        } else {
94            fType = kSimple_Type;
95        }
96    } else {
97        fType = kNinePatch_Type;
98    }
99
100    fRect = rect;
101    fRadii[kUpperLeft_Corner].set(leftRad, topRad);
102    fRadii[kUpperRight_Corner].set(rightRad, topRad);
103    fRadii[kLowerRight_Corner].set(rightRad, bottomRad);
104    fRadii[kLowerLeft_Corner].set(leftRad, bottomRad);
105
106    SkDEBUGCODE(this->validate();)
107}
108
109/*
110 *  TODO: clean this guy up and possibly add to SkScalar.h
111 */
112static inline SkScalar SkScalarDecULP(SkScalar value) {
113#if SK_SCALAR_IS_FLOAT
114        return SkBits2Float(SkFloat2Bits(value) - 1);
115#else
116    #error "need impl for doubles"
117#endif
118}
119
120static SkScalar clamp_radius_add(SkScalar rad, SkScalar min, SkScalar max) {
121    SkASSERT(rad <= max - min);
122    if (min + rad > max) {
123        rad = SkScalarDecULP(rad);
124    }
125    return rad;
126}
127
128static SkScalar clamp_radius_sub(SkScalar rad, SkScalar min, SkScalar max) {
129    SkASSERT(rad <= max - min);
130    if (max - rad < min) {
131        rad = SkScalarDecULP(rad);
132    }
133    return rad;
134}
135
136void SkRRect::setRectRadii(const SkRect& rect, const SkVector radii[4]) {
137    if (rect.isEmpty() || !rect.isFinite()) {
138        this->setEmpty();
139        return;
140    }
141
142    if (!SkScalarsAreFinite(&radii[0].fX, 8)) {
143        this->setRect(rect);    // devolve into a simple rect
144        return;
145    }
146
147    fRect = rect;
148    memcpy(fRadii, radii, sizeof(fRadii));
149
150    bool allCornersSquare = true;
151
152    // Clamp negative radii to zero
153    for (int i = 0; i < 4; ++i) {
154        if (fRadii[i].fX <= 0 || fRadii[i].fY <= 0) {
155            // In this case we are being a little fast & loose. Since one of
156            // the radii is 0 the corner is square. However, the other radii
157            // could still be non-zero and play in the global scale factor
158            // computation.
159            fRadii[i].fX = 0;
160            fRadii[i].fY = 0;
161        } else {
162            allCornersSquare = false;
163        }
164    }
165
166    if (allCornersSquare) {
167        this->setRect(rect);
168        return;
169    }
170
171    // Proportionally scale down all radii to fit. Find the minimum ratio
172    // of a side and the radii on that side (for all four sides) and use
173    // that to scale down _all_ the radii. This algorithm is from the
174    // W3 spec (http://www.w3.org/TR/css3-background/) section 5.5 - Overlapping
175    // Curves:
176    // "Let f = min(Li/Si), where i is one of { top, right, bottom, left },
177    //   Si is the sum of the two corresponding radii of the corners on side i,
178    //   and Ltop = Lbottom = the width of the box,
179    //   and Lleft = Lright = the height of the box.
180    // If f < 1, then all corner radii are reduced by multiplying them by f."
181    SkScalar scale = SK_Scalar1;
182
183    if (fRadii[0].fX + fRadii[1].fX > rect.width()) {
184        scale = SkMinScalar(scale,
185                            SkScalarDiv(rect.width(), fRadii[0].fX + fRadii[1].fX));
186    }
187    if (fRadii[1].fY + fRadii[2].fY > rect.height()) {
188        scale = SkMinScalar(scale,
189                            SkScalarDiv(rect.height(), fRadii[1].fY + fRadii[2].fY));
190    }
191    if (fRadii[2].fX + fRadii[3].fX > rect.width()) {
192        scale = SkMinScalar(scale,
193                            SkScalarDiv(rect.width(), fRadii[2].fX + fRadii[3].fX));
194    }
195    if (fRadii[3].fY + fRadii[0].fY > rect.height()) {
196        scale = SkMinScalar(scale,
197                            SkScalarDiv(rect.height(), fRadii[3].fY + fRadii[0].fY));
198    }
199
200    if (scale < SK_Scalar1) {
201        for (int i = 0; i < 4; ++i) {
202            fRadii[i].fX *= scale;
203            fRadii[i].fY *= scale;
204        }
205    }
206
207    // skbug.com/3239 -- its possible that we can hit the following inconsistency:
208    //     rad == bounds.bottom - bounds.top
209    //     bounds.bottom - radius < bounds.top
210    //     YIKES
211    // We need to detect and "fix" this now, otherwise we can have the following wackiness:
212    //     path.addRRect(rrect);
213    //     rrect.rect() != path.getBounds()
214    fRadii[0].fX = clamp_radius_add(fRadii[0].fX, rect.fLeft, rect.fRight);
215    fRadii[0].fY = clamp_radius_add(fRadii[0].fY, rect.fTop, rect.fBottom);
216    fRadii[1].fX = clamp_radius_sub(fRadii[1].fX, rect.fLeft, rect.fRight);
217    fRadii[1].fY = clamp_radius_add(fRadii[1].fY, rect.fTop, rect.fBottom);
218    fRadii[2].fX = clamp_radius_sub(fRadii[2].fX, rect.fLeft, rect.fRight);
219    fRadii[2].fY = clamp_radius_sub(fRadii[2].fY, rect.fTop, rect.fBottom);
220    fRadii[3].fX = clamp_radius_add(fRadii[3].fX, rect.fLeft, rect.fRight);
221    fRadii[3].fY = clamp_radius_sub(fRadii[3].fY, rect.fTop, rect.fBottom);
222
223    // At this point we're either oval, simple, or complex (not empty or rect).
224    this->computeType();
225
226    SkDEBUGCODE(this->validate();)
227}
228
229// This method determines if a point known to be inside the RRect's bounds is
230// inside all the corners.
231bool SkRRect::checkCornerContainment(SkScalar x, SkScalar y) const {
232    SkPoint canonicalPt; // (x,y) translated to one of the quadrants
233    int index;
234
235    if (kOval_Type == this->type()) {
236        canonicalPt.set(x - fRect.centerX(), y - fRect.centerY());
237        index = kUpperLeft_Corner;  // any corner will do in this case
238    } else {
239        if (x < fRect.fLeft + fRadii[kUpperLeft_Corner].fX &&
240            y < fRect.fTop + fRadii[kUpperLeft_Corner].fY) {
241            // UL corner
242            index = kUpperLeft_Corner;
243            canonicalPt.set(x - (fRect.fLeft + fRadii[kUpperLeft_Corner].fX),
244                            y - (fRect.fTop + fRadii[kUpperLeft_Corner].fY));
245            SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY < 0);
246        } else if (x < fRect.fLeft + fRadii[kLowerLeft_Corner].fX &&
247                   y > fRect.fBottom - fRadii[kLowerLeft_Corner].fY) {
248            // LL corner
249            index = kLowerLeft_Corner;
250            canonicalPt.set(x - (fRect.fLeft + fRadii[kLowerLeft_Corner].fX),
251                            y - (fRect.fBottom - fRadii[kLowerLeft_Corner].fY));
252            SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY > 0);
253        } else if (x > fRect.fRight - fRadii[kUpperRight_Corner].fX &&
254                   y < fRect.fTop + fRadii[kUpperRight_Corner].fY) {
255            // UR corner
256            index = kUpperRight_Corner;
257            canonicalPt.set(x - (fRect.fRight - fRadii[kUpperRight_Corner].fX),
258                            y - (fRect.fTop + fRadii[kUpperRight_Corner].fY));
259            SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY < 0);
260        } else if (x > fRect.fRight - fRadii[kLowerRight_Corner].fX &&
261                   y > fRect.fBottom - fRadii[kLowerRight_Corner].fY) {
262            // LR corner
263            index = kLowerRight_Corner;
264            canonicalPt.set(x - (fRect.fRight - fRadii[kLowerRight_Corner].fX),
265                            y - (fRect.fBottom - fRadii[kLowerRight_Corner].fY));
266            SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY > 0);
267        } else {
268            // not in any of the corners
269            return true;
270        }
271    }
272
273    // A point is in an ellipse (in standard position) if:
274    //      x^2     y^2
275    //     ----- + ----- <= 1
276    //      a^2     b^2
277    // or :
278    //     b^2*x^2 + a^2*y^2 <= (ab)^2
279    SkScalar dist =  SkScalarMul(SkScalarSquare(canonicalPt.fX), SkScalarSquare(fRadii[index].fY)) +
280                     SkScalarMul(SkScalarSquare(canonicalPt.fY), SkScalarSquare(fRadii[index].fX));
281    return dist <= SkScalarSquare(SkScalarMul(fRadii[index].fX, fRadii[index].fY));
282}
283
284bool SkRRect::allCornersCircular() const {
285    return fRadii[0].fX == fRadii[0].fY &&
286        fRadii[1].fX == fRadii[1].fY &&
287        fRadii[2].fX == fRadii[2].fY &&
288        fRadii[3].fX == fRadii[3].fY;
289}
290
291bool SkRRect::contains(const SkRect& rect) const {
292    if (!this->getBounds().contains(rect)) {
293        // If 'rect' isn't contained by the RR's bounds then the
294        // RR definitely doesn't contain it
295        return false;
296    }
297
298    if (this->isRect()) {
299        // the prior test was sufficient
300        return true;
301    }
302
303    // At this point we know all four corners of 'rect' are inside the
304    // bounds of of this RR. Check to make sure all the corners are inside
305    // all the curves
306    return this->checkCornerContainment(rect.fLeft, rect.fTop) &&
307           this->checkCornerContainment(rect.fRight, rect.fTop) &&
308           this->checkCornerContainment(rect.fRight, rect.fBottom) &&
309           this->checkCornerContainment(rect.fLeft, rect.fBottom);
310}
311
312static bool radii_are_nine_patch(const SkVector radii[4]) {
313    return radii[SkRRect::kUpperLeft_Corner].fX == radii[SkRRect::kLowerLeft_Corner].fX &&
314           radii[SkRRect::kUpperLeft_Corner].fY == radii[SkRRect::kUpperRight_Corner].fY &&
315           radii[SkRRect::kUpperRight_Corner].fX == radii[SkRRect::kLowerRight_Corner].fX &&
316           radii[SkRRect::kLowerLeft_Corner].fY == radii[SkRRect::kLowerRight_Corner].fY;
317}
318
319// There is a simplified version of this method in setRectXY
320void SkRRect::computeType() {
321    struct Validator {
322        Validator(const SkRRect* r) : fR(r) {}
323        ~Validator() { SkDEBUGCODE(fR->validate();) }
324        const SkRRect* fR;
325    } autoValidate(this);
326
327    if (fRect.isEmpty()) {
328        fType = kEmpty_Type;
329        return;
330    }
331
332    bool allRadiiEqual = true; // are all x radii equal and all y radii?
333    bool allCornersSquare = 0 == fRadii[0].fX || 0 == fRadii[0].fY;
334
335    for (int i = 1; i < 4; ++i) {
336        if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
337            // if either radius is zero the corner is square so both have to
338            // be non-zero to have a rounded corner
339            allCornersSquare = false;
340        }
341        if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
342            allRadiiEqual = false;
343        }
344    }
345
346    if (allCornersSquare) {
347        fType = kRect_Type;
348        return;
349    }
350
351    if (allRadiiEqual) {
352        if (fRadii[0].fX >= SkScalarHalf(fRect.width()) &&
353            fRadii[0].fY >= SkScalarHalf(fRect.height())) {
354            fType = kOval_Type;
355        } else {
356            fType = kSimple_Type;
357        }
358        return;
359    }
360
361    if (radii_are_nine_patch(fRadii)) {
362        fType = kNinePatch_Type;
363    } else {
364        fType = kComplex_Type;
365    }
366}
367
368static bool matrix_only_scale_and_translate(const SkMatrix& matrix) {
369    const SkMatrix::TypeMask m = (SkMatrix::TypeMask) (SkMatrix::kAffine_Mask
370                                    | SkMatrix::kPerspective_Mask);
371    return (matrix.getType() & m) == 0;
372}
373
374bool SkRRect::transform(const SkMatrix& matrix, SkRRect* dst) const {
375    if (NULL == dst) {
376        return false;
377    }
378
379    // Assert that the caller is not trying to do this in place, which
380    // would violate const-ness. Do not return false though, so that
381    // if they know what they're doing and want to violate it they can.
382    SkASSERT(dst != this);
383
384    if (matrix.isIdentity()) {
385        *dst = *this;
386        return true;
387    }
388
389    // If transform supported 90 degree rotations (which it could), we could
390    // use SkMatrix::rectStaysRect() to check for a valid transformation.
391    if (!matrix_only_scale_and_translate(matrix)) {
392        return false;
393    }
394
395    SkRect newRect;
396    if (!matrix.mapRect(&newRect, fRect)) {
397        return false;
398    }
399
400    // At this point, this is guaranteed to succeed, so we can modify dst.
401    dst->fRect = newRect;
402
403    // Since the only transforms that were allowed are scale and translate, the type
404    // remains unchanged.
405    dst->fType = fType;
406
407    if (kOval_Type == fType) {
408        for (int i = 0; i < 4; ++i) {
409            dst->fRadii[i].fX = SkScalarHalf(newRect.width());
410            dst->fRadii[i].fY = SkScalarHalf(newRect.height());
411        }
412        SkDEBUGCODE(dst->validate();)
413        return true;
414    }
415
416    // Now scale each corner
417    SkScalar xScale = matrix.getScaleX();
418    const bool flipX = xScale < 0;
419    if (flipX) {
420        xScale = -xScale;
421    }
422    SkScalar yScale = matrix.getScaleY();
423    const bool flipY = yScale < 0;
424    if (flipY) {
425        yScale = -yScale;
426    }
427
428    // Scale the radii without respecting the flip.
429    for (int i = 0; i < 4; ++i) {
430        dst->fRadii[i].fX = SkScalarMul(fRadii[i].fX, xScale);
431        dst->fRadii[i].fY = SkScalarMul(fRadii[i].fY, yScale);
432    }
433
434    // Now swap as necessary.
435    if (flipX) {
436        if (flipY) {
437            // Swap with opposite corners
438            SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerRight_Corner]);
439            SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerLeft_Corner]);
440        } else {
441            // Only swap in x
442            SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kUpperLeft_Corner]);
443            SkTSwap(dst->fRadii[kLowerRight_Corner], dst->fRadii[kLowerLeft_Corner]);
444        }
445    } else if (flipY) {
446        // Only swap in y
447        SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerLeft_Corner]);
448        SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerRight_Corner]);
449    }
450
451    SkDEBUGCODE(dst->validate();)
452
453    return true;
454}
455
456///////////////////////////////////////////////////////////////////////////////
457
458void SkRRect::inset(SkScalar dx, SkScalar dy, SkRRect* dst) const {
459    SkRect r = fRect;
460
461    r.inset(dx, dy);
462    if (r.isEmpty()) {
463        dst->setEmpty();
464        return;
465    }
466
467    SkVector radii[4];
468    memcpy(radii, fRadii, sizeof(radii));
469    for (int i = 0; i < 4; ++i) {
470        if (radii[i].fX) {
471            radii[i].fX -= dx;
472        }
473        if (radii[i].fY) {
474            radii[i].fY -= dy;
475        }
476    }
477    dst->setRectRadii(r, radii);
478}
479
480///////////////////////////////////////////////////////////////////////////////
481
482size_t SkRRect::writeToMemory(void* buffer) const {
483    SkASSERT(kSizeInMemory == sizeof(SkRect) + sizeof(fRadii));
484
485    memcpy(buffer, &fRect, sizeof(SkRect));
486    memcpy((char*)buffer + sizeof(SkRect), fRadii, sizeof(fRadii));
487    return kSizeInMemory;
488}
489
490size_t SkRRect::readFromMemory(const void* buffer, size_t length) {
491    if (length < kSizeInMemory) {
492        return 0;
493    }
494
495    SkScalar storage[12];
496    SkASSERT(sizeof(storage) == kSizeInMemory);
497
498    // we make a local copy, to ensure alignment before we cast
499    memcpy(storage, buffer, kSizeInMemory);
500
501    this->setRectRadii(*(const SkRect*)&storage[0],
502                       (const SkVector*)&storage[4]);
503    return kSizeInMemory;
504}
505
506#include "SkString.h"
507#include "SkStringUtils.h"
508
509void SkRRect::dump(bool asHex) const {
510    SkScalarAsStringType asType = asHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
511
512    fRect.dump(asHex);
513    SkString line("const SkPoint corners[] = {\n");
514    for (int i = 0; i < 4; ++i) {
515        SkString strX, strY;
516        SkAppendScalar(&strX, fRadii[i].x(), asType);
517        SkAppendScalar(&strY, fRadii[i].y(), asType);
518        line.appendf("    { %s, %s },", strX.c_str(), strY.c_str());
519        if (asHex) {
520            line.appendf(" /* %f %f */", fRadii[i].x(), fRadii[i].y());
521        }
522        line.append("\n");
523    }
524    line.append("};");
525    SkDebugf("%s\n", line.c_str());
526}
527
528///////////////////////////////////////////////////////////////////////////////
529
530#ifdef SK_DEBUG
531void SkRRect::validate() const {
532    bool allRadiiZero = (0 == fRadii[0].fX && 0 == fRadii[0].fY);
533    bool allCornersSquare = (0 == fRadii[0].fX || 0 == fRadii[0].fY);
534    bool allRadiiSame = true;
535
536    for (int i = 1; i < 4; ++i) {
537        if (0 != fRadii[i].fX || 0 != fRadii[i].fY) {
538            allRadiiZero = false;
539        }
540
541        if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
542            allRadiiSame = false;
543        }
544
545        if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
546            allCornersSquare = false;
547        }
548    }
549    bool patchesOfNine = radii_are_nine_patch(fRadii);
550
551    switch (fType) {
552        case kEmpty_Type:
553            SkASSERT(fRect.isEmpty());
554            SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare);
555            break;
556        case kRect_Type:
557            SkASSERT(!fRect.isEmpty());
558            SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare);
559            break;
560        case kOval_Type:
561            SkASSERT(!fRect.isEmpty());
562            SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare);
563
564            for (int i = 0; i < 4; ++i) {
565                SkASSERT(SkScalarNearlyEqual(fRadii[i].fX, SkScalarHalf(fRect.width())));
566                SkASSERT(SkScalarNearlyEqual(fRadii[i].fY, SkScalarHalf(fRect.height())));
567            }
568            break;
569        case kSimple_Type:
570            SkASSERT(!fRect.isEmpty());
571            SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare);
572            break;
573        case kNinePatch_Type:
574            SkASSERT(!fRect.isEmpty());
575            SkASSERT(!allRadiiZero && !allRadiiSame && !allCornersSquare);
576            SkASSERT(patchesOfNine);
577            break;
578        case kComplex_Type:
579            SkASSERT(!fRect.isEmpty());
580            SkASSERT(!allRadiiZero && !allRadiiSame && !allCornersSquare);
581            SkASSERT(!patchesOfNine);
582            break;
583    }
584}
585#endif // SK_DEBUG
586
587///////////////////////////////////////////////////////////////////////////////
588