SkRRect.h revision bcbef579d02e255b9a29b5db2d6804f4bfc76d1c
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#ifndef SkRRect_DEFINED
9#define SkRRect_DEFINED
10
11#include "SkRect.h"
12#include "SkPoint.h"
13
14class SkPath;
15
16// Path forward:
17//   core work
18//      add validate method (all radii positive, all radii sums < rect size, etc.)
19//      add contains(SkRect&)  - for clip stack
20//      add contains(SkRRect&) - for clip stack
21//      add heart rect computation (max rect inside RR)
22//      add 9patch rect computation
23//      add growToInclude(SkPath&)
24//   analysis
25//      use growToInclude to fit skp round rects & generate stats (RRs vs. real paths)
26//      check on # of rectorus's the RRs could handle
27//   rendering work
28//      add entry points (clipRRect, drawRRect) - plumb down to SkDevice
29//      update SkPath.addRRect() to take an SkRRect - only use quads
30//          -- alternatively add addRRectToPath here
31//      add GM and bench
32//   clipping opt
33//      update SkClipStack to perform logic with RRs
34//   further out
35//      add RR rendering shader to Ganesh (akin to cicle drawing code)
36//          - only for simple RRs
37//      detect and triangulate RRectorii rather than falling back to SW in Ganesh
38//
39
40/** \class SkRRect
41
42    The SkRRect class represents a rounded rect with a potentially different
43    radii for each corner. It does not have a constructor so must be
44    initialized with one of the initialization functions (e.g., setEmpty,
45    setRectRadii, etc.)
46
47    This class is intended to roughly match CSS' border-*-*-radius capabilities.
48    This means:
49        If either of a corner's radii are 0 the corner will be square.
50        Negative radii are not allowed (they are clamped to zero).
51        If the corner curves overlap they will be proportionally reduced to fit.
52*/
53class SK_API SkRRect {
54public:
55    /**
56     * Enum to capture the various possible subtypes of RR. Accessed
57     * by type(). The subtypes become progressively less restrictive.
58     */
59    enum Type {
60        // !< Internal indicator that the sub type must be computed.
61        kUnknown_Type = -1,
62
63        // !< The RR is empty
64        kEmpty_Type,
65
66        //!< The RR is actually a (non-empty) rect (i.e., at least one radius
67        //!< at each corner is zero)
68        kRect_Type,
69
70        //!< The RR is actually a (non-empty) oval (i.e., all x radii are equal
71        //!< and >= width/2 and all the y radii are equal and >= height/2
72        kOval_Type,
73
74        //!< The RR is non-empty and all the x radii are equal & all y radii
75        //!< are equal but it is not an oval (i.e., there are lines between
76        //!< the curves) nor a rect (i.e., both radii are non-zero)
77        kSimple_Type,
78
79        //!< A fully general (non-empty) RR. Some of the x and/or y radii are
80        //!< different from the others and there must be one corner where
81        //!< both radii are non-zero.
82        kComplex_Type,
83    };
84
85    /**
86     * Returns the RR's sub type.
87     */
88    Type getType() const {
89        SkDEBUGCODE(this->validate();)
90
91        if (kUnknown_Type == fType) {
92            this->computeType();
93        }
94        SkASSERT(kUnknown_Type != fType);
95        return fType;
96    }
97
98    Type type() const { return this->getType(); }
99
100    inline bool isEmpty() const { return kEmpty_Type == this->getType(); }
101    inline bool isRect() const { return kRect_Type == this->getType(); }
102    inline bool isOval() const { return kOval_Type == this->getType(); }
103    inline bool isSimple() const { return kSimple_Type == this->getType(); }
104    inline bool isComplex() const { return kComplex_Type == this->getType(); }
105
106    SkScalar width() const { return fRect.width(); }
107    SkScalar height() const { return fRect.height(); }
108
109    /**
110     * Set this RR to the empty rectangle (0,0,0,0) with 0 x & y radii.
111     */
112    void setEmpty() {
113        fRect.setEmpty();
114        memset(fRadii, 0, sizeof(fRadii));
115        fType = kEmpty_Type;
116
117        SkDEBUGCODE(this->validate();)
118    }
119
120    /**
121     * Set this RR to match the supplied rect. All radii will be 0.
122     */
123    void setRect(const SkRect& rect) {
124        if (rect.isEmpty()) {
125            this->setEmpty();
126            return;
127        }
128
129        fRect = rect;
130        memset(fRadii, 0, sizeof(fRadii));
131        fType = kRect_Type;
132
133        SkDEBUGCODE(this->validate();)
134    }
135
136    /**
137     * Set this RR to match the supplied oval. All x radii will equal half the
138     * width and all y radii will equal half the height.
139     */
140    void setOval(const SkRect& oval) {
141        if (oval.isEmpty()) {
142            this->setEmpty();
143            return;
144        }
145
146        SkScalar xRad = SkScalarHalf(oval.width());
147        SkScalar yRad = SkScalarHalf(oval.height());
148
149        fRect = oval;
150        for (int i = 0; i < 4; ++i) {
151            fRadii[i].set(xRad, yRad);
152        }
153        fType = kOval_Type;
154
155        SkDEBUGCODE(this->validate();)
156    }
157
158    /**
159     * Initialize the RR with the same radii for all four corners.
160     */
161    void setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad);
162
163    /**
164     * Initialize the RR with potentially different radii for all four corners.
165     */
166    void setRectRadii(const SkRect& rect, const SkVector radii[4]);
167
168    // The radii are stored in UL, UR, LR, LL order.
169    enum Corner {
170        kUpperLeft_Corner,
171        kUpperRight_Corner,
172        kLowerRight_Corner,
173        kLowerLeft_Corner
174    };
175
176    const SkRect& rect() const { return fRect; }
177    const SkVector& radii(Corner corner) const { return fRadii[corner]; }
178    const SkRect& getBounds() const { return fRect; }
179
180    /**
181     *  When a rrect is simple, all of its radii are equal. This returns one
182     *  of those radii. This call requires the rrect to be non-complex.
183     */
184    const SkVector& getSimpleRadii() const {
185        SkASSERT(!this->isComplex());
186        return fRadii[0];
187    }
188
189    friend bool operator==(const SkRRect& a, const SkRRect& b) {
190        return a.fRect == b.fRect &&
191               SkScalarsEqual((SkScalar*) a.fRadii, (SkScalar*) b.fRadii, 8);
192    }
193
194    friend bool operator!=(const SkRRect& a, const SkRRect& b) {
195        return a.fRect != b.fRect ||
196               !SkScalarsEqual((SkScalar*) a.fRadii, (SkScalar*) b.fRadii, 8);
197    }
198
199    /**
200     *  Returns true if (p.fX,p.fY) is inside the RR, and the RR
201     *  is not empty.
202     *
203     *  Contains treats the left and top differently from the right and bottom.
204     *  The left and top coordinates of the RR are themselves considered
205     *  to be inside, while the right and bottom are not. All the points on the
206     *  edges of the corners are considered to be inside.
207     */
208    bool contains(const SkPoint& p) const {
209        return contains(p.fX, p.fY);
210    }
211
212    /**
213     *  Returns true if (x,y) is inside the RR, and the RR
214     *  is not empty.
215     *
216     *  Contains treats the left and top differently from the right and bottom.
217     *  The left and top coordinates of the RR are themselves considered
218     *  to be inside, while the right and bottom are not. All the points on the
219     *  edges of the corners are considered to be inside.
220     */
221    bool contains(SkScalar x, SkScalar y) const;
222
223    /**
224     *  Call inset on the bounds, and adjust the radii to reflect what happens
225     *  in stroking: If the corner is sharp (no curvature), leave it alone,
226     *  otherwise we grow/shrink the radii by the amount of the inset. If a
227     *  given radius becomes negative, it is pinned to 0.
228     *
229     *  It is valid for dst == this.
230     */
231    void inset(SkScalar dx, SkScalar dy, SkRRect* dst) const;
232
233    void inset(SkScalar dx, SkScalar dy) {
234        this->inset(dx, dy, this);
235    }
236
237    /**
238     *  Call outset on the bounds, and adjust the radii to reflect what happens
239     *  in stroking: If the corner is sharp (no curvature), leave it alone,
240     *  otherwise we grow/shrink the radii by the amount of the inset. If a
241     *  given radius becomes negative, it is pinned to 0.
242     *
243     *  It is valid for dst == this.
244     */
245    void outset(SkScalar dx, SkScalar dy, SkRRect* dst) const {
246        this->inset(-dx, -dy, dst);
247    }
248    void outset(SkScalar dx, SkScalar dy) {
249        this->inset(-dx, -dy, this);
250    }
251
252    SkDEBUGCODE(void validate() const;)
253
254    enum {
255        kSizeInMemory = 12 * sizeof(SkScalar)
256    };
257
258    /**
259     *  Write the rrect into the specified buffer. This is guaranteed to always
260     *  write kSizeInMemory bytes, and that value is guaranteed to always be
261     *  a multiple of 4. Return kSizeInMemory.
262     */
263    uint32_t writeToMemory(void* buffer) const;
264
265    /**
266     *  Read the rrect from the specified buffer. This is guaranteed to always
267     *  read kSizeInMemory bytes, and that value is guaranteed to always be
268     *  a multiple of 4. Return kSizeInMemory.
269     */
270    uint32_t readFromMemory(const void* buffer);
271
272private:
273    SkRect fRect;
274    // Radii order is UL, UR, LR, LL. Use Corner enum to index into fRadii[]
275    SkVector fRadii[4];
276    mutable Type fType;
277    // TODO: add padding so we can use memcpy for flattening and not copy
278    // uninitialized data
279
280    void computeType() const;
281
282    // to access fRadii directly
283    friend class SkPath;
284};
285
286#endif
287