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 "include/core/SkPoint.h" |

12 | #include "include/core/SkRect.h" |

13 | #include "include/core/SkScalar.h" |

14 | #include "include/core/SkTypes.h" |

15 | |

16 | #include <cstdint> |

17 | #include <cstring> |

18 | |

19 | class SkMatrix; |

20 | class SkString; |

21 | |

22 | /** \class SkRRect |

23 | SkRRect describes a rounded rectangle with a bounds and a pair of radii for each corner. |

24 | The bounds and radii can be set so that SkRRect describes: a rectangle with sharp corners; |

25 | a circle; an oval; or a rectangle with one or more rounded corners. |

26 | |

27 | SkRRect allows implementing CSS properties that describe rounded corners. |

28 | SkRRect may have up to eight different radii, one for each axis on each of its four |

29 | corners. |

30 | |

31 | SkRRect may modify the provided parameters when initializing bounds and radii. |

32 | If either axis radii is zero or less: radii are stored as zero; corner is square. |

33 | If corner curves overlap, radii are proportionally reduced to fit within bounds. |

34 | */ |

35 | class SK_API SkRRect { |

36 | public: |

37 | |

38 | /** Initializes bounds at (0, 0), the origin, with zero width and height. |

39 | Initializes corner radii to (0, 0), and sets type of kEmpty_Type. |

40 | |

41 | @return empty SkRRect |

42 | */ |

43 | SkRRect() = default; |

44 | |

45 | /** Initializes to copy of rrect bounds and corner radii. |

46 | |

47 | @param rrect bounds and corner to copy |

48 | @return copy of rrect |

49 | */ |

50 | SkRRect(const SkRRect& rrect) = default; |

51 | |

52 | /** Copies rrect bounds and corner radii. |

53 | |

54 | @param rrect bounds and corner to copy |

55 | @return copy of rrect |

56 | */ |

57 | SkRRect& operator=(const SkRRect& rrect) = default; |

58 | |

59 | /** \enum SkRRect::Type |

60 | Type describes possible specializations of SkRRect. Each Type is |

61 | exclusive; a SkRRect may only have one type. |

62 | |

63 | Type members become progressively less restrictive; larger values of |

64 | Type have more degrees of freedom than smaller values. |

65 | */ |

66 | enum Type { |

67 | kEmpty_Type, //!< zero width or height |

68 | kRect_Type, //!< non-zero width and height, and zeroed radii |

69 | kOval_Type, //!< non-zero width and height filled with radii |

70 | kSimple_Type, //!< non-zero width and height with equal radii |

71 | kNinePatch_Type, //!< non-zero width and height with axis-aligned radii |

72 | kComplex_Type, //!< non-zero width and height with arbitrary radii |

73 | kLastType = kComplex_Type, //!< largest Type value |

74 | }; |

75 | |

76 | Type getType() const { |

77 | SkASSERT(this->isValid()); |

78 | return static_cast<Type>(fType); |

79 | } |

80 | |

81 | Type type() const { return this->getType(); } |

82 | |

83 | inline bool isEmpty() const { return kEmpty_Type == this->getType(); } |

84 | inline bool isRect() const { return kRect_Type == this->getType(); } |

85 | inline bool isOval() const { return kOval_Type == this->getType(); } |

86 | inline bool isSimple() const { return kSimple_Type == this->getType(); } |

87 | inline bool isNinePatch() const { return kNinePatch_Type == this->getType(); } |

88 | inline bool isComplex() const { return kComplex_Type == this->getType(); } |

89 | |

90 | /** Returns span on the x-axis. This does not check if result fits in 32-bit float; |

91 | result may be infinity. |

92 | |

93 | @return rect().fRight minus rect().fLeft |

94 | */ |

95 | SkScalar width() const { return fRect.width(); } |

96 | |

97 | /** Returns span on the y-axis. This does not check if result fits in 32-bit float; |

98 | result may be infinity. |

99 | |

100 | @return rect().fBottom minus rect().fTop |

101 | */ |

102 | SkScalar height() const { return fRect.height(); } |

103 | |

104 | /** Returns top-left corner radii. If type() returns kEmpty_Type, kRect_Type, |

105 | kOval_Type, or kSimple_Type, returns a value representative of all corner radii. |

106 | If type() returns kNinePatch_Type or kComplex_Type, at least one of the |

107 | remaining three corners has a different value. |

108 | |

109 | @return corner radii for simple types |

110 | */ |

111 | SkVector getSimpleRadii() const { |

112 | return fRadii[0]; |

113 | } |

114 | |

115 | /** Sets bounds to zero width and height at (0, 0), the origin. Sets |

116 | corner radii to zero and sets type to kEmpty_Type. |

117 | */ |

118 | void setEmpty() { *this = SkRRect(); } |

119 | |

120 | /** Sets bounds to sorted rect, and sets corner radii to zero. |

121 | If set bounds has width and height, and sets type to kRect_Type; |

122 | otherwise, sets type to kEmpty_Type. |

123 | |

124 | @param rect bounds to set |

125 | */ |

126 | void setRect(const SkRect& rect) { |

127 | if (!this->initializeRect(rect)) { |

128 | return; |

129 | } |

130 | |

131 | memset(s: fRadii, c: 0, n: sizeof(fRadii)); |

132 | fType = kRect_Type; |

133 | |

134 | SkASSERT(this->isValid()); |

135 | } |

136 | |

137 | /** Initializes bounds at (0, 0), the origin, with zero width and height. |

138 | Initializes corner radii to (0, 0), and sets type of kEmpty_Type. |

139 | |

140 | @return empty SkRRect |

141 | */ |

142 | static SkRRect MakeEmpty() { return SkRRect(); } |

143 | |

144 | /** Initializes to copy of r bounds and zeroes corner radii. |

145 | |

146 | @param r bounds to copy |

147 | @return copy of r |

148 | */ |

149 | static SkRRect MakeRect(const SkRect& r) { |

150 | SkRRect rr; |

151 | rr.setRect(r); |

152 | return rr; |

153 | } |

154 | |

155 | /** Sets bounds to oval, x-axis radii to half oval.width(), and all y-axis radii |

156 | to half oval.height(). If oval bounds is empty, sets to kEmpty_Type. |

157 | Otherwise, sets to kOval_Type. |

158 | |

159 | @param oval bounds of oval |

160 | @return oval |

161 | */ |

162 | static SkRRect MakeOval(const SkRect& oval) { |

163 | SkRRect rr; |

164 | rr.setOval(oval); |

165 | return rr; |

166 | } |

167 | |

168 | /** Sets to rounded rectangle with the same radii for all four corners. |

169 | If rect is empty, sets to kEmpty_Type. |

170 | Otherwise, if xRad and yRad are zero, sets to kRect_Type. |

171 | Otherwise, if xRad is at least half rect.width() and yRad is at least half |

172 | rect.height(), sets to kOval_Type. |

173 | Otherwise, sets to kSimple_Type. |

174 | |

175 | @param rect bounds of rounded rectangle |

176 | @param xRad x-axis radius of corners |

177 | @param yRad y-axis radius of corners |

178 | @return rounded rectangle |

179 | */ |

180 | static SkRRect MakeRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) { |

181 | SkRRect rr; |

182 | rr.setRectXY(rect, xRad, yRad); |

183 | return rr; |

184 | } |

