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