blob: dae5a0cdde917a939dba4d06d8f6823f7d9e251b [file] [log] [blame]
robertphillips@google.com5985e7c2012-11-29 13:24:55 +00001/*
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
reed@google.com4ed0fb72012-12-12 20:48:18 +000014class SkPath;
scroggo@google.com20e3cd22013-11-05 15:54:42 +000015class SkMatrix;
reed@google.com4ed0fb72012-12-12 20:48:18 +000016
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000017// Path forward:
18// core work
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000019// 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
commit-bot@chromium.org14e50ae2014-02-16 23:35:31 +000028// update SkPath.addRRect() to only use quads
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000029// add GM and bench
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000030// further out
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000031// detect and triangulate RRectorii rather than falling back to SW in Ganesh
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +000032//
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000033
34/** \class SkRRect
35
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +000036 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,
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000039 setRectRadii, etc.)
40
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +000041 This class is intended to roughly match CSS' border-*-*-radius capabilities.
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000042 This means:
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +000043 If either of a corner's radii are 0 the corner will be square.
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000044 Negative radii are not allowed (they are clamped to zero).
45 If the corner curves overlap they will be proportionally reduced to fit.
46*/
47class SK_API SkRRect {
48public:
Brian Salomon0a241ce2017-12-15 11:31:05 -050049 /** Default initialized to a rrect at the origin with zero width and height. */
50 SkRRect() = default;
51
Cary Clark1e447df2018-06-12 16:49:49 -040052 SkRRect(const SkRRect& rrect) = default;
53 SkRRect& operator=(const SkRRect& rrect) = default;
bsalomon7f0d9f32016-08-15 14:49:10 -070054
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +000055 /**
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000056 * Enum to capture the various possible subtypes of RR. Accessed
57 * by type(). The subtypes become progressively less restrictive.
58 */
59 enum Type {
Brian Salomon0a241ce2017-12-15 11:31:05 -050060 // !< The RR has zero width and/or zero height. All radii are zero.
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000061 kEmpty_Type,
62
63 //!< The RR is actually a (non-empty) rect (i.e., at least one radius
64 //!< at each corner is zero)
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +000065 kRect_Type,
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000066
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +000067 //!< The RR is actually a (non-empty) oval (i.e., all x radii are equal
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000068 //!< and >= width/2 and all the y radii are equal and >= height/2
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +000069 kOval_Type,
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000070
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +000071 //!< The RR is non-empty and all the x radii are equal & all y radii
72 //!< are equal but it is not an oval (i.e., there are lines between
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000073 //!< the curves) nor a rect (i.e., both radii are non-zero)
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +000074 kSimple_Type,
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000075
commit-bot@chromium.orgf338d7c2014-03-17 21:17:30 +000076 //!< The RR is non-empty and the two left x radii are equal, the two top
77 //!< y radii are equal, and the same for the right and bottom but it is
78 //!< neither an rect, oval, nor a simple RR. It is called "nine patch"
79 //!< because the centers of the corner ellipses form an axis aligned
80 //!< rect with edges that divide the RR into an 9 rectangular patches:
81 //!< an interior patch, four edge patches, and four corner patches.
82 kNinePatch_Type,
83
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000084 //!< A fully general (non-empty) RR. Some of the x and/or y radii are
85 //!< different from the others and there must be one corner where
86 //!< both radii are non-zero.
87 kComplex_Type,
Adrienne Walkerba9741d2017-08-23 13:26:32 -070088
89 kLastType = kComplex_Type,
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000090 };
91
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +000092 /**
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000093 * Returns the RR's sub type.
94 */
reed@google.com4ed0fb72012-12-12 20:48:18 +000095 Type getType() const {
Robert Phillips49da3342016-09-23 14:23:22 -040096 SkASSERT(this->isValid());
reed727b8c12014-10-22 11:23:56 -070097 return static_cast<Type>(fType);
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000098 }
99
reed@google.com4ed0fb72012-12-12 20:48:18 +0000100 Type type() const { return this->getType(); }
101
102 inline bool isEmpty() const { return kEmpty_Type == this->getType(); }
103 inline bool isRect() const { return kRect_Type == this->getType(); }
104 inline bool isOval() const { return kOval_Type == this->getType(); }
105 inline bool isSimple() const { return kSimple_Type == this->getType(); }
commit-bot@chromium.orgf338d7c2014-03-17 21:17:30 +0000106 inline bool isNinePatch() const { return kNinePatch_Type == this->getType(); }
reed@google.com4ed0fb72012-12-12 20:48:18 +0000107 inline bool isComplex() const { return kComplex_Type == this->getType(); }
108
mike@reedtribe.org37071642012-12-17 02:10:42 +0000109 SkScalar width() const { return fRect.width(); }
110 SkScalar height() const { return fRect.height(); }
111
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +0000112 /**
Mike Reed242135a2018-02-22 13:41:39 -0500113 * kSimple means that all corners have the same x,y radii. This returns the top/left
114 * corner's radii as representative of all corners. If the RRect is kComplex, then
115 * this still returns that corner's radii, but it is not indicative of the other corners.
116 */
117 SkVector getSimpleRadii() const {
118 return fRadii[0];
119 }
120
121 /**
Brian Salomon0a241ce2017-12-15 11:31:05 -0500122 * Same as default initialized - zero width and height at the origin.
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000123 */
Brian Salomon0a241ce2017-12-15 11:31:05 -0500124 void setEmpty() { *this = SkRRect(); }
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000125
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +0000126 /**
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000127 * Set this RR to match the supplied rect. All radii will be 0.
128 */
129 void setRect(const SkRect& rect) {
Brian Salomon0a241ce2017-12-15 11:31:05 -0500130 if (!this->initializeRect(rect)) {
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000131 return;
132 }
133
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000134 memset(fRadii, 0, sizeof(fRadii));
135 fType = kRect_Type;
136
Robert Phillips49da3342016-09-23 14:23:22 -0400137 SkASSERT(this->isValid());
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000138 }
139
Brian Salomon0a241ce2017-12-15 11:31:05 -0500140 /** Makes an empty rrect at the origin with zero width and height. */
141 static SkRRect MakeEmpty() { return SkRRect(); }
bsalomonee295642016-06-06 14:01:25 -0700142
reed021f6312015-08-09 19:41:13 -0700143 static SkRRect MakeRect(const SkRect& r) {
144 SkRRect rr;
145 rr.setRect(r);
146 return rr;
147 }
bsalomonee295642016-06-06 14:01:25 -0700148
robertphillips30c4cae2015-09-15 10:20:55 -0700149 static SkRRect MakeOval(const SkRect& oval) {
150 SkRRect rr;
151 rr.setOval(oval);
152 return rr;
153 }
154
bsalomon4a4f14b2015-12-09 10:17:35 -0800155 static SkRRect MakeRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) {
156 SkRRect rr;
157 rr.setRectXY(rect, xRad, yRad);
158 return rr;
159 }
160
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +0000161 /**
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000162 * Set this RR to match the supplied oval. All x radii will equal half the
163 * width and all y radii will equal half the height.
164 */
165 void setOval(const SkRect& oval) {
Brian Salomon0a241ce2017-12-15 11:31:05 -0500166 if (!this->initializeRect(oval)) {
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000167 return;
168 }
169
robertphillips05302f82015-09-29 11:24:07 -0700170 SkScalar xRad = SkScalarHalf(fRect.width());
171 SkScalar yRad = SkScalarHalf(fRect.height());
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000172
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000173 for (int i = 0; i < 4; ++i) {
174 fRadii[i].set(xRad, yRad);
175 }
176 fType = kOval_Type;
177
Robert Phillips49da3342016-09-23 14:23:22 -0400178 SkASSERT(this->isValid());
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000179 }
180
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +0000181 /**
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000182 * Initialize the RR with the same radii for all four corners.
183 */
184 void setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad);
185
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +0000186 /**
commit-bot@chromium.orgf338d7c2014-03-17 21:17:30 +0000187 * Initialize the rr with one radius per-side.
188 */
189 void setNinePatch(const SkRect& rect, SkScalar leftRad, SkScalar topRad,
190 SkScalar rightRad, SkScalar bottomRad);
191
192 /**
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000193 * Initialize the RR with potentially different radii for all four corners.
194 */
195 void setRectRadii(const SkRect& rect, const SkVector radii[4]);
196
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +0000197 // The radii are stored in UL, UR, LR, LL order.
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000198 enum Corner {
199 kUpperLeft_Corner,
200 kUpperRight_Corner,
201 kLowerRight_Corner,
Cary Clark1e447df2018-06-12 16:49:49 -0400202 kLowerLeft_Corner,
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000203 };
204
205 const SkRect& rect() const { return fRect; }
Mike Reed242135a2018-02-22 13:41:39 -0500206 SkVector radii(Corner corner) const { return fRadii[corner]; }
reed@google.com4ed0fb72012-12-12 20:48:18 +0000207 const SkRect& getBounds() const { return fRect; }
208
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000209 friend bool operator==(const SkRRect& a, const SkRRect& b) {
Cary Clarkdf429f32017-11-08 11:44:31 -0500210 return a.fRect == b.fRect && SkScalarsEqual(&a.fRadii[0].fX, &b.fRadii[0].fX, 8);
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000211 }
212
213 friend bool operator!=(const SkRRect& a, const SkRRect& b) {
Cary Clarkdf429f32017-11-08 11:44:31 -0500214 return a.fRect != b.fRect || !SkScalarsEqual(&a.fRadii[0].fX, &b.fRadii[0].fX, 8);
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000215 }
216
217 /**
mike@reedtribe.orgbcbef572012-12-23 23:11:21 +0000218 * Call inset on the bounds, and adjust the radii to reflect what happens
219 * in stroking: If the corner is sharp (no curvature), leave it alone,
220 * otherwise we grow/shrink the radii by the amount of the inset. If a
221 * given radius becomes negative, it is pinned to 0.
222 *
Brian Salomon0a241ce2017-12-15 11:31:05 -0500223 * If the inset amount is larger than the width/height then the rrect collapses to
224 * a degenerate line or point.
225 *
226 * If the inset is sufficiently negative to cause the bounds to become infinite then
227 * the result is a default initialized rrect.
228 *
mike@reedtribe.orgbcbef572012-12-23 23:11:21 +0000229 * It is valid for dst == this.
230 */
mike@reedtribe.org37071642012-12-17 02:10:42 +0000231 void inset(SkScalar dx, SkScalar dy, SkRRect* dst) const;
mike@reedtribe.orgbcbef572012-12-23 23:11:21 +0000232
mike@reedtribe.org37071642012-12-17 02:10:42 +0000233 void inset(SkScalar dx, SkScalar dy) {
234 this->inset(dx, dy, this);
235 }
mike@reedtribe.orgbcbef572012-12-23 23:11:21 +0000236
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 */
mike@reedtribe.org37071642012-12-17 02:10:42 +0000245 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 }
skia.committer@gmail.comf1f66c02014-03-05 03:02:06 +0000251
commit-bot@chromium.orgfbde87f2014-03-04 16:25:34 +0000252 /**
253 * Translate the rrect by (dx, dy).
254 */
255 void offset(SkScalar dx, SkScalar dy) {
256 fRect.offset(dx, dy);
257 }
mike@reedtribe.org37071642012-12-17 02:10:42 +0000258
bsalomon7f0d9f32016-08-15 14:49:10 -0700259 SkRRect SK_WARN_UNUSED_RESULT makeOffset(SkScalar dx, SkScalar dy) const {
260 return SkRRect(fRect.makeOffset(dx, dy), fRadii, fType);
261 }
262
robertphillips@google.com32c1b662013-04-25 12:23:00 +0000263 /**
264 * Returns true if 'rect' is wholy inside the RR, and both
265 * are not empty.
266 */
267 bool contains(const SkRect& rect) const;
268
Robert Phillips49da3342016-09-23 14:23:22 -0400269 bool isValid() const;
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000270
Cary Clarkd98f78c2018-04-26 08:32:37 -0400271 static constexpr size_t kSizeInMemory = 12 * sizeof(SkScalar);
skia.committer@gmail.com306ab9d2012-12-13 02:01:33 +0000272
reed@google.com4ed0fb72012-12-12 20:48:18 +0000273 /**
274 * Write the rrect into the specified buffer. This is guaranteed to always
275 * write kSizeInMemory bytes, and that value is guaranteed to always be
276 * a multiple of 4. Return kSizeInMemory.
277 */
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +0000278 size_t writeToMemory(void* buffer) const;
reed@google.com4ed0fb72012-12-12 20:48:18 +0000279
280 /**
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +0000281 * Reads the rrect from the specified buffer
282 *
283 * If the specified buffer is large enough, this will read kSizeInMemory bytes,
284 * and that value is guaranteed to always be a multiple of 4.
285 *
286 * @param buffer Memory to read from
287 * @param length Amount of memory available in the buffer
288 * @return number of bytes read (must be a multiple of 4) or
289 * 0 if there was not enough memory available
reed@google.com4ed0fb72012-12-12 20:48:18 +0000290 */
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +0000291 size_t readFromMemory(const void* buffer, size_t length);
reed@google.com4ed0fb72012-12-12 20:48:18 +0000292
scroggo@google.com20e3cd22013-11-05 15:54:42 +0000293 /**
294 * Transform by the specified matrix, and put the result in dst.
295 *
296 * @param matrix SkMatrix specifying the transform. Must only contain
297 * scale and/or translate, or this call will fail.
298 * @param dst SkRRect to store the result. It is an error to use this,
299 * which would make this function no longer const.
Mike Klein20d479c2017-10-11 17:13:28 -0400300 * @return true on success, false on failure.
scroggo@google.com20e3cd22013-11-05 15:54:42 +0000301 */
302 bool transform(const SkMatrix& matrix, SkRRect* dst) const;
303
reede05fed02014-12-15 07:59:53 -0800304 void dump(bool asHex) const;
305 void dump() const { this->dump(false); }
306 void dumpHex() const { this->dump(true); }
bsalomonb6b02522014-06-09 07:59:06 -0700307
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000308private:
Mike Reed242135a2018-02-22 13:41:39 -0500309 static bool AreRectAndRadiiValid(const SkRect&, const SkVector[4]);
310
bsalomon7f0d9f32016-08-15 14:49:10 -0700311 SkRRect(const SkRect& rect, const SkVector radii[4], int32_t type)
312 : fRect(rect)
313 , fRadii{radii[0], radii[1], radii[2], radii[3]}
314 , fType(type) {}
315
Brian Salomon0a241ce2017-12-15 11:31:05 -0500316 /**
317 * Initializes fRect. If the passed in rect is not finite or empty the rrect will be fully
318 * initialized and false is returned. Otherwise, just fRect is initialized and true is returned.
319 */
320 bool initializeRect(const SkRect&);
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000321
mtklein8aacf202014-12-18 13:29:54 -0800322 void computeType();
robertphillips@google.com32c1b662013-04-25 12:23:00 +0000323 bool checkCornerContainment(SkScalar x, SkScalar y) const;
Cary Clark9b8b0ce2018-03-21 16:17:10 -0400324 void scaleRadii(const SkRect& rect);
reed@google.com4ed0fb72012-12-12 20:48:18 +0000325
Brian Salomon0a241ce2017-12-15 11:31:05 -0500326 SkRect fRect = SkRect::MakeEmpty();
327 // Radii order is UL, UR, LR, LL. Use Corner enum to index into fRadii[]
328 SkVector fRadii[4] = {{0, 0}, {0, 0}, {0,0}, {0,0}};
329 // use an explicitly sized type so we're sure the class is dense (no uninitialized bytes)
330 int32_t fType = kEmpty_Type;
331 // TODO: add padding so we can use memcpy for flattening and not copy uninitialized data
332
reed@google.com4ed0fb72012-12-12 20:48:18 +0000333 // to access fRadii directly
334 friend class SkPath;
Mike Reed242135a2018-02-22 13:41:39 -0500335 friend class SkRRectPriv;
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000336};
337
robertphillips@google.com85203012012-11-29 13:49:33 +0000338#endif