Depending on what is returned, dst[] is treated as follows
1 dst[0..2] is the original quad
2 dst[0..2] and dst[2..4] are the two new quads
- If dst == null, it is ignored and only the count is returned.
*/
int SkChopQuadAtYExtrema(const SkPoint src[3], SkPoint dst[5]);
+int SkChopQuadAtXExtrema(const SkPoint src[3], SkPoint dst[5]);
/** Given 3 points on a quadratic bezier, divide it into 2 quadratics
if the point of maximum curvature exists on the quad segment.
#include "SkColorFilter.h"
#include "SkTime.h"
#include "SkRandom.h"
+
#include "SkLineClipper.h"
+#include "SkQuadClipper.h"
+
+static void drawQuad(SkCanvas* canvas, const SkPoint pts[3], const SkPaint& p) {
+ SkPath path;
+ path.moveTo(pts[0]);
+ path.quadTo(pts[1], pts[2]);
+ canvas->drawPath(path, p);
+}
+
+typedef void (*clipper_proc)(const SkPoint src[], const SkRect& clip,
+ SkCanvas*, const SkPaint&, const SkPaint&);
+
+static void check_clipper(int count, const SkPoint pts[], const SkRect& clip) {
+ for (int i = 0; i < count; i++) {
+ SkASSERT(pts[i].fX >= clip.fLeft);
+ SkASSERT(pts[i].fX <= clip.fRight);
+ SkASSERT(pts[i].fY >= clip.fTop);
+ SkASSERT(pts[i].fY <= clip.fBottom);
+ }
+}
+
+static void line_clipper(const SkPoint src[], const SkRect& clip,
+ SkCanvas* canvas, const SkPaint& p0, const SkPaint& p1) {
+ canvas->drawPoints(SkCanvas::kLines_PointMode, 2, src, p1);
+
+ SkPoint dst[SkLineClipper::kMaxPoints];
+ int count = SkLineClipper::ClipLine(src, clip, dst);
+ for (int i = 0; i < count; i++) {
+ check_clipper(2, &dst[i], clip);
+ canvas->drawPoints(SkCanvas::kLines_PointMode, 2, &dst[i], p0);
+ }
+}
+
+static void quad_clipper(const SkPoint src[], const SkRect& clip,
+ SkCanvas* canvas, const SkPaint& p0, const SkPaint& p1) {
+ drawQuad(canvas, src, p1);
+
+ SkQuadClipper2 clipper;
+ if (clipper.clipQuad(src, clip)) {
+ SkPoint pts[3];
+ SkPath::Verb verb;
+ while ((verb = clipper.next(pts)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kLine_Verb:
+ canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts, p0);
+ break;
+ case SkPath::kQuad_Verb:
+ drawQuad(canvas, pts, p0);
+ break;
+ default:
+ SkASSERT(!"unexpected verb");
+ }
+ }
+ }
+}
+
+static const clipper_proc gProcs[] = {
+ line_clipper,
+ quad_clipper
+};
+
+///////////////////////////////////////////////////////////////////////////////
enum {
- W = 640/4,
- H = 480/4
+ W = 640/3,
+ H = 480/3
};
class LineClipperView : public SkView {
+ int fProcIndex;
SkRect fClip;
SkRandom fRand;
- SkPoint fPts[2];
+ SkPoint fPts[4];
void randPts() {
- fPts[0].set(fRand.nextUScalar1() * 640, fRand.nextUScalar1() * 480);
- fPts[1].set(fRand.nextUScalar1() * 640, fRand.nextUScalar1() * 480);
+ for (int i = 0; i < SK_ARRAY_COUNT(fPts); i++) {
+ fPts[i].set(fRand.nextUScalar1() * 640,
+ fRand.nextUScalar1() * 480);
+ }
}
public:
int y = (480 - H)/2;
fClip.set(x, y, x + W, y + H);
this->randPts();
+
+ fProcIndex = 1;
}
protected:
canvas->drawLine(-999, y, 999, y, paint);
}
- static void check_lineclipper(int count, const SkPoint pts[],
- const SkRect& clip) {
- if (count > 0) {
- for (int i = 0; i <= count; i++) {
- SkASSERT(pts[i].fX >= clip.fLeft);
- SkASSERT(pts[i].fX <= clip.fRight);
- SkASSERT(pts[i].fY >= clip.fTop);
- SkASSERT(pts[i].fY <= clip.fBottom);
- }
- }
- }
-
virtual void onDraw(SkCanvas* canvas) {
this->drawBG(canvas);
- SkPaint paint;
+ SkPaint paint, paint1;
drawVLine(canvas, fClip.fLeft + SK_ScalarHalf, paint);
drawVLine(canvas, fClip.fRight - SK_ScalarHalf, paint);
paint.setAntiAlias(true);
paint.setColor(SK_ColorBLUE);
- paint.setStrokeWidth(SkIntToScalar(3));
+ paint.setStyle(SkPaint::kStroke_Style);
+ // paint.setStrokeWidth(SkIntToScalar(3));
paint.setStrokeCap(SkPaint::kRound_Cap);
- SkPoint pts[SkLineClipper::kMaxPoints];
- int count = SkLineClipper::ClipLine(fPts, fClip, pts);
- check_lineclipper(count, pts, fClip);
- for (int i = 0; i < count; i++) {
- canvas->drawPoints(SkCanvas::kLines_PointMode, 2, &pts[i], paint);
- }
-
- paint.setColor(SK_ColorRED);
- paint.setStrokeWidth(0);
- canvas->drawPoints(SkCanvas::kLines_PointMode, 2, fPts, paint);
+
+ paint1.setAntiAlias(true);
+ paint1.setColor(SK_ColorRED);
+ paint1.setStyle(SkPaint::kStroke_Style);
+ gProcs[fProcIndex](fPts, fClip, canvas, paint, paint1);
if (true) {
this->randPts();
}
virtual SkView::Click* onFindClickHandler(SkScalar x, SkScalar y) {
- this->randPts();
+ // fProcIndex = (fProcIndex + 1) % SK_ARRAY_COUNT(gProcs);
+ if (x < 50 && y < 50) {
+ this->randPts();
+ }
this->inval(NULL);
return NULL;
}
coords[2] = coords[6] = coords[4];
}
-static inline void force_quad_monotonic_in_y(SkPoint pts[3])
-{
- // zap pts[1].fY to the nearest value
- SkScalar ab = SkScalarAbs(pts[0].fY - pts[1].fY);
- SkScalar bc = SkScalarAbs(pts[1].fY - pts[2].fY);
- pts[1].fY = ab < bc ? pts[0].fY : pts[2].fY;
-}
-
/* Returns 0 for 1 quad, and 1 for two quads, either way the answer is
- stored in dst[]. Guarantees that the 1/2 quads will be monotonic.
-*/
+ stored in dst[]. Guarantees that the 1/2 quads will be monotonic.
+ */
int SkChopQuadAtYExtrema(const SkPoint src[3], SkPoint dst[5])
{
SkASSERT(src);
SkASSERT(dst);
-
+
#if 0
static bool once = true;
if (once)
SkDebugf("chop=%d, Y=[%x %x %x %x %x %x]\n", n, d[0].fY, d[1].fY, d[2].fY, d[3].fY, d[4].fY, d[5].fY);
}
#endif
-
+
SkScalar a = src[0].fY;
SkScalar b = src[1].fY;
SkScalar c = src[2].fY;
-
+
if (is_not_monotonic(a, b, c))
{
SkScalar tValue;
return 0;
}
+/* Returns 0 for 1 quad, and 1 for two quads, either way the answer is
+ stored in dst[]. Guarantees that the 1/2 quads will be monotonic.
+ */
+int SkChopQuadAtXExtrema(const SkPoint src[3], SkPoint dst[5])
+{
+ SkASSERT(src);
+ SkASSERT(dst);
+
+ SkScalar a = src[0].fX;
+ SkScalar b = src[1].fX;
+ SkScalar c = src[2].fX;
+
+ if (is_not_monotonic(a, b, c)) {
+ SkScalar tValue;
+ if (valid_unit_divide(a - b, a - b - b + c, &tValue)) {
+ SkChopQuadAt(src, dst, tValue);
+ flatten_double_quad_extrema(&dst[0].fX);
+ return 1;
+ }
+ // if we get here, we need to force dst to be monotonic, even though
+ // we couldn't compute a unit_divide value (probably underflow).
+ b = SkScalarAbs(a - b) < SkScalarAbs(b - c) ? a : c;
+ }
+ dst[0].set(a, src[0].fY);
+ dst[1].set(b, src[1].fY);
+ dst[2].set(c, src[2].fY);
+ return 0;
+}
+
// F(t) = a (1 - t) ^ 2 + 2 b t (1 - t) + c t ^ 2
// F'(t) = 2 (b - a) + 2 (a - 2b + c) t
// F''(t) = 2 (a - 2b + c)
#include "SkQuadClipper.h"
#include "SkGeometry.h"
-static bool chopMonoQuadAtY(SkPoint pts[3], SkScalar y, SkScalar* t) {
+static bool chopMonoQuadAt(SkScalar c0, SkScalar c1, SkScalar c2,
+ SkScalar target, SkScalar* t) {
/* Solve F(t) = y where F(t) := [0](1-t)^2 + 2[1]t(1-t) + [2]t^2
* We solve for t, using quadratic equation, hence we have to rearrange
* our cooefficents to look like At^2 + Bt + C
*/
- SkScalar A = pts[0].fY - pts[1].fY - pts[1].fY + pts[2].fY;
- SkScalar B = 2*(pts[1].fY - pts[0].fY);
- SkScalar C = pts[0].fY - y;
+ SkScalar A = c0 - c1 - c1 + c2;
+ SkScalar B = 2*(c1 - c0);
+ SkScalar C = c0 - target;
SkScalar roots[2]; // we only expect one, but make room for 2 for safety
int count = SkFindUnitQuadRoots(A, B, C, roots);
return false;
}
+static bool chopMonoQuadAtY(SkPoint pts[3], SkScalar y, SkScalar* t) {
+ return chopMonoQuadAt(pts[0].fY, pts[1].fY, pts[2].fY, y, t);
+}
+
+static bool chopMonoQuadAtX(SkPoint pts[3], SkScalar x, SkScalar* t) {
+ return chopMonoQuadAt(pts[0].fX, pts[1].fX, pts[2].fX, x, t);
+}
+
SkQuadClipper::SkQuadClipper() {}
void SkQuadClipper::setClip(const SkIRect& clip) {
return true;
}
+///////////////////////////////////////////////////////////////////////////////
+
+// Modify pts[] in place so that it is clipped in Y to the clip rect
+static void chop_quad_in_Y(SkPoint pts[3], const SkRect& clip) {
+ SkScalar t;
+ SkPoint tmp[5]; // for SkChopQuadAt
+
+ // are we partially above
+ if (pts[0].fY < clip.fTop) {
+ if (chopMonoQuadAtY(pts, clip.fTop, &t)) {
+ // take the 2nd chopped quad
+ SkChopQuadAt(pts, tmp, t);
+ pts[0] = tmp[2];
+ pts[1] = tmp[3];
+ } else {
+ // if chopMonoQuadAtY failed, then we may have hit inexact numerics
+ // so we just clamp against the top
+ for (int i = 0; i < 3; i++) {
+ if (pts[i].fY < clip.fTop) {
+ pts[i].fY = clip.fTop;
+ }
+ }
+ }
+ }
+
+ // are we partially below
+ if (pts[2].fY > clip.fBottom) {
+ if (chopMonoQuadAtY(pts, clip.fBottom, &t)) {
+ SkChopQuadAt(pts, tmp, t);
+ pts[1] = tmp[1];
+ pts[2] = tmp[2];
+ } else {
+ // if chopMonoQuadAtY failed, then we may have hit inexact numerics
+ // so we just clamp against the bottom
+ for (int i = 0; i < 3; i++) {
+ if (pts[i].fY > clip.fBottom) {
+ pts[i].fY = clip.fBottom;
+ }
+ }
+ }
+ }
+}
+
+/* src[] must be monotonic in Y. This routine copies src into dst, and sorts
+ it to be increasing in Y. If it had to reverse the order of the points,
+ it returns true, otherwise it returns false
+ */
+static bool sort_increasing_Y(SkPoint dst[], const SkPoint src[]) {
+ // we need the data to be monotonically increasing in Y
+ if (src[0].fY > src[2].fY) {
+ SkASSERT(src[0].fY >= src[1].fY);
+ SkASSERT(src[1].fY >= src[2].fY);
+ dst[0] = src[2];
+ dst[1] = src[1];
+ dst[2] = src[0];
+ return true;
+ } else {
+ SkASSERT(src[2].fY >= src[1].fY);
+ SkASSERT(src[1].fY >= src[0].fY);
+ memcpy(dst, src, 3 * sizeof(SkPoint));
+ return false;
+ }
+}
+
+// srcPts[] must be monotonic in X and Y
+void SkQuadClipper2::clipMonoQuad(const SkPoint srcPts[3], const SkRect& clip) {
+ SkPoint pts[3];
+ bool reverse = sort_increasing_Y(pts, srcPts);
+
+ // are we completely above or below
+ if (pts[2].fY <= clip.fTop || pts[0].fY >= clip.fBottom) {
+ return;
+ }
+
+ // Now chop so that pts is contained within clip in Y
+ chop_quad_in_Y(pts, clip);
+
+ if (pts[0].fX > pts[2].fX) {
+ SkTSwap<SkPoint>(pts[0], pts[2]);
+ reverse = !reverse;
+ }
+ SkASSERT(pts[0].fX <= pts[1].fX);
+ SkASSERT(pts[1].fX <= pts[2].fX);
+
+ // Now chop in X has needed, and record the segments
+
+ if (pts[2].fX <= clip.fLeft) { // wholly to the left
+ this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse);
+ return;
+ }
+ if (pts[0].fX >= clip.fRight) { // wholly to the right
+ this->appendVLine(clip.fRight, pts[0].fY, pts[2].fY, reverse);
+ return;
+ }
+
+ SkScalar t;
+ SkPoint tmp[5]; // for SkChopQuadAt
+
+ // are we partially to the left
+ if (pts[0].fX < clip.fLeft) {
+ if (chopMonoQuadAtX(pts, clip.fLeft, &t)) {
+ SkChopQuadAt(pts, tmp, t);
+ this->appendVLine(clip.fLeft, tmp[0].fY, tmp[2].fY, reverse);
+ pts[0] = tmp[2];
+ pts[1] = tmp[3];
+ } else {
+ // if chopMonoQuadAtY failed, then we may have hit inexact numerics
+ // so we just clamp against the left
+ this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse);
+ }
+ }
+
+ // are we partially to the right
+ if (pts[2].fX > clip.fRight) {
+ if (chopMonoQuadAtX(pts, clip.fRight, &t)) {
+ SkChopQuadAt(pts, tmp, t);
+ this->appendQuad(tmp, reverse);
+ this->appendVLine(clip.fRight, tmp[2].fY, tmp[4].fY, reverse);
+ } else {
+ // if chopMonoQuadAtY failed, then we may have hit inexact numerics
+ // so we just clamp against the right
+ this->appendVLine(clip.fRight, pts[0].fY, pts[3].fY, reverse);
+ }
+ } else { // wholly inside the clip
+ this->appendQuad(pts, reverse);
+ }
+}
+
+static bool quick_reject_quad(const SkPoint srcPts[3], const SkRect& clip) {
+ return (srcPts[0].fY <= clip.fTop &&
+ srcPts[1].fY <= clip.fTop &&
+ srcPts[2].fY <= clip.fTop)
+ ||
+ (srcPts[0].fY >= clip.fBottom &&
+ srcPts[1].fY >= clip.fBottom &&
+ srcPts[2].fY >= clip.fBottom);
+}
+
+bool SkQuadClipper2::clipQuad(const SkPoint srcPts[3], const SkRect& clip) {
+ fCurrPoint = fPoints;
+ fCurrVerb = fVerbs;
+
+ if (!quick_reject_quad(srcPts, clip)) {
+ SkPoint monoY[5];
+ int countY = SkChopQuadAtYExtrema(srcPts, monoY);
+ for (int y = 0; y <= countY; y++) {
+ SkPoint monoX[5];
+ int countX = SkChopQuadAtXExtrema(&monoY[y * 2], monoX);
+ SkASSERT(countY + countX <= 3);
+ for (int x = 0; x <= countX; x++) {
+ this->clipMonoQuad(&monoX[x * 2], clip);
+ SkASSERT(fCurrVerb - fVerbs < kMaxVerbs);
+ SkASSERT(fCurrPoint - fPoints <= kMaxPoints);
+ }
+ }
+ }
+
+ *fCurrVerb = SkPath::kDone_Verb;
+ fCurrPoint = fPoints;
+ fCurrVerb = fVerbs;
+ return SkPath::kDone_Verb != fVerbs[0];
+}
+
+void SkQuadClipper2::appendVLine(SkScalar x, SkScalar y0, SkScalar y1,
+ bool reverse) {
+ *fCurrVerb++ = SkPath::kLine_Verb;
+
+ if (reverse) {
+ SkTSwap<SkScalar>(y0, y1);
+ }
+ fCurrPoint[0].set(x, y0);
+ fCurrPoint[1].set(x, y1);
+ fCurrPoint += 2;
+}
+
+void SkQuadClipper2::appendQuad(const SkPoint pts[3], bool reverse) {
+ *fCurrVerb++ = SkPath::kQuad_Verb;
+
+ if (reverse) {
+ fCurrPoint[0] = pts[2];
+ fCurrPoint[2] = pts[0];
+ } else {
+ fCurrPoint[0] = pts[0];
+ fCurrPoint[2] = pts[2];
+ }
+ fCurrPoint[1] = pts[1];
+ fCurrPoint += 3;
+}
+
+SkPath::Verb SkQuadClipper2::next(SkPoint pts[]) {
+ SkPath::Verb verb = *fCurrVerb;
+
+ switch (verb) {
+ case SkPath::kLine_Verb:
+ memcpy(pts, fCurrPoint, 2 * sizeof(SkPoint));
+ fCurrPoint += 2;
+ fCurrVerb += 1;
+ break;
+ case SkPath::kQuad_Verb:
+ memcpy(pts, fCurrPoint, 3 * sizeof(SkPoint));
+ fCurrPoint += 3;
+ fCurrVerb += 1;
+ break;
+ case SkPath::kDone_Verb:
+ break;
+ default:
+ SkASSERT(!"unexpected verb in quadclippper2 iter");
+ break;
+ }
+ return verb;
+}
+
#ifndef SkQuadClipper_DEFINED
#define SkQuadClipper_DEFINED
-#include "SkPoint.h"
-#include "SkRect.h"
+#include "SkPath.h"
/** This class is initialized with a clip rectangle, and then can be fed quads,
which must already be monotonic in Y.
SkQuadClipper();
void setClip(const SkIRect& clip);
-
- bool clipQuad(const SkPoint src[3], SkPoint dst[3]);
+ bool clipQuad(const SkPoint src[3], SkPoint dst[3]);
+
private:
SkRect fClip;
};
+/** Iterator that returns the clipped segements of a quad clipped to a rect.
+ The segments will be either lines or quads (based on SkPath::Verb), and
+ will all be monotonic in Y
+ */
+class SkQuadClipper2 {
+public:
+ bool clipQuad(const SkPoint pts[3], const SkRect& clip);
+
+ SkPath::Verb next(SkPoint pts[]);
+
+private:
+ SkPoint* fCurrPoint;
+ SkPath::Verb* fCurrVerb;
+
+ enum {
+ kMaxVerbs = 10,
+ kMaxPoints = 21
+ };
+ SkPoint fPoints[kMaxPoints];
+ SkPath::Verb fVerbs[kMaxVerbs];
+
+ void clipMonoQuad(const SkPoint srcPts[3], const SkRect& clip);
+ void appendVLine(SkScalar x, SkScalar y0, SkScalar y1, bool reverse);
+ void appendQuad(const SkPoint pts[3], bool reverse);
+};
+
#endif