185 | |

186 | /** Sets bounds to oval, x-axis radii to half oval.width(), and all y-axis radii |

187 | to half oval.height(). If oval bounds is empty, sets to kEmpty_Type. |

188 | Otherwise, sets to kOval_Type. |

189 | |

190 | @param oval bounds of oval |

191 | */ |

192 | void setOval(const SkRect& oval); |

193 | |

194 | /** Sets to rounded rectangle with the same radii for all four corners. |

195 | If rect is empty, sets to kEmpty_Type. |

196 | Otherwise, if xRad or yRad is zero, sets to kRect_Type. |

197 | Otherwise, if xRad is at least half rect.width() and yRad is at least half |

198 | rect.height(), sets to kOval_Type. |

199 | Otherwise, sets to kSimple_Type. |

200 | |

201 | @param rect bounds of rounded rectangle |

202 | @param xRad x-axis radius of corners |

203 | @param yRad y-axis radius of corners |

204 | |

205 | example: https://fiddle.skia.org/c/@RRect_setRectXY |

206 | */ |

207 | void setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad); |

208 | |

209 | /** Sets bounds to rect. Sets radii to (leftRad, topRad), (rightRad, topRad), |

210 | (rightRad, bottomRad), (leftRad, bottomRad). |

211 | |

212 | If rect is empty, sets to kEmpty_Type. |

213 | Otherwise, if leftRad and rightRad are zero, sets to kRect_Type. |

214 | Otherwise, if topRad and bottomRad are zero, sets to kRect_Type. |

215 | Otherwise, if leftRad and rightRad are equal and at least half rect.width(), and |

216 | topRad and bottomRad are equal at least half rect.height(), sets to kOval_Type. |

217 | Otherwise, if leftRad and rightRad are equal, and topRad and bottomRad are equal, |

218 | sets to kSimple_Type. Otherwise, sets to kNinePatch_Type. |

219 | |

220 | Nine patch refers to the nine parts defined by the radii: one center rectangle, |

221 | four edge patches, and four corner patches. |

222 | |

223 | @param rect bounds of rounded rectangle |

224 | @param leftRad left-top and left-bottom x-axis radius |

225 | @param topRad left-top and right-top y-axis radius |

226 | @param rightRad right-top and right-bottom x-axis radius |

227 | @param bottomRad left-bottom and right-bottom y-axis radius |

228 | */ |

