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