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 */ 88 Type getType() const { 89 SkDEBUGCODE(this->validate();) 90 return static_cast<Type>(fType); 91 } 92 93 Type type() const { return this->getType(); } 94 95 inline bool isEmpty() const { return kEmpty_Type == this->getType(); } 96 inline bool isRect() const { return kRect_Type == this->getType(); } 97 inline bool isOval() const { return kOval_Type == this->getType(); } 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. 101 inline bool isSimpleCircular() const { 102 return this->isSimple() && SkScalarNearlyEqual(fRadii[0].fX, fRadii[0].fY); 103 } 104 inline bool isCircle() const { 105 return this->isOval() && SkScalarNearlyEqual(fRadii[0].fX, fRadii[0].fY); 106 } 107 inline bool isNinePatch() const { return kNinePatch_Type == this->getType(); } 108 inline bool isComplex() const { return kComplex_Type == this->getType(); } 109 110 bool allCornersCircular() const; 111 112 SkScalar width() const { return fRect.width(); } 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 */ 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 */ 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 144 static SkRRect MakeRect(const SkRect& r) { 145 SkRRect rr; 146 rr.setRect(r); 147 return rr; 148 } 149 150 static SkRRect MakeOval(const SkRect& oval) { 151 SkRRect rr; 152 rr.setOval(oval); 153 return rr; 154 } 155 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 */ 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 210 const SkRect& rect() const { return fRect; } 211 const SkVector& radii(Corner corner) const { return fRadii[corner]; } 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 */ 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 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 */ 257 void outset(SkScalar dx, SkScalar dy, SkRRect* dst) const { 258 this->inset(-dx, -dy, dst); 259 } 260 void outset(SkScalar dx, SkScalar dy) { 261 this->inset(-dx, -dy, this); 262 } 263 264 /** 265 * Translate the rrect by (dx, dy). 266 */ 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; 315 void dump() const { this->dump(false); } 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