add quadclipping utility, plus sample test
authorreed@android.com <reed@android.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 17 Nov 2009 18:47:52 +0000 (18:47 +0000)
committerreed@android.com <reed@android.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 17 Nov 2009 18:47:52 +0000 (18:47 +0000)
git-svn-id: http://skia.googlecode.com/svn/trunk@429 2bbb7eff-a529-9590-31e7-b0007b416f81

include/core/SkGeometry.h
samplecode/SampleLineClipper.cpp
src/core/SkGeometry.cpp
src/core/SkQuadClipper.cpp
src/core/SkQuadClipper.h

index c517855..920afb0 100644 (file)
@@ -59,9 +59,9 @@ int SkFindQuadExtrema(SkScalar a, SkScalar b, SkScalar c, SkScalar tValues[1]);
     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.
index e990c86..ccf4281 100644 (file)
 #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:
@@ -36,6 +102,8 @@ public:
         int y = (480 - H)/2;
         fClip.set(x, y, x + W, y + H);
         this->randPts();
+        
+        fProcIndex = 1;
     }
     
 protected:
@@ -60,22 +128,10 @@ 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);
@@ -87,18 +143,14 @@ protected:
         
         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();
@@ -107,7 +159,10 @@ protected:
     }
 
     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;
     }
index 2775543..483c08e 100644 (file)
@@ -260,22 +260,14 @@ static inline void flatten_double_quad_extrema(SkScalar coords[14])
     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)
@@ -288,11 +280,11 @@ int SkChopQuadAtYExtrema(const SkPoint src[3], SkPoint dst[5])
         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;
@@ -312,6 +304,35 @@ int SkChopQuadAtYExtrema(const SkPoint src[3], SkPoint dst[5])
     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)
index 7521bdc..9d76298 100644 (file)
 #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);
@@ -35,6 +36,14 @@ static bool chopMonoQuadAtY(SkPoint pts[3], SkScalar y, SkScalar* t) {
     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) {
@@ -111,3 +120,215 @@ bool SkQuadClipper::clipQuad(const SkPoint srcPts[3], SkPoint dst[3]) {
     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;
+}
+
index fcb230f..9f9a6d5 100644 (file)
@@ -17,8 +17,7 @@
 #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.
@@ -31,11 +30,37 @@ public:
     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