track oval in SkPath

Committed on behalf of Guanqun.Lu@gmail.com

Review URL: http://codereview.appspot.com/6012047/



git-svn-id: http://skia.googlecode.com/svn/trunk@3705 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
bsalomon@google.com 2012-04-17 15:22:06 +00:00
parent bddbc45b0b
commit c047d414a4
3 changed files with 285 additions and 0 deletions

View File

@ -161,6 +161,18 @@ public:
this->setConvexity(isConvex ? kConvex_Convexity : kConcave_Convexity);
}
/** Returns true if the path is an oval.
*
* @param rect returns the bounding rect of this oval. It's a circle
* if the height and width are the same.
*
* @return true if this path is an oval.
* Tracking whether a path is an oval is considered an
* optimization for performance and so some paths that are in
* fact ovals can report false.
*/
bool isOval(SkRect* rect) const;
/** Clear any lines and curves from the path, making it empty. This frees up
internal storage associated with those segments.
This does NOT change the fill-type setting nor isConvex
@ -747,6 +759,8 @@ private:
uint8_t fSegmentMask;
mutable uint8_t fBoundsIsDirty;
mutable uint8_t fConvexity;
mutable SkBool8 fIsOval;
#ifdef SK_BUILD_FOR_ANDROID
uint32_t fGenerationID;
const SkPath* fSourcePath;
@ -778,7 +792,10 @@ private:
//
inline void injectMoveToIfNeeded();
inline bool hasOnlyMoveTos() const;
friend class SkAutoPathBoundsUpdate;
friend class SkAutoDisableOvalCheck;
};
#endif

View File

@ -32,6 +32,21 @@ static bool is_degenerate(const SkPath& path) {
return SkPath::kDone_Verb == iter.next(pts);
}
class SkAutoDisableOvalCheck {
public:
SkAutoDisableOvalCheck(SkPath* path) : fPath(path) {
fSaved = fPath->fIsOval;
}
~SkAutoDisableOvalCheck() {
fPath->fIsOval = fSaved;
}
private:
SkPath* fPath;
bool fSaved;
};
/* This guy's constructor/destructor bracket a path editing operation. It is
used when we know the bounds of the amount we are going to add to the path
(usually a new contour, but not required).
@ -119,6 +134,7 @@ SkPath::SkPath()
fConvexity = kUnknown_Convexity;
fSegmentMask = 0;
fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
fIsOval = false;
#ifdef SK_BUILD_FOR_ANDROID
fGenerationID = 0;
fSourcePath = NULL;
@ -151,6 +167,7 @@ SkPath& SkPath::operator=(const SkPath& src) {
fConvexity = src.fConvexity;
fSegmentMask = src.fSegmentMask;
fLastMoveToIndex = src.fLastMoveToIndex;
fIsOval = src.fIsOval;
GEN_ID_INC;
}
SkDEBUGCODE(this->validate();)
@ -182,6 +199,7 @@ void SkPath::swap(SkPath& other) {
SkTSwap<uint8_t>(fConvexity, other.fConvexity);
SkTSwap<uint8_t>(fSegmentMask, other.fSegmentMask);
SkTSwap<int>(fLastMoveToIndex, other.fLastMoveToIndex);
SkTSwap<SkBool8>(fIsOval, other.fIsOval);
GEN_ID_INC;
}
}
@ -210,6 +228,7 @@ void SkPath::reset() {
fConvexity = kUnknown_Convexity;
fSegmentMask = 0;
fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
fIsOval = false;
}
void SkPath::rewind() {
@ -222,6 +241,7 @@ void SkPath::rewind() {
fBoundsIsDirty = true;
fSegmentMask = 0;
fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE;
fIsOval = false;
}
bool SkPath::isEmpty() const {
@ -388,6 +408,7 @@ void SkPath::setLastPt(SkScalar x, SkScalar y) {
if (count == 0) {
this->moveTo(x, y);
} else {
fIsOval = false;
fPts[count - 1].set(x, y);
GEN_ID_INC;
}
@ -415,6 +436,7 @@ void SkPath::setConvexity(Convexity c) {
do { \
fBoundsIsDirty = true; \
fConvexity = kUnknown_Convexity; \
fIsOval = false; \
} while (0)
#define DIRTY_AFTER_EDIT_NO_CONVEXITY_CHANGE \
@ -729,7 +751,31 @@ void SkPath::addRoundRect(const SkRect& rect, const SkScalar rad[],
this->close();
}
bool SkPath::hasOnlyMoveTos() const {
const uint8_t* verbs = fVerbs.begin();
const uint8_t* verbStop = fVerbs.end();
while (verbs != verbStop) {
if (*verbs == kLine_Verb ||
*verbs == kQuad_Verb ||
*verbs == kCubic_Verb) {
return false;
}
++verbs;
}
return true;
}
void SkPath::addOval(const SkRect& oval, Direction dir) {
/* If addOval() is called after previous moveTo(),
this path is still marked as an oval. This is used to
fit into WebKit's calling sequences.
We can't simply check isEmpty() in this case, as additional
moveTo() would mark the path non empty.
*/
fIsOval = hasOnlyMoveTos();
SkAutoDisableOvalCheck adoc(this);
SkAutoPathBoundsUpdate apbu(this, oval);
SkScalar cx = oval.centerX();
@ -795,6 +841,17 @@ void SkPath::addOval(const SkRect& oval, Direction dir) {
this->close();
}
bool SkPath::isOval(SkRect* rect) const {
if (isEmpty() || !fIsOval) {
return false;
}
if (rect) {
*rect = getBounds();
}
return true;
}
void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
if (r > 0) {
SkRect rect;
@ -970,6 +1027,8 @@ void SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy) {
void SkPath::addPath(const SkPath& path, const SkMatrix& matrix) {
this->incReserve(path.fPts.count());
fIsOval = false;
RawIter iter(path);
SkPoint pts[4];
Verb verb;
@ -1023,6 +1082,8 @@ void SkPath::pathTo(const SkPath& path) {
this->incReserve(vcount);
fIsOval = false;
const uint8_t* verbs = path.fVerbs.begin();
const SkPoint* pts = path.fPts.begin() + 1; // 1 for the initial moveTo
@ -1055,6 +1116,8 @@ void SkPath::reversePathTo(const SkPath& path) {
this->incReserve(vcount);
fIsOval = false;
const uint8_t* verbs = path.fVerbs.begin();
const SkPoint* pts = path.fPts.begin();
@ -1095,6 +1158,8 @@ void SkPath::reverseAddPath(const SkPath& src) {
const uint8_t* startVerbs = src.fVerbs.begin();
const uint8_t* verbs = src.fVerbs.end();
fIsOval = false;
bool needMove = true;
bool needClose = false;
while (verbs > startVerbs) {
@ -1228,8 +1293,19 @@ void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
dst->fFillType = fFillType;
dst->fSegmentMask = fSegmentMask;
dst->fConvexity = fConvexity;
dst->fIsOval = fIsOval;
}
matrix.mapPoints(dst->fPts.begin(), fPts.begin(), fPts.count());
// isOval() must be called after dst->fPts are changed.
// otherwise, isOval() would call validate() implicitly and
// at that time it finds its control points are not bound by fBounds.
if (this->isOval(NULL)) {
// It's an oval only if it stays a rect.
dst->fIsOval = matrix.rectStaysRect();
}
SkDEBUGCODE(dst->validate();)
}
}

View File

@ -1053,6 +1053,196 @@ static void test_raw_iter(skiatest::Reporter* reporter) {
}
}
static void check_for_circle(skiatest::Reporter* reporter,
const SkPath& path, bool expected) {
SkRect rect;
REPORTER_ASSERT(reporter, path.isOval(&rect) == expected);
if (expected) {
REPORTER_ASSERT(reporter, rect.height() == rect.width());
}
}
static void test_circle_skew(skiatest::Reporter* reporter,
const SkPath& path) {
SkPath tmp;
SkMatrix m;
m.setSkew(SkIntToScalar(3), SkIntToScalar(5));
path.transform(m, &tmp);
check_for_circle(reporter, tmp, false);
}
static void test_circle_translate(skiatest::Reporter* reporter,
const SkPath& path) {
SkPath tmp;
// translate at small offset
SkMatrix m;
m.setTranslate(SkIntToScalar(15), SkIntToScalar(15));
path.transform(m, &tmp);
check_for_circle(reporter, tmp, true);
tmp.reset();
m.reset();
// translate at a relatively big offset
m.setTranslate(SkIntToScalar(1000), SkIntToScalar(1000));
path.transform(m, &tmp);
check_for_circle(reporter, tmp, true);
}
static void test_circle_rotate(skiatest::Reporter* reporter,
const SkPath& path) {
for (int angle = 0; angle < 360; ++angle) {
SkPath tmp;
SkMatrix m;
m.setRotate(SkIntToScalar(angle));
path.transform(m, &tmp);
// TODO: a rotated circle whose rotated angle is not a mutiple of 90
// degrees is not an oval anymore, this can be improved. we made this
// for the simplicity of our implementation.
if (angle % 90 == 0) {
check_for_circle(reporter, tmp, true);
} else {
check_for_circle(reporter, tmp, false);
}
}
}
static void test_circle_with_direction(skiatest::Reporter* reporter,
SkPath::Direction dir) {
SkPath path;
// circle at origin
path.addCircle(0, 0, SkIntToScalar(20), dir);
check_for_circle(reporter, path, true);
test_circle_rotate(reporter, path);
test_circle_translate(reporter, path);
test_circle_skew(reporter, path);
// circle at an offset at (10, 10)
path.reset();
path.addCircle(SkIntToScalar(10), SkIntToScalar(10),
SkIntToScalar(20), dir);
check_for_circle(reporter, path, true);
test_circle_rotate(reporter, path);
test_circle_translate(reporter, path);
test_circle_skew(reporter, path);
}
static void test_circle_with_add_paths(skiatest::Reporter* reporter) {
SkPath path;
SkPath circle;
SkPath rect;
SkPath empty;
circle.addCircle(0, 0, SkIntToScalar(10), SkPath::kCW_Direction);
rect.addRect(SkIntToScalar(5), SkIntToScalar(5),
SkIntToScalar(20), SkIntToScalar(20), SkPath::kCW_Direction);
SkMatrix translate;
translate.setTranslate(SkIntToScalar(12), SkIntToScalar(12));
// For simplicity, all the path concatenation related operations
// would mark it non-circle, though in theory it's still a circle.
// empty + circle (translate)
path = empty;
path.addPath(circle, translate);
check_for_circle(reporter, path, false);
// circle + empty (translate)
path = circle;
path.addPath(empty, translate);
check_for_circle(reporter, path, false);
// test reverseAddPath
path = circle;
path.reverseAddPath(rect);
check_for_circle(reporter, path, false);
}
static void test_circle(skiatest::Reporter* reporter) {
test_circle_with_direction(reporter, SkPath::kCW_Direction);
test_circle_with_direction(reporter, SkPath::kCCW_Direction);
// multiple addCircle()
SkPath path;
path.addCircle(0, 0, SkIntToScalar(10), SkPath::kCW_Direction);
path.addCircle(0, 0, SkIntToScalar(20), SkPath::kCW_Direction);
check_for_circle(reporter, path, false);
// some extra lineTo() would make isOval() fail
path.reset();
path.addCircle(0, 0, SkIntToScalar(10), SkPath::kCW_Direction);
path.lineTo(0, 0);
check_for_circle(reporter, path, false);
// not back to the original point
path.reset();
path.addCircle(0, 0, SkIntToScalar(10), SkPath::kCW_Direction);
path.setLastPt(SkIntToScalar(5), SkIntToScalar(5));
check_for_circle(reporter, path, false);
test_circle_with_add_paths(reporter);
}
static void test_oval(skiatest::Reporter* reporter) {
SkRect rect;
SkMatrix m;
SkPath path;
rect = SkRect::MakeWH(SkIntToScalar(30), SkIntToScalar(50));
path.addOval(rect);
REPORTER_ASSERT(reporter, path.isOval(NULL));
m.setRotate(90);
SkPath tmp;
path.transform(m, &tmp);
// an oval rotated 90 degrees is still an oval.
REPORTER_ASSERT(reporter, tmp.isOval(NULL));
m.reset();
m.setRotate(30);
tmp.reset();
path.transform(m, &tmp);
// an oval rotated 30 degrees is not an oval anymore.
REPORTER_ASSERT(reporter, !tmp.isOval(NULL));
// since empty path being transformed.
path.reset();
tmp.reset();
m.reset();
path.transform(m, &tmp);
REPORTER_ASSERT(reporter, !tmp.isOval(NULL));
// empty path is not an oval
tmp.reset();
REPORTER_ASSERT(reporter, !tmp.isOval(NULL));
// only has moveTo()s
tmp.reset();
tmp.moveTo(0, 0);
tmp.moveTo(SkIntToScalar(10), SkIntToScalar(10));
REPORTER_ASSERT(reporter, !tmp.isOval(NULL));
// mimic WebKit's calling convention,
// call moveTo() first and then call addOval()
path.reset();
path.moveTo(0, 0);
path.addOval(rect);
REPORTER_ASSERT(reporter, path.isOval(NULL));
// copy path
path.reset();
tmp.reset();
tmp.addOval(rect);
path = tmp;
REPORTER_ASSERT(reporter, path.isOval(NULL));
}
void TestPath(skiatest::Reporter* reporter) {
{
SkSize size;
@ -1138,6 +1328,8 @@ void TestPath(skiatest::Reporter* reporter) {
test_bounds(reporter);
test_iter(reporter);
test_raw_iter(reporter);
test_circle(reporter);
test_oval(reporter);
}
#include "TestClassDef.h"