Home | History | Annotate | Download | only in core
      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     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     Type getType() const {
     92         SkASSERT(this->isValid());
     93         return static_cast<Type>(fType);
     94     }
     95 
     96     Type type() const { return this->getType(); }
     97 
     98     inline bool isEmpty() const { return kEmpty_Type == this->getType(); }
     99     inline bool isRect() const { return kRect_Type == this->getType(); }
    100     inline bool isOval() const { return kOval_Type == this->getType(); }
    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.
    104     inline bool isSimpleCircular() const {
    105         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     inline bool isNinePatch() const { return kNinePatch_Type == this->getType(); }
    111     inline bool isComplex() const { return kComplex_Type == this->getType(); }
    112 
    113     bool allCornersCircular(SkScalar tolerance = SK_ScalarNearlyZero) const;
    114 
    115     SkScalar width() const { return fRect.width(); }
    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      */
    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      */
    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 
    147     static SkRRect MakeEmpty() {
    148         SkRRect rr;
    149         rr.setEmpty();
    150         return rr;
    151     }
    152 
    153     static SkRRect MakeRect(const SkRect& r) {
    154         SkRRect rr;
    155         rr.setRect(r);
    156         return rr;
    157     }
    158 
    159     static SkRRect MakeOval(const SkRect& oval) {
    160         SkRRect rr;
    161         rr.setOval(oval);
    162         return rr;
    163     }
    164 
    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      */
    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 
    219     const SkRect& rect() const { return fRect; }
    220     const SkVector& radii(Corner corner) const { return fRadii[corner]; }
    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      */
    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 
    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     void outset(SkScalar dx, SkScalar dy, SkRRect* dst) const {
    267         this->inset(-dx, -dy, dst);
    268     }
    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     void offset(SkScalar dx, SkScalar dy) {
    277         fRect.offset(dx, dy);
    278     }
    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     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
    354