229 | void setNinePatch(const SkRect& rect, SkScalar leftRad, SkScalar topRad, |

230 | SkScalar rightRad, SkScalar bottomRad); |

231 | |

232 | /** Sets bounds to rect. Sets radii array for individual control of all for corners. |

233 | |

234 | If rect is empty, sets to kEmpty_Type. |

235 | Otherwise, if one of each corner radii are zero, sets to kRect_Type. |

236 | Otherwise, if all x-axis radii are equal and at least half rect.width(), and |

237 | all y-axis radii are equal at least half rect.height(), sets to kOval_Type. |

238 | Otherwise, if all x-axis radii are equal, and all y-axis radii are equal, |

239 | sets to kSimple_Type. Otherwise, sets to kNinePatch_Type. |

240 | |

241 | @param rect bounds of rounded rectangle |

242 | @param radii corner x-axis and y-axis radii |

243 | |

244 | example: https://fiddle.skia.org/c/@RRect_setRectRadii |

245 | */ |

246 | void setRectRadii(const SkRect& rect, const SkVector radii[4]); |

247 | |

248 | /** \enum SkRRect::Corner |

249 | The radii are stored: top-left, top-right, bottom-right, bottom-left. |

250 | */ |

251 | enum Corner { |

252 | kUpperLeft_Corner, //!< index of top-left corner radii |

253 | kUpperRight_Corner, //!< index of top-right corner radii |

254 | kLowerRight_Corner, //!< index of bottom-right corner radii |

255 | kLowerLeft_Corner, //!< index of bottom-left corner radii |

256 | }; |

257 | |

258 | /** Returns bounds. Bounds may have zero width or zero height. Bounds right is |

259 | greater than or equal to left; bounds bottom is greater than or equal to top. |

260 | Result is identical to getBounds(). |

261 | |

262 | @return bounding box |

263 | */ |

264 | const SkRect& rect() const { return fRect; } |

265 | |

266 | /** Returns scalar pair for radius of curve on x-axis and y-axis for one corner. |

267 | Both radii may be zero. If not zero, both are positive and finite. |

268 | |

269 | @return x-axis and y-axis radii for one corner |

270 | */ |

271 | SkVector radii(Corner corner) const { return fRadii[corner]; } |

