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 
14 class SkPath;
15 class SkMatrix;
16 
17 // Path forward:
18 //   core work
19 //      add validate method (all radii positive, all radii sums < rect size, etc.)
20 //      add contains(SkRect&)  - for clip stack
21 //      add contains(SkRRect&) - for clip stack
22 //      add heart rect computation (max rect inside RR)
23 //      add 9patch rect computation
24 //      add growToInclude(SkPath&)
25 //   analysis
26 //      use growToInclude to fit skp round rects & generate stats (RRs vs. real paths)
27 //      check on # of rectorus's the RRs could handle
28 //   rendering work
29 //      update SkPath.addRRect() to only use quads
30 //      add GM and bench
31 //   further out
32 //      detect and triangulate RRectorii rather than falling back to SW in Ganesh
33 //
34 
35 /** \class SkRRect
36 
37     The SkRRect class represents a rounded rect with a potentially different
38     radii for each corner. It does not have a constructor so must be
39     initialized with one of the initialization functions (e.g., setEmpty,
40     setRectRadii, etc.)
41 
42     This class is intended to roughly match CSS' border-*-*-radius capabilities.
43     This means:
44         If either of a corner's radii are 0 the corner will be square.
45         Negative radii are not allowed (they are clamped to zero).
46         If the corner curves overlap they will be proportionally reduced to fit.
47 */
48 class SK_API SkRRect {
49 public:
50     /**
51      * Enum to capture the various possible subtypes of RR. Accessed
52      * by type(). The subtypes become progressively less restrictive.
53      */
54     enum Type {
55         // !< The RR is empty
56         kEmpty_Type,
57 
58         //!< The RR is actually a (non-empty) rect (i.e., at least one radius
59         //!< at each corner is zero)
60         kRect_Type,
61 
62         //!< The RR is actually a (non-empty) oval (i.e., all x radii are equal
63         //!< and >= width/2 and all the y radii are equal and >= height/2
64         kOval_Type,
65 
66         //!< The RR is non-empty and all the x radii are equal & all y radii
67         //!< are equal but it is not an oval (i.e., there are lines between
68         //!< the curves) nor a rect (i.e., both radii are non-zero)
69         kSimple_Type,
70 
71         //!< The RR is non-empty and the two left x radii are equal, the two top
72         //!< y radii are equal, and the same for the right and bottom but it is
73         //!< neither an rect, oval, nor a simple RR. It is called "nine patch"
74         //!< because the centers of the corner ellipses form an axis aligned
75         //!< rect with edges that divide the RR into an 9 rectangular patches:
76         //!< an interior patch, four edge patches, and four corner patches.
77         kNinePatch_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      */
getType()88     Type getType() const {
89         SkDEBUGCODE(this->validate();)
90         return static_cast<Type>(fType);
91     }
92 
type()93     Type type() const { return this->getType(); }
94 
isEmpty()95     inline bool isEmpty() const { return kEmpty_Type == this->getType(); }
isRect()96     inline bool isRect() const { return kRect_Type == this->getType(); }
isOval()97     inline bool isOval() const { return kOval_Type == this->getType(); }
isSimple()98     inline bool isSimple() const { return kSimple_Type == this->getType(); }
99     // TODO: should isSimpleCircular & isCircle take a tolerance? This could help
100     // instances where the mapping to device space is noisy.
isSimpleCircular()101     inline bool isSimpleCircular() const {
102         return this->isSimple() && SkScalarNearlyEqual(fRadii[0].fX, fRadii[0].fY);
103     }
isCircle()104     inline bool isCircle() const {
105         return this->isOval() && SkScalarNearlyEqual(fRadii[0].fX, fRadii[0].fY);
106     }
isNinePatch()107     inline bool isNinePatch() const { return kNinePatch_Type == this->getType(); }
isComplex()108     inline bool isComplex() const { return kComplex_Type == this->getType(); }
109 
110     bool allCornersCircular() const;
111 
width()112     SkScalar width() const { return fRect.width(); }
height()113     SkScalar height() const { return fRect.height(); }
114 
115     /**
116      * Set this RR to the empty rectangle (0,0,0,0) with 0 x & y radii.
117      */
setEmpty()118     void setEmpty() {
119         fRect.setEmpty();
120         memset(fRadii, 0, sizeof(fRadii));
121         fType = kEmpty_Type;
122 
123         SkDEBUGCODE(this->validate();)
124     }
125 
126     /**
127      * Set this RR to match the supplied rect. All radii will be 0.
128      */
setRect(const SkRect & rect)129     void setRect(const SkRect& rect) {
130         fRect = rect;
131         fRect.sort();
132 
133         if (fRect.isEmpty()) {
134             this->setEmpty();
135             return;
136         }
137 
138         memset(fRadii, 0, sizeof(fRadii));
139         fType = kRect_Type;
140 
141         SkDEBUGCODE(this->validate();)
142     }
143 
MakeRect(const SkRect & r)144     static SkRRect MakeRect(const SkRect& r) {
145         SkRRect rr;
146         rr.setRect(r);
147         return rr;
148     }
149 
MakeOval(const SkRect & oval)150     static SkRRect MakeOval(const SkRect& oval) {
151         SkRRect rr;
152         rr.setOval(oval);
153         return rr;
154     }
155 
MakeRectXY(const SkRect & rect,SkScalar xRad,SkScalar yRad)156     static SkRRect MakeRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) {
157         SkRRect rr;
158         rr.setRectXY(rect, xRad, yRad);
159         return rr;
160     }
161 
162     /**
163      * Set this RR to match the supplied oval. All x radii will equal half the
164      * width and all y radii will equal half the height.
165      */
setOval(const SkRect & oval)166     void setOval(const SkRect& oval) {
167         fRect = oval;
168         fRect.sort();
169 
170         if (fRect.isEmpty()) {
171             this->setEmpty();
172             return;
173         }
174 
175         SkScalar xRad = SkScalarHalf(fRect.width());
176         SkScalar yRad = SkScalarHalf(fRect.height());
177 
178         for (int i = 0; i < 4; ++i) {
179             fRadii[i].set(xRad, yRad);
180         }
181         fType = kOval_Type;
182 
183         SkDEBUGCODE(this->validate();)
184     }
185 
186     /**
187      * Initialize the RR with the same radii for all four corners.
188      */
189     void setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad);
190 
191     /**
192      * Initialize the rr with one radius per-side.
193      */
194     void setNinePatch(const SkRect& rect, SkScalar leftRad, SkScalar topRad,
195                       SkScalar rightRad, SkScalar bottomRad);
196 
197     /**
198      * Initialize the RR with potentially different radii for all four corners.
199      */
200     void setRectRadii(const SkRect& rect, const SkVector radii[4]);
201 
202     // The radii are stored in UL, UR, LR, LL order.
203     enum Corner {
204         kUpperLeft_Corner,
205         kUpperRight_Corner,
206         kLowerRight_Corner,
207         kLowerLeft_Corner
208     };
209 
rect()210     const SkRect& rect() const { return fRect; }
radii(Corner corner)211     const SkVector& radii(Corner corner) const { return fRadii[corner]; }
getBounds()212     const SkRect& getBounds() const { return fRect; }
213 
214     /**
215      *  When a rrect is simple, all of its radii are equal. This returns one
216      *  of those radii. This call requires the rrect to be non-complex.
217      */
getSimpleRadii()218     const SkVector& getSimpleRadii() const {
219         SkASSERT(!this->isComplex());
220         return fRadii[0];
221     }
222 
223     friend bool operator==(const SkRRect& a, const SkRRect& b) {
224         return a.fRect == b.fRect &&
225                SkScalarsEqual(a.fRadii[0].asScalars(),
226                               b.fRadii[0].asScalars(), 8);
227     }
228 
229     friend bool operator!=(const SkRRect& a, const SkRRect& b) {
230         return a.fRect != b.fRect ||
231                !SkScalarsEqual(a.fRadii[0].asScalars(),
232                                b.fRadii[0].asScalars(), 8);
233     }
234 
235     /**
236      *  Call inset on the bounds, and adjust the radii to reflect what happens
237      *  in stroking: If the corner is sharp (no curvature), leave it alone,
238      *  otherwise we grow/shrink the radii by the amount of the inset. If a
239      *  given radius becomes negative, it is pinned to 0.
240      *
241      *  It is valid for dst == this.
242      */
243     void inset(SkScalar dx, SkScalar dy, SkRRect* dst) const;
244 
inset(SkScalar dx,SkScalar dy)245     void inset(SkScalar dx, SkScalar dy) {
246         this->inset(dx, dy, this);
247     }
248 
249     /**
250      *  Call outset on the bounds, and adjust the radii to reflect what happens
251      *  in stroking: If the corner is sharp (no curvature), leave it alone,
252      *  otherwise we grow/shrink the radii by the amount of the inset. If a
253      *  given radius becomes negative, it is pinned to 0.
254      *
255      *  It is valid for dst == this.
256      */
outset(SkScalar dx,SkScalar dy,SkRRect * dst)257     void outset(SkScalar dx, SkScalar dy, SkRRect* dst) const {
258         this->inset(-dx, -dy, dst);
259     }
outset(SkScalar dx,SkScalar dy)260     void outset(SkScalar dx, SkScalar dy) {
261         this->inset(-dx, -dy, this);
262     }
263 
264     /**
265      * Translate the rrect by (dx, dy).
266      */
offset(SkScalar dx,SkScalar dy)267     void offset(SkScalar dx, SkScalar dy) {
268         fRect.offset(dx, dy);
269     }
270 
271     /**
272      *  Returns true if 'rect' is wholy inside the RR, and both
273      *  are not empty.
274      */
275     bool contains(const SkRect& rect) const;
276 
277     SkDEBUGCODE(void validate() const;)
278 
279     enum {
280         kSizeInMemory = 12 * sizeof(SkScalar)
281     };
282 
283     /**
284      *  Write the rrect into the specified buffer. This is guaranteed to always
285      *  write kSizeInMemory bytes, and that value is guaranteed to always be
286      *  a multiple of 4. Return kSizeInMemory.
287      */
288     size_t writeToMemory(void* buffer) const;
289 
290     /**
291      * Reads the rrect from the specified buffer
292      *
293      * If the specified buffer is large enough, this will read kSizeInMemory bytes,
294      * and that value is guaranteed to always be a multiple of 4.
295      *
296      * @param buffer Memory to read from
297      * @param length Amount of memory available in the buffer
298      * @return number of bytes read (must be a multiple of 4) or
299      *         0 if there was not enough memory available
300      */
301     size_t readFromMemory(const void* buffer, size_t length);
302 
303     /**
304      *  Transform by the specified matrix, and put the result in dst.
305      *
306      *  @param matrix SkMatrix specifying the transform. Must only contain
307      *      scale and/or translate, or this call will fail.
308      *  @param dst SkRRect to store the result. It is an error to use this,
309      *      which would make this function no longer const.
310      *  @return true on success, false on failure. If false, dst is unmodified.
311      */
312     bool transform(const SkMatrix& matrix, SkRRect* dst) const;
313 
314     void dump(bool asHex) const;
dump()315     void dump() const { this->dump(false); }
dumpHex()316     void dumpHex() const { this->dump(true); }
317 
318 private:
319     SkRect fRect;
320     // Radii order is UL, UR, LR, LL. Use Corner enum to index into fRadii[]
321     SkVector fRadii[4];
322     // use an explicitly sized type so we're sure the class is dense (no uninitialized bytes)
323     int32_t fType;
324     // TODO: add padding so we can use memcpy for flattening and not copy
325     // uninitialized data
326 
327     void computeType();
328     bool checkCornerContainment(SkScalar x, SkScalar y) const;
329     void scaleRadii();
330 
331     // to access fRadii directly
332     friend class SkPath;
333 };
334 
335 #endif
336