add unittest for IntersectLine, used by hairlines
authorreed@android.com <reed@android.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Mon, 30 Nov 2009 12:48:33 +0000 (12:48 +0000)
committerreed@android.com <reed@android.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Mon, 30 Nov 2009 12:48:33 +0000 (12:48 +0000)
git-svn-id: http://skia.googlecode.com/svn/trunk@447 2bbb7eff-a529-9590-31e7-b0007b416f81

samplecode/SampleHairline.cpp
src/core/SkEdgeClipper.cpp
src/core/SkLineClipper.cpp
src/core/SkScan_Antihair.cpp
tests/ClipperTest.cpp [new file with mode: 0644]
tests/tests_files.mk

index 9b6bcd5..6c2fc43 100644 (file)
@@ -181,12 +181,14 @@ static int cycle_hairproc_index(int index) {
 }
 
 class HairlineView : public SkView {
+    SkMSec fNow;
     int fProcIndex;
     bool fDoAA;
 public:
        HairlineView() {
         fProcIndex = 0;
         fDoAA = true;
+        fNow = 0;
     }
     
 protected:
@@ -218,7 +220,9 @@ protected:
     virtual void onDraw(SkCanvas* canvas) {
         this->drawBG(canvas);
         
-        if (true) {
+        gRand.setSeed(fNow);
+        
+        if (false) {
             test_chromium_9005();
         }
         
@@ -242,15 +246,19 @@ protected:
         bm2.eraseColor(0);
         gProcs[fProcIndex].fProc(&c2, paint, bm);
         canvas->drawBitmap(bm2, SkIntToScalar(10), SkIntToScalar(10), NULL);
-        
-        fCounter += 1;
-        fDoAA = !fDoAA;
-        if (fCounter > 50) {
-            fProcIndex = cycle_hairproc_index(fProcIndex);
-            // todo: signal that we want to rebuild our TITLE
-            fCounter = 0;
+
+        SkMSec now = SampleCode::GetAnimTime();
+        if (fNow != now) {
+            fNow = now;
+            fCounter += 1;
+            fDoAA = !fDoAA;
+            if (fCounter > 50) {
+                fProcIndex = cycle_hairproc_index(fProcIndex);
+                // todo: signal that we want to rebuild our TITLE
+                fCounter = 0;
+            }
+            this->inval(NULL);
         }
-        this->inval(NULL);
     }
 
     virtual SkView::Click* onFindClickHandler(SkScalar x, SkScalar y) {
index dc136a6..d41291d 100644 (file)
@@ -235,23 +235,31 @@ static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
  //   SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
     SkASSERT(c0 < target && target < c3);
 
-    SkScalar D = c0;
+    SkScalar D = c0 - target;
     SkScalar A = c3 + 3*(c1 - c2) - c0;
     SkScalar B = 3*(c2 - c1 - c1 + c0);
     SkScalar C = 3*(c1 - c0);
 
+    const SkScalar TOLERANCE = SK_Scalar1 / 4096;
     SkScalar minT = 0;
     SkScalar maxT = SK_Scalar1;
-    for (int i = 0; i < 8; i++) {
-        SkScalar mid = SkScalarAve(minT, maxT);
-        SkScalar coord = eval_cubic_coeff(A, B, C, D, mid);
-        if (coord < target) {
+    SkScalar mid;
+    int i;
+    for (i = 0; i < 16; i++) {
+        mid = SkScalarAve(minT, maxT);
+        SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
+        if (delta < 0) {
             minT = mid;
+            delta = -delta;
         } else {
             maxT = mid;
         }
+        if (delta < TOLERANCE) {
+            break;
+        }
     }
-    *t = SkScalarAve(minT, maxT);
+    *t = mid;
+//    SkDebugf("-- evalCubicAt %d delta %g\n", i, eval_cubic_coeff(A, B, C, D, *t));
     return true;
 }
 
index 58bf603..057f546 100644 (file)
@@ -24,21 +24,37 @@ static SkScalar sect_with_vertical(const SkPoint src[2], SkScalar X) {
 
 ///////////////////////////////////////////////////////////////////////////////
 
+static inline bool nestedLT(SkScalar a, SkScalar b, SkScalar dim) {
+    return a <= b && (a < b || dim > 0);
+}
+
+// returns true if outer contains inner, even if inner is empty.
+// note: outer.contains(inner) always returns false if inner is empty.
+static inline bool containsNoEmptyCheck(const SkRect& outer,
+                                        const SkRect& inner) {
+    return  outer.fLeft <= inner.fLeft && outer.fTop <= inner.fTop &&
+            outer.fRight >= inner.fRight && outer.fBottom >= inner.fBottom;
+}
+
 bool SkLineClipper::IntersectLine(const SkPoint src[2], const SkRect& clip,
                                   SkPoint dst[2]) {
     SkRect bounds;
     
     bounds.set(src, 2);
-    if (bounds.fLeft >= clip.fRight || clip.fLeft >= bounds.fRight ||
-        bounds.fTop >= clip.fBottom || clip.fTop >= bounds.fBottom) {
-        return false;
-    }
-    if (clip.contains(bounds)) {
+    if (containsNoEmptyCheck(clip, bounds)) {
         if (src != dst) {
             memcpy(dst, src, 2 * sizeof(SkPoint));
         }
         return true;
     }
+    // check for no overlap, and only permit coincident edges if the line
+    // and the edge are colinear
+    if (nestedLT(bounds.fRight, clip.fLeft, bounds.width()) ||
+        nestedLT(clip.fRight, bounds.fLeft, bounds.width()) ||
+        nestedLT(bounds.fBottom, clip.fTop, bounds.height()) ||
+        nestedLT(clip.fBottom, bounds.fTop, bounds.height())) {
+        return false;
+    }
 
     int index0, index1;
     
@@ -70,7 +86,9 @@ bool SkLineClipper::IntersectLine(const SkPoint src[2], const SkRect& clip,
     }
 
     // check for quick-reject in X again, now that we may have been chopped
-    if (tmp[index1].fX <= clip.fLeft || tmp[index0].fX >= clip.fRight) {
+    if ((tmp[index1].fX <= clip.fLeft || tmp[index0].fX >= clip.fRight) &&
+        tmp[index0].fX < tmp[index1].fX) {
+        // only reject if we have a non-zero width
         return false;
     }
 
@@ -80,6 +98,10 @@ bool SkLineClipper::IntersectLine(const SkPoint src[2], const SkRect& clip,
     if (tmp[index1].fX > clip.fRight) {
         tmp[index1].set(clip.fRight, sect_with_vertical(src, clip.fRight));
     }
+#ifdef SK_DEBUG
+    bounds.set(tmp, 2);
+    SkASSERT(containsNoEmptyCheck(clip, bounds));
+#endif
     memcpy(dst, tmp, sizeof(tmp));
     return true;
 }
index 13cdc09..0003298 100644 (file)
@@ -429,6 +429,17 @@ void SkScan::AntiHairLine(const SkPoint& pt0, const SkPoint& pt1,
     if (clip) {
         SkRect clipBounds;
         clipBounds.set(clip->getBounds());
+        /*  We perform integral clipping later on, but we do a scalar clip first
+            to ensure that our coordinates are expressible in fixed/integers.
+
+            antialiased hairlines can draw up to 1/2 of a pixel outside of
+            their bounds, so we need to outset the clip before calling the
+            clipper. To make the numerics safer, we outset by a whole pixel,
+            since the 1/2 pixel boundary is important to the antihair blitter,
+            we don't want to risk numerical fate by chopping on that edge.
+         */
+        clipBounds.inset(-SK_Scalar1, -SK_Scalar1);
+
         if (!SkLineClipper::IntersectLine(pts, clipBounds, pts)) {
             return;
         }
diff --git a/tests/ClipperTest.cpp b/tests/ClipperTest.cpp
new file mode 100644 (file)
index 0000000..66301fc
--- /dev/null
@@ -0,0 +1,89 @@
+#include "Test.h"
+#include "SkPath.h"
+#include "SkLineClipper.h"
+#include "SkEdgeClipper.h"
+
+static void test_intersectline(skiatest::Reporter* reporter) {
+    static const SkScalar L = 0;
+    static const SkScalar T = 0;
+    static const SkScalar R = SkIntToScalar(100);
+    static const SkScalar B = SkIntToScalar(100);
+    static const SkScalar CX = SkScalarHalf(L + R);
+    static const SkScalar CY = SkScalarHalf(T + B);
+    static const SkRect gR = { L, T, R, B };
+
+    size_t i;
+    SkPoint dst[2];
+
+    static const SkPoint gEmpty[] = {
+        // sides
+        { L, CY }, { L - 10, CY },
+        { R, CY }, { R + 10, CY },
+        { CX, T }, { CX, T - 10 },
+        { CX, B }, { CX, B + 10 },
+        // corners
+        { L, T }, { L - 10, T - 10 },
+        { L, B }, { L - 10, B + 10 },
+        { R, T }, { R + 10, T - 10 },
+        { R, B }, { R + 10, B + 10 },
+    };
+    for (i = 0; i < SK_ARRAY_COUNT(gEmpty); i += 2) {
+        bool valid = SkLineClipper::IntersectLine(&gEmpty[i], gR, dst);
+        if (valid) {
+            SkDebugf("----- [%d] %g %g -> %g %g\n", i/2, dst[0].fX, dst[0].fY, dst[1].fX, dst[1].fY);
+        }
+        REPORTER_ASSERT(reporter, !valid);
+    }
+    
+    static const SkPoint gFull[] = {
+        // diagonals, chords
+        { L, T }, { R, B },
+        { L, B }, { R, T },
+        { CX, T }, { CX, B },
+        { L, CY }, { R, CY },
+        { CX, T }, { R, CY },
+        { CX, T }, { L, CY },
+        { L, CY }, { CX, B },
+        { R, CY }, { CX, B },
+        // edges
+        { L, T }, { L, B },
+        { R, T }, { R, B },
+        { L, T }, { R, T },
+        { L, B }, { R, B },
+    };
+    for (i = 0; i < SK_ARRAY_COUNT(gFull); i += 2) {
+        bool valid = SkLineClipper::IntersectLine(&gFull[i], gR, dst);
+        if (!valid || memcmp(&gFull[i], dst, sizeof(dst))) {
+            SkDebugf("++++ [%d] %g %g -> %g %g\n", i/2, dst[0].fX, dst[0].fY, dst[1].fX, dst[1].fY);
+        }
+        REPORTER_ASSERT(reporter, valid && !memcmp(&gFull[i], dst, sizeof(dst)));
+    }
+    
+    static const SkPoint gPartial[] = {
+        { L - 10, CY }, { CX, CY }, { L, CY }, { CX, CY },
+        { CX, T - 10 }, { CX, CY }, { CX, T }, { CX, CY },
+        { R + 10, CY }, { CX, CY }, { R, CY }, { CX, CY },
+        { CX, B + 10 }, { CX, CY }, { CX, B }, { CX, CY },
+        // extended edges
+        { L, T - 10 }, { L, B + 10 }, { L, T }, { L, B },
+        { R, T - 10 }, { R, B + 10 }, { R, T }, { R, B },
+        { L - 10, T }, { R + 10, T }, { L, T }, { R, T },
+        { L - 10, B }, { R + 10, B }, { L, B }, { R, B },
+    };
+    for (i = 0; i < SK_ARRAY_COUNT(gPartial); i += 4) {
+        bool valid = SkLineClipper::IntersectLine(&gPartial[i], gR, dst);
+        if (!valid || memcmp(&gPartial[i+2], dst, sizeof(dst))) {
+            SkDebugf("++++ [%d] %g %g -> %g %g\n", i/2, dst[0].fX, dst[0].fY, dst[1].fX, dst[1].fY);
+        }
+        REPORTER_ASSERT(reporter, valid &&
+                                  !memcmp(&gPartial[i+2], dst, sizeof(dst)));
+    }
+    
+}
+
+void TestClipper(skiatest::Reporter* reporter) {
+    test_intersectline(reporter);
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("Clipper", TestClipperClass, TestClipper)
index 04fc013..79e7efe 100644 (file)
@@ -1,4 +1,5 @@
 SOURCE := \
+       ClipperTest.cpp \
        GeometryTest.cpp \
     MathTest.cpp \
     MatrixTest.cpp \