Line data Source code
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:
49 39 : 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 : */
91 0 : Type getType() const {
92 0 : SkASSERT(this->isValid());
93 0 : return static_cast<Type>(fType);
94 : }
95 :
96 0 : Type type() const { return this->getType(); }
97 :
98 0 : inline bool isEmpty() const { return kEmpty_Type == this->getType(); }
99 0 : inline bool isRect() const { return kRect_Type == this->getType(); }
100 0 : inline bool isOval() const { return kOval_Type == this->getType(); }
101 0 : 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.
104 0 : inline bool isSimpleCircular() const {
105 0 : return this->isSimple() && SkScalarNearlyEqual(fRadii[0].fX, fRadii[0].fY);
106 : }
107 : inline bool isCircle() const {
108 : return this->isOval() && SkScalarNearlyEqual(fRadii[0].fX, fRadii[0].fY);
109 : }
110 0 : inline bool isNinePatch() const { return kNinePatch_Type == this->getType(); }
111 0 : inline bool isComplex() const { return kComplex_Type == this->getType(); }
112 :
113 : bool allCornersCircular() const;
114 :
115 0 : SkScalar width() const { return fRect.width(); }
116 0 : 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 : */
121 0 : void setEmpty() {
122 0 : fRect.setEmpty();
123 0 : memset(fRadii, 0, sizeof(fRadii));
124 0 : fType = kEmpty_Type;
125 :
126 0 : SkASSERT(this->isValid());
127 0 : }
128 :
129 : /**
130 : * Set this RR to match the supplied rect. All radii will be 0.
131 : */
132 0 : void setRect(const SkRect& rect) {
133 0 : fRect = rect;
134 0 : fRect.sort();
135 :
136 0 : if (fRect.isEmpty()) {
137 0 : this->setEmpty();
138 0 : return;
139 : }
140 :
141 0 : memset(fRadii, 0, sizeof(fRadii));
142 0 : fType = kRect_Type;
143 :
144 0 : SkASSERT(this->isValid());
145 : }
146 :
147 : static SkRRect MakeEmpty() {
148 : SkRRect rr;
149 : rr.setEmpty();
150 : return rr;
151 : }
152 :
153 0 : static SkRRect MakeRect(const SkRect& r) {
154 0 : SkRRect rr;
155 0 : rr.setRect(r);
156 0 : return rr;
157 : }
158 :
159 0 : static SkRRect MakeOval(const SkRect& oval) {
160 0 : SkRRect rr;
161 0 : rr.setOval(oval);
162 0 : return rr;
163 : }
164 :
165 0 : static SkRRect MakeRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) {
166 0 : SkRRect rr;
167 0 : rr.setRectXY(rect, xRad, yRad);
168 0 : 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 : */
175 0 : void setOval(const SkRect& oval) {
176 0 : fRect = oval;
177 0 : fRect.sort();
178 :
179 0 : if (fRect.isEmpty()) {
180 0 : this->setEmpty();
181 0 : return;
182 : }
183 :
184 0 : SkScalar xRad = SkScalarHalf(fRect.width());
185 0 : SkScalar yRad = SkScalarHalf(fRect.height());
186 :
187 0 : for (int i = 0; i < 4; ++i) {
188 0 : fRadii[i].set(xRad, yRad);
189 : }
190 0 : fType = kOval_Type;
191 :
192 0 : 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 :
219 0 : const SkRect& rect() const { return fRect; }
220 0 : const SkVector& radii(Corner corner) const { return fRadii[corner]; }
221 0 : 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 : */
227 0 : const SkVector& getSimpleRadii() const {
228 0 : SkASSERT(!this->isComplex());
229 0 : return fRadii[0];
230 : }
231 :
232 0 : friend bool operator==(const SkRRect& a, const SkRRect& b) {
233 0 : return a.fRect == b.fRect &&
234 0 : SkScalarsEqual(a.fRadii[0].asScalars(),
235 0 : b.fRadii[0].asScalars(), 8);
236 : }
237 :
238 0 : friend bool operator!=(const SkRRect& a, const SkRRect& b) {
239 0 : return a.fRect != b.fRect ||
240 0 : !SkScalarsEqual(a.fRadii[0].asScalars(),
241 0 : 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 :
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 : */
266 0 : void outset(SkScalar dx, SkScalar dy, SkRRect* dst) const {
267 0 : this->inset(-dx, -dy, dst);
268 0 : }
269 : void outset(SkScalar dx, SkScalar dy) {
270 : this->inset(-dx, -dy, this);
271 : }
272 :
273 : /**
274 : * Translate the rrect by (dx, dy).
275 : */
276 0 : void offset(SkScalar dx, SkScalar dy) {
277 0 : fRect.offset(dx, dy);
278 0 : }
279 :
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;
328 0 : void dump() const { this->dump(false); }
329 : void dumpHex() const { this->dump(true); }
330 :
331 : private:
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
|