michael@0: /* michael@0: * Copyright 2012 Google Inc. michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: #ifndef SkRRect_DEFINED michael@0: #define SkRRect_DEFINED michael@0: michael@0: #include "SkRect.h" michael@0: #include "SkPoint.h" michael@0: michael@0: class SkPath; michael@0: class SkMatrix; michael@0: michael@0: // Path forward: michael@0: // core work michael@0: // add validate method (all radii positive, all radii sums < rect size, etc.) michael@0: // add contains(SkRect&) - for clip stack michael@0: // add contains(SkRRect&) - for clip stack michael@0: // add heart rect computation (max rect inside RR) michael@0: // add 9patch rect computation michael@0: // add growToInclude(SkPath&) michael@0: // analysis michael@0: // use growToInclude to fit skp round rects & generate stats (RRs vs. real paths) michael@0: // check on # of rectorus's the RRs could handle michael@0: // rendering work michael@0: // update SkPath.addRRect() to only use quads michael@0: // add GM and bench michael@0: // further out michael@0: // detect and triangulate RRectorii rather than falling back to SW in Ganesh michael@0: // michael@0: michael@0: /** \class SkRRect michael@0: michael@0: The SkRRect class represents a rounded rect with a potentially different michael@0: radii for each corner. It does not have a constructor so must be michael@0: initialized with one of the initialization functions (e.g., setEmpty, michael@0: setRectRadii, etc.) michael@0: michael@0: This class is intended to roughly match CSS' border-*-*-radius capabilities. michael@0: This means: michael@0: If either of a corner's radii are 0 the corner will be square. michael@0: Negative radii are not allowed (they are clamped to zero). michael@0: If the corner curves overlap they will be proportionally reduced to fit. michael@0: */ michael@0: class SK_API SkRRect { michael@0: public: michael@0: /** michael@0: * Enum to capture the various possible subtypes of RR. Accessed michael@0: * by type(). The subtypes become progressively less restrictive. michael@0: */ michael@0: enum Type { michael@0: // !< Internal indicator that the sub type must be computed. michael@0: kUnknown_Type = -1, michael@0: michael@0: // !< The RR is empty michael@0: kEmpty_Type, michael@0: michael@0: //!< The RR is actually a (non-empty) rect (i.e., at least one radius michael@0: //!< at each corner is zero) michael@0: kRect_Type, michael@0: michael@0: //!< The RR is actually a (non-empty) oval (i.e., all x radii are equal michael@0: //!< and >= width/2 and all the y radii are equal and >= height/2 michael@0: kOval_Type, michael@0: michael@0: //!< The RR is non-empty and all the x radii are equal & all y radii michael@0: //!< are equal but it is not an oval (i.e., there are lines between michael@0: //!< the curves) nor a rect (i.e., both radii are non-zero) michael@0: kSimple_Type, michael@0: michael@0: //!< A fully general (non-empty) RR. Some of the x and/or y radii are michael@0: //!< different from the others and there must be one corner where michael@0: //!< both radii are non-zero. michael@0: kComplex_Type, michael@0: }; michael@0: michael@0: /** michael@0: * Returns the RR's sub type. michael@0: */ michael@0: Type getType() const { michael@0: SkDEBUGCODE(this->validate();) michael@0: michael@0: if (kUnknown_Type == fType) { michael@0: this->computeType(); michael@0: } michael@0: SkASSERT(kUnknown_Type != fType); michael@0: return fType; michael@0: } michael@0: michael@0: Type type() const { return this->getType(); } michael@0: michael@0: inline bool isEmpty() const { return kEmpty_Type == this->getType(); } michael@0: inline bool isRect() const { return kRect_Type == this->getType(); } michael@0: inline bool isOval() const { return kOval_Type == this->getType(); } michael@0: inline bool isSimple() const { return kSimple_Type == this->getType(); } michael@0: inline bool isSimpleCircular() const { michael@0: return this->isSimple() && fRadii[0].fX == fRadii[0].fY; michael@0: } michael@0: inline bool isComplex() const { return kComplex_Type == this->getType(); } michael@0: michael@0: bool allCornersCircular() const; michael@0: michael@0: /** michael@0: * Are both x-radii the same on the two left corners, and similar for the top, right, and michael@0: * bottom. When this is the case the four ellipse centers form a rectangle. michael@0: */ michael@0: bool isNinePatch() const { michael@0: return fRadii[kUpperLeft_Corner].fX == fRadii[kLowerLeft_Corner].fX && michael@0: fRadii[kUpperRight_Corner].fX == fRadii[kLowerRight_Corner].fX && michael@0: fRadii[kUpperLeft_Corner].fY == fRadii[kUpperRight_Corner].fY && michael@0: fRadii[kLowerLeft_Corner].fY == fRadii[kLowerRight_Corner].fY; michael@0: } michael@0: michael@0: SkScalar width() const { return fRect.width(); } michael@0: SkScalar height() const { return fRect.height(); } michael@0: michael@0: /** michael@0: * Set this RR to the empty rectangle (0,0,0,0) with 0 x & y radii. michael@0: */ michael@0: void setEmpty() { michael@0: fRect.setEmpty(); michael@0: memset(fRadii, 0, sizeof(fRadii)); michael@0: fType = kEmpty_Type; michael@0: michael@0: SkDEBUGCODE(this->validate();) michael@0: } michael@0: michael@0: /** michael@0: * Set this RR to match the supplied rect. All radii will be 0. michael@0: */ michael@0: void setRect(const SkRect& rect) { michael@0: if (rect.isEmpty()) { michael@0: this->setEmpty(); michael@0: return; michael@0: } michael@0: michael@0: fRect = rect; michael@0: memset(fRadii, 0, sizeof(fRadii)); michael@0: fType = kRect_Type; michael@0: michael@0: SkDEBUGCODE(this->validate();) michael@0: } michael@0: michael@0: /** michael@0: * Set this RR to match the supplied oval. All x radii will equal half the michael@0: * width and all y radii will equal half the height. michael@0: */ michael@0: void setOval(const SkRect& oval) { michael@0: if (oval.isEmpty()) { michael@0: this->setEmpty(); michael@0: return; michael@0: } michael@0: michael@0: SkScalar xRad = SkScalarHalf(oval.width()); michael@0: SkScalar yRad = SkScalarHalf(oval.height()); michael@0: michael@0: fRect = oval; michael@0: for (int i = 0; i < 4; ++i) { michael@0: fRadii[i].set(xRad, yRad); michael@0: } michael@0: fType = kOval_Type; michael@0: michael@0: SkDEBUGCODE(this->validate();) michael@0: } michael@0: michael@0: /** michael@0: * Initialize the RR with the same radii for all four corners. michael@0: */ michael@0: void setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad); michael@0: michael@0: /** michael@0: * Initialize the RR with potentially different radii for all four corners. michael@0: */ michael@0: void setRectRadii(const SkRect& rect, const SkVector radii[4]); michael@0: michael@0: // The radii are stored in UL, UR, LR, LL order. michael@0: enum Corner { michael@0: kUpperLeft_Corner, michael@0: kUpperRight_Corner, michael@0: kLowerRight_Corner, michael@0: kLowerLeft_Corner michael@0: }; michael@0: michael@0: const SkRect& rect() const { return fRect; } michael@0: const SkVector& radii(Corner corner) const { return fRadii[corner]; } michael@0: const SkRect& getBounds() const { return fRect; } michael@0: michael@0: /** michael@0: * When a rrect is simple, all of its radii are equal. This returns one michael@0: * of those radii. This call requires the rrect to be non-complex. michael@0: */ michael@0: const SkVector& getSimpleRadii() const { michael@0: SkASSERT(!this->isComplex()); michael@0: return fRadii[0]; michael@0: } michael@0: michael@0: friend bool operator==(const SkRRect& a, const SkRRect& b) { michael@0: return a.fRect == b.fRect && michael@0: SkScalarsEqual(a.fRadii[0].asScalars(), michael@0: b.fRadii[0].asScalars(), 8); michael@0: } michael@0: michael@0: friend bool operator!=(const SkRRect& a, const SkRRect& b) { michael@0: return a.fRect != b.fRect || michael@0: !SkScalarsEqual(a.fRadii[0].asScalars(), michael@0: b.fRadii[0].asScalars(), 8); michael@0: } michael@0: michael@0: /** michael@0: * Call inset on the bounds, and adjust the radii to reflect what happens michael@0: * in stroking: If the corner is sharp (no curvature), leave it alone, michael@0: * otherwise we grow/shrink the radii by the amount of the inset. If a michael@0: * given radius becomes negative, it is pinned to 0. michael@0: * michael@0: * It is valid for dst == this. michael@0: */ michael@0: void inset(SkScalar dx, SkScalar dy, SkRRect* dst) const; michael@0: michael@0: void inset(SkScalar dx, SkScalar dy) { michael@0: this->inset(dx, dy, this); michael@0: } michael@0: michael@0: /** michael@0: * Call outset on the bounds, and adjust the radii to reflect what happens michael@0: * in stroking: If the corner is sharp (no curvature), leave it alone, michael@0: * otherwise we grow/shrink the radii by the amount of the inset. If a michael@0: * given radius becomes negative, it is pinned to 0. michael@0: * michael@0: * It is valid for dst == this. michael@0: */ michael@0: void outset(SkScalar dx, SkScalar dy, SkRRect* dst) const { michael@0: this->inset(-dx, -dy, dst); michael@0: } michael@0: void outset(SkScalar dx, SkScalar dy) { michael@0: this->inset(-dx, -dy, this); michael@0: } michael@0: michael@0: /** michael@0: * Translate the rrect by (dx, dy). michael@0: */ michael@0: void offset(SkScalar dx, SkScalar dy) { michael@0: fRect.offset(dx, dy); michael@0: } michael@0: michael@0: /** michael@0: * Returns true if 'rect' is wholy inside the RR, and both michael@0: * are not empty. michael@0: */ michael@0: bool contains(const SkRect& rect) const; michael@0: michael@0: SkDEBUGCODE(void validate() const;) michael@0: michael@0: enum { michael@0: kSizeInMemory = 12 * sizeof(SkScalar) michael@0: }; michael@0: michael@0: /** michael@0: * Write the rrect into the specified buffer. This is guaranteed to always michael@0: * write kSizeInMemory bytes, and that value is guaranteed to always be michael@0: * a multiple of 4. Return kSizeInMemory. michael@0: */ michael@0: size_t writeToMemory(void* buffer) const; michael@0: michael@0: /** michael@0: * Reads the rrect from the specified buffer michael@0: * michael@0: * If the specified buffer is large enough, this will read kSizeInMemory bytes, michael@0: * and that value is guaranteed to always be a multiple of 4. michael@0: * michael@0: * @param buffer Memory to read from michael@0: * @param length Amount of memory available in the buffer michael@0: * @return number of bytes read (must be a multiple of 4) or michael@0: * 0 if there was not enough memory available michael@0: */ michael@0: size_t readFromMemory(const void* buffer, size_t length); michael@0: michael@0: /** michael@0: * Transform by the specified matrix, and put the result in dst. michael@0: * michael@0: * @param matrix SkMatrix specifying the transform. Must only contain michael@0: * scale and/or translate, or this call will fail. michael@0: * @param dst SkRRect to store the result. It is an error to use this, michael@0: * which would make this function no longer const. michael@0: * @return true on success, false on failure. If false, dst is unmodified. michael@0: */ michael@0: bool transform(const SkMatrix& matrix, SkRRect* dst) const; michael@0: michael@0: private: michael@0: SkRect fRect; michael@0: // Radii order is UL, UR, LR, LL. Use Corner enum to index into fRadii[] michael@0: SkVector fRadii[4]; michael@0: mutable Type fType; michael@0: // TODO: add padding so we can use memcpy for flattening and not copy michael@0: // uninitialized data michael@0: michael@0: void computeType() const; michael@0: bool checkCornerContainment(SkScalar x, SkScalar y) const; michael@0: michael@0: // to access fRadii directly michael@0: friend class SkPath; michael@0: }; michael@0: michael@0: #endif