blob: dae5a0cdde917a939dba4d06d8f6823f7d9e251b [file] [log] [blame]
/*
* Copyright 2012 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkRRect_DEFINED
#define SkRRect_DEFINED
#include "SkRect.h"
#include "SkPoint.h"
class SkPath;
class SkMatrix;
// Path forward:
// core work
// add contains(SkRect&) - for clip stack
// add contains(SkRRect&) - for clip stack
// add heart rect computation (max rect inside RR)
// add 9patch rect computation
// add growToInclude(SkPath&)
// analysis
// use growToInclude to fit skp round rects & generate stats (RRs vs. real paths)
// check on # of rectorus's the RRs could handle
// rendering work
// update SkPath.addRRect() to only use quads
// add GM and bench
// further out
// detect and triangulate RRectorii rather than falling back to SW in Ganesh
//
/** \class SkRRect
The SkRRect class represents a rounded rect with a potentially different
radii for each corner. It does not have a constructor so must be
initialized with one of the initialization functions (e.g., setEmpty,
setRectRadii, etc.)
This class is intended to roughly match CSS' border-*-*-radius capabilities.
This means:
If either of a corner's radii are 0 the corner will be square.
Negative radii are not allowed (they are clamped to zero).
If the corner curves overlap they will be proportionally reduced to fit.
*/
class SK_API SkRRect {
public:
/** Default initialized to a rrect at the origin with zero width and height. */
SkRRect() = default;
SkRRect(const SkRRect& rrect) = default;
SkRRect& operator=(const SkRRect& rrect) = default;
/**
* Enum to capture the various possible subtypes of RR. Accessed
* by type(). The subtypes become progressively less restrictive.
*/
enum Type {
// !< The RR has zero width and/or zero height. All radii are zero.
kEmpty_Type,
//!< The RR is actually a (non-empty) rect (i.e., at least one radius
//!< at each corner is zero)
kRect_Type,
//!< The RR is actually a (non-empty) oval (i.e., all x radii are equal
//!< and >= width/2 and all the y radii are equal and >= height/2
kOval_Type,
//!< The RR is non-empty and all the x radii are equal & all y radii
//!< are equal but it is not an oval (i.e., there are lines between
//!< the curves) nor a rect (i.e., both radii are non-zero)
kSimple_Type,
//!< The RR is non-empty and the two left x radii are equal, the two top
//!< y radii are equal, and the same for the right and bottom but it is
//!< neither an rect, oval, nor a simple RR. It is called "nine patch"
//!< because the centers of the corner ellipses form an axis aligned
//!< rect with edges that divide the RR into an 9 rectangular patches:
//!< an interior patch, four edge patches, and four corner patches.
kNinePatch_Type,
//!< A fully general (non-empty) RR. Some of the x and/or y radii are
//!< different from the others and there must be one corner where
//!< both radii are non-zero.
kComplex_Type,
kLastType = kComplex_Type,
};
/**
* Returns the RR's sub type.
*/
Type getType() const {
SkASSERT(this->isValid());
return static_cast<Type>(fType);
}
Type type() const { return this->getType(); }
inline bool isEmpty() const { return kEmpty_Type == this->getType(); }
inline bool isRect() const { return kRect_Type == this->getType(); }
inline bool isOval() const { return kOval_Type == this->getType(); }
inline bool isSimple() const { return kSimple_Type == this->getType(); }
inline bool isNinePatch() const { return kNinePatch_Type == this->getType(); }
inline bool isComplex() const { return kComplex_Type == this->getType(); }
SkScalar width() const { return fRect.width(); }
SkScalar height() const { return fRect.height(); }
/**
* kSimple means that all corners have the same x,y radii. This returns the top/left
* corner's radii as representative of all corners. If the RRect is kComplex, then
* this still returns that corner's radii, but it is not indicative of the other corners.
*/
SkVector getSimpleRadii() const {
return fRadii[0];
}
/**
* Same as default initialized - zero width and height at the origin.
*/
void setEmpty() { *this = SkRRect(); }
/**
* Set this RR to match the supplied rect. All radii will be 0.
*/
void setRect(const SkRect& rect) {
if (!this->initializeRect(rect)) {
return;
}
memset(fRadii, 0, sizeof(fRadii));
fType = kRect_Type;
SkASSERT(this->isValid());
}
/** Makes an empty rrect at the origin with zero width and height. */
static SkRRect MakeEmpty() { return SkRRect(); }
static SkRRect MakeRect(const SkRect& r) {
SkRRect rr;
rr.setRect(r);
return rr;
}
static SkRRect MakeOval(const SkRect& oval) {
SkRRect rr;
rr.setOval(oval);
return rr;
}
static SkRRect MakeRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) {
SkRRect rr;
rr.setRectXY(rect, xRad, yRad);
return rr;
}
/**
* Set this RR to match the supplied oval. All x radii will equal half the
* width and all y radii will equal half the height.
*/
void setOval(const SkRect& oval) {
if (!this->initializeRect(oval)) {
return;
}
SkScalar xRad = SkScalarHalf(fRect.width());
SkScalar yRad = SkScalarHalf(fRect.height());
for (int i = 0; i < 4; ++i) {
fRadii[i].set(xRad, yRad);
}
fType = kOval_Type;
SkASSERT(this->isValid());
}
/**
* Initialize the RR with the same radii for all four corners.
*/
void setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad);
/**
* Initialize the rr with one radius per-side.
*/
void setNinePatch(const SkRect& rect, SkScalar leftRad, SkScalar topRad,
SkScalar rightRad, SkScalar bottomRad);
/**
* Initialize the RR with potentially different radii for all four corners.
*/
void setRectRadii(const SkRect& rect, const SkVector radii[4]);
// The radii are stored in UL, UR, LR, LL order.
enum Corner {
kUpperLeft_Corner,
kUpperRight_Corner,
kLowerRight_Corner,
kLowerLeft_Corner,
};
const SkRect& rect() const { return fRect; }
SkVector radii(Corner corner) const { return fRadii[corner]; }
const SkRect& getBounds() const { return fRect; }
friend bool operator==(const SkRRect& a, const SkRRect& b) {
return a.fRect == b.fRect && SkScalarsEqual(&a.fRadii[0].fX, &b.fRadii[0].fX, 8);
}
friend bool operator!=(const SkRRect& a, const SkRRect& b) {
return a.fRect != b.fRect || !SkScalarsEqual(&a.fRadii[0].fX, &b.fRadii[0].fX, 8);
}
/**
* Call inset on the bounds, and adjust the radii to reflect what happens
* in stroking: If the corner is sharp (no curvature), leave it alone,
* otherwise we grow/shrink the radii by the amount of the inset. If a
* given radius becomes negative, it is pinned to 0.
*
* If the inset amount is larger than the width/height then the rrect collapses to
* a degenerate line or point.
*
* If the inset is sufficiently negative to cause the bounds to become infinite then
* the result is a default initialized rrect.
*
* It is valid for dst == this.
*/
void inset(SkScalar dx, SkScalar dy, SkRRect* dst) const;
void inset(SkScalar dx, SkScalar dy) {
this->inset(dx, dy, this);
}
/**
* Call outset on the bounds, and adjust the radii to reflect what happens
* in stroking: If the corner is sharp (no curvature), leave it alone,
* otherwise we grow/shrink the radii by the amount of the inset. If a
* given radius becomes negative, it is pinned to 0.
*
* It is valid for dst == this.
*/
void outset(SkScalar dx, SkScalar dy, SkRRect* dst) const {
this->inset(-dx, -dy, dst);
}
void outset(SkScalar dx, SkScalar dy) {
this->inset(-dx, -dy, this);
}
/**
* Translate the rrect by (dx, dy).
*/
void offset(SkScalar dx, SkScalar dy) {
fRect.offset(dx, dy);
}
SkRRect SK_WARN_UNUSED_RESULT makeOffset(SkScalar dx, SkScalar dy) const {
return SkRRect(fRect.makeOffset(dx, dy), fRadii, fType);
}
/**
* Returns true if 'rect' is wholy inside the RR, and both
* are not empty.
*/
bool contains(const SkRect& rect) const;
bool isValid() const;
static constexpr size_t kSizeInMemory = 12 * sizeof(SkScalar);
/**
* Write the rrect into the specified buffer. This is guaranteed to always
* write kSizeInMemory bytes, and that value is guaranteed to always be
* a multiple of 4. Return kSizeInMemory.
*/
size_t writeToMemory(void* buffer) const;
/**
* Reads the rrect from the specified buffer
*
* If the specified buffer is large enough, this will read kSizeInMemory bytes,
* and that value is guaranteed to always be a multiple of 4.
*
* @param buffer Memory to read from
* @param length Amount of memory available in the buffer
* @return number of bytes read (must be a multiple of 4) or
* 0 if there was not enough memory available
*/
size_t readFromMemory(const void* buffer, size_t length);
/**
* Transform by the specified matrix, and put the result in dst.
*
* @param matrix SkMatrix specifying the transform. Must only contain
* scale and/or translate, or this call will fail.
* @param dst SkRRect to store the result. It is an error to use this,
* which would make this function no longer const.
* @return true on success, false on failure.
*/
bool transform(const SkMatrix& matrix, SkRRect* dst) const;
void dump(bool asHex) const;
void dump() const { this->dump(false); }
void dumpHex() const { this->dump(true); }
private:
static bool AreRectAndRadiiValid(const SkRect&, const SkVector[4]);
SkRRect(const SkRect& rect, const SkVector radii[4], int32_t type)
: fRect(rect)
, fRadii{radii[0], radii[1], radii[2], radii[3]}
, fType(type) {}
/**
* Initializes fRect. If the passed in rect is not finite or empty the rrect will be fully
* initialized and false is returned. Otherwise, just fRect is initialized and true is returned.
*/
bool initializeRect(const SkRect&);
void computeType();
bool checkCornerContainment(SkScalar x, SkScalar y) const;
void scaleRadii(const SkRect& rect);
SkRect fRect = SkRect::MakeEmpty();
// Radii order is UL, UR, LR, LL. Use Corner enum to index into fRadii[]
SkVector fRadii[4] = {{0, 0}, {0, 0}, {0,0}, {0,0}};
// use an explicitly sized type so we're sure the class is dense (no uninitialized bytes)
int32_t fType = kEmpty_Type;
// TODO: add padding so we can use memcpy for flattening and not copy uninitialized data
// to access fRadii directly
friend class SkPath;
friend class SkRRectPriv;
};
#endif