272 | |

273 | /** Returns bounds. Bounds may have zero width or zero height. Bounds right is |

274 | greater than or equal to left; bounds bottom is greater than or equal to top. |

275 | Result is identical to rect(). |

276 | |

277 | @return bounding box |

278 | */ |

279 | const SkRect& getBounds() const { return fRect; } |

280 | |

281 | /** Returns true if bounds and radii in a are equal to bounds and radii in b. |

282 | |

283 | a and b are not equal if either contain NaN. a and b are equal if members |

284 | contain zeroes with different signs. |

285 | |

286 | @param a SkRect bounds and radii to compare |

287 | @param b SkRect bounds and radii to compare |

288 | @return true if members are equal |

289 | */ |

290 | friend bool operator==(const SkRRect& a, const SkRRect& b) { |

291 | return a.fRect == b.fRect && SkScalarsEqual(a: &a.fRadii[0].fX, b: &b.fRadii[0].fX, n: 8); |

292 | } |

293 | |

294 | /** Returns true if bounds and radii in a are not equal to bounds and radii in b. |

295 | |

296 | a and b are not equal if either contain NaN. a and b are equal if members |

297 | contain zeroes with different signs. |

298 | |

299 | @param a SkRect bounds and radii to compare |

300 | @param b SkRect bounds and radii to compare |

301 | @return true if members are not equal |

302 | */ |

303 | friend bool operator!=(const SkRRect& a, const SkRRect& b) { |

304 | return a.fRect != b.fRect || !SkScalarsEqual(a: &a.fRadii[0].fX, b: &b.fRadii[0].fX, n: 8); |

305 | } |

306 | |

307 | /** Copies SkRRect to dst, then insets dst bounds by dx and dy, and adjusts dst |

308 | radii by dx and dy. dx and dy may be positive, negative, or zero. dst may be |

309 | SkRRect. |

310 | |

311 | If either corner radius is zero, the corner has no curvature and is unchanged. |

312 | Otherwise, if adjusted radius becomes negative, pins radius to zero. |

313 | If dx exceeds half dst bounds width, dst bounds left and right are set to |

314 | bounds x-axis center. If dy exceeds half dst bounds height, dst bounds top and |

315 | bottom are set to bounds y-axis center. |

316 | |

317 | If dx or dy cause the bounds to become infinite, dst bounds is zeroed. |

318 | |

319 | @param dx added to rect().fLeft, and subtracted from rect().fRight |

320 | @param dy added to rect().fTop, and subtracted from rect().fBottom |

321 | @param dst insets bounds and radii |

322 | |

323 | example: https://fiddle.skia.org/c/@RRect_inset |

324 | */ |

325 | void inset(SkScalar dx, SkScalar dy, SkRRect* dst) const; |

326 | |

327 | /** Insets bounds by dx and dy, and adjusts radii by dx and dy. dx and dy may be |

328 | positive, negative, or zero. |

329 | |

330 | If either corner radius is zero, the corner has no curvature and is unchanged. |

331 | Otherwise, if adjusted radius becomes negative, pins radius to zero. |

332 | If dx exceeds half bounds width, bounds left and right are set to |

333 | bounds x-axis center. If dy exceeds half bounds height, bounds top and |

334 | bottom are set to bounds y-axis center. |

335 | |

336 | If dx or dy cause the bounds to become infinite, bounds is zeroed. |

337 | |

338 | @param dx added to rect().fLeft, and subtracted from rect().fRight |

339 | @param dy added to rect().fTop, and subtracted from rect().fBottom |

340 | */ |

341 | void inset(SkScalar dx, SkScalar dy) { |

342 | this->inset(dx, dy, dst: this); |

343 | } |

344 | |

345 | /** Outsets dst bounds by dx and dy, and adjusts radii by dx and dy. dx and dy may be |

346 | positive, negative, or zero. |

347 | |

348 | If either corner radius is zero, the corner has no curvature and is unchanged. |

349 | Otherwise, if adjusted radius becomes negative, pins radius to zero. |

350 | If dx exceeds half dst bounds width, dst bounds left and right are set to |

351 | bounds x-axis center. If dy exceeds half dst bounds height, dst bounds top and |

352 | bottom are set to bounds y-axis center. |

353 | |

354 | If dx or dy cause the bounds to become infinite, dst bounds is zeroed. |

355 | |

356 | @param dx subtracted from rect().fLeft, and added to rect().fRight |

357 | @param dy subtracted from rect().fTop, and added to rect().fBottom |

358 | @param dst outset bounds and radii |

359 | */ |

360 | void outset(SkScalar dx, SkScalar dy, SkRRect* dst) const { |

361 | this->inset(dx: -dx, dy: -dy, dst); |

362 | } |

363 | |

364 | /** Outsets bounds by dx and dy, and adjusts radii by dx and dy. dx and dy may be |

365 | positive, negative, or zero. |

366 | |

367 | If either corner radius is zero, the corner has no curvature and is unchanged. |

368 | Otherwise, if adjusted radius becomes negative, pins radius to zero. |

369 | If dx exceeds half bounds width, bounds left and right are set to |

370 | bounds x-axis center. If dy exceeds half bounds height, bounds top and |

371 | bottom are set to bounds y-axis center. |

372 | |

373 | If dx or dy cause the bounds to become infinite, bounds is zeroed. |

374 | |

375 | @param dx subtracted from rect().fLeft, and added to rect().fRight |

376 | @param dy subtracted from rect().fTop, and added to rect().fBottom |

377 | */ |

378 | void outset(SkScalar dx, SkScalar dy) { |

379 | this->inset(dx: -dx, dy: -dy, dst: this); |

380 | } |

381 | |

382 | /** Translates SkRRect by (dx, dy). |

383 | |

384 | @param dx offset added to rect().fLeft and rect().fRight |

385 | @param dy offset added to rect().fTop and rect().fBottom |

386 | */ |

387 | void offset(SkScalar dx, SkScalar dy) { |

388 | fRect.offset(dx, dy); |

389 | } |

390 | |

391 | /** Returns SkRRect translated by (dx, dy). |

392 | |

393 | @param dx offset added to rect().fLeft and rect().fRight |

394 | @param dy offset added to rect().fTop and rect().fBottom |

395 | @return SkRRect bounds offset by (dx, dy), with unchanged corner radii |

396 | */ |

397 | [[nodiscard]] SkRRect makeOffset(SkScalar dx, SkScalar dy) const { |

398 | return SkRRect(fRect.makeOffset(dx, dy), fRadii, fType); |

399 | } |

400 | |

401 | /** Returns true if rect is inside the bounds and corner radii, and if |

402 | SkRRect and rect are not empty. |

403 | |

404 | @param rect area tested for containment |

405 | @return true if SkRRect contains rect |

406 | |

407 | example: https://fiddle.skia.org/c/@RRect_contains |

408 | */ |

409 | bool contains(const SkRect& rect) const; |

410 | |

411 | /** Returns true if bounds and radii values are finite and describe a SkRRect |

412 | SkRRect::Type that matches getType(). All SkRRect methods construct valid types, |

413 | even if the input values are not valid. Invalid SkRRect data can only |

414 | be generated by corrupting memory. |

415 | |

416 | @return true if bounds and radii match type() |

417 | |

418 | example: https://fiddle.skia.org/c/@RRect_isValid |

419 | */ |

420 | bool isValid() const; |

421 | |

422 | static constexpr size_t kSizeInMemory = 12 * sizeof(SkScalar); |

423 | |

424 | /** Writes SkRRect to buffer. Writes kSizeInMemory bytes, and returns |

425 | kSizeInMemory, the number of bytes written. |

426 | |

427 | @param buffer storage for SkRRect |

428 | @return bytes written, kSizeInMemory |

429 | |

430 | example: https://fiddle.skia.org/c/@RRect_writeToMemory |

431 | */ |

432 | size_t writeToMemory(void* buffer) const; |

433 | |

434 | /** Reads SkRRect from buffer, reading kSizeInMemory bytes. |

435 | Returns kSizeInMemory, bytes read if length is at least kSizeInMemory. |

436 | Otherwise, returns zero. |

437 | |

438 | @param buffer memory to read from |

439 | @param length size of buffer |

440 | @return bytes read, or 0 if length is less than kSizeInMemory |

441 | |

442 | example: https://fiddle.skia.org/c/@RRect_readFromMemory |

443 | */ |

444 | size_t readFromMemory(const void* buffer, size_t length); |

445 | |

446 | /** Transforms by SkRRect by matrix, storing result in dst. |

447 | Returns true if SkRRect transformed can be represented by another SkRRect. |

448 | Returns false if matrix contains transformations that are not axis aligned. |

449 | |

450 | Asserts in debug builds if SkRRect equals dst. |

451 | |

452 | @param matrix SkMatrix specifying the transform |

453 | @param dst SkRRect to store the result |

454 | @return true if transformation succeeded. |

455 | |

456 | example: https://fiddle.skia.org/c/@RRect_transform |

457 | */ |

458 | bool transform(const SkMatrix& matrix, SkRRect* dst) const; |

459 | |

460 | /** Writes text representation of SkRRect to standard output. |

461 | Set asHex true to generate exact binary representations |

462 | of floating point numbers. |

463 | |

464 | @param asHex true if SkScalar values are written as hexadecimal |

465 | |

466 | example: https://fiddle.skia.org/c/@RRect_dump |

467 | */ |

468 | void dump(bool asHex) const; |

469 | SkString dumpToString(bool asHex) const; |

470 | |

471 | /** Writes text representation of SkRRect to standard output. The representation |

472 | may be directly compiled as C++ code. Floating point values are written |

473 | with limited precision; it may not be possible to reconstruct original |

474 | SkRRect from output. |

475 | */ |

476 | void dump() const { this->dump(asHex: false); } |

477 | |

478 | /** Writes text representation of SkRRect to standard output. The representation |

479 | may be directly compiled as C++ code. Floating point values are written |

480 | in hexadecimal to preserve their exact bit pattern. The output reconstructs the |

481 | original SkRRect. |

482 | */ |

483 | void dumpHex() const { this->dump(asHex: true); } |

484 | |

485 | private: |

486 | static bool AreRectAndRadiiValid(const SkRect&, const SkVector[4]); |

487 | |

488 | SkRRect(const SkRect& rect, const SkVector radii[4], int32_t type) |

489 | : fRect(rect) |

490 | , fRadii{radii[0], radii[1], radii[2], radii[3]} |

491 | , fType(type) {} |

492 | |

493 | /** |

494 | * Initializes fRect. If the passed in rect is not finite or empty the rrect will be fully |

495 | * initialized and false is returned. Otherwise, just fRect is initialized and true is returned. |

496 | */ |

497 | bool initializeRect(const SkRect&); |

498 | |

499 | void computeType(); |

500 | bool checkCornerContainment(SkScalar x, SkScalar y) const; |

501 | // Returns true if the radii had to be scaled to fit rect |

502 | bool scaleRadii(); |

503 | |

504 | SkRect fRect = SkRect::MakeEmpty(); |

505 | // Radii order is UL, UR, LR, LL. Use Corner enum to index into fRadii[] |

506 | SkVector fRadii[4] = {{.fX: 0, .fY: 0}, {.fX: 0, .fY: 0}, {.fX: 0,.fY: 0}, {.fX: 0,.fY: 0}}; |

507 | // use an explicitly sized type so we're sure the class is dense (no uninitialized bytes) |

508 | int32_t fType = kEmpty_Type; |

509 | // TODO: add padding so we can use memcpy for flattening and not copy uninitialized data |

510 | |

511 | // to access fRadii directly |

512 | friend class SkPath; |

513 | friend class SkRRectPriv; |

514 | }; |

515 | |

516 | #endif |

517 |