From: reed@android.com Date: Mon, 30 Nov 2009 12:48:33 +0000 (+0000) Subject: add unittest for IntersectLine, used by hairlines X-Git-Tag: accepted/tizen/5.0/unified/20181102.025319~19310 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=a3d901099d7d295cd7d9df4114e874d9ccfff447;p=platform%2Fupstream%2FlibSkiaSharp.git add unittest for IntersectLine, used by hairlines git-svn-id: http://skia.googlecode.com/svn/trunk@447 2bbb7eff-a529-9590-31e7-b0007b416f81 --- diff --git a/samplecode/SampleHairline.cpp b/samplecode/SampleHairline.cpp index 9b6bcd5..6c2fc43 100644 --- a/samplecode/SampleHairline.cpp +++ b/samplecode/SampleHairline.cpp @@ -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) { diff --git a/src/core/SkEdgeClipper.cpp b/src/core/SkEdgeClipper.cpp index dc136a6..d41291d 100644 --- a/src/core/SkEdgeClipper.cpp +++ b/src/core/SkEdgeClipper.cpp @@ -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; } diff --git a/src/core/SkLineClipper.cpp b/src/core/SkLineClipper.cpp index 58bf603..057f546 100644 --- a/src/core/SkLineClipper.cpp +++ b/src/core/SkLineClipper.cpp @@ -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; } diff --git a/src/core/SkScan_Antihair.cpp b/src/core/SkScan_Antihair.cpp index 13cdc09..0003298 100644 --- a/src/core/SkScan_Antihair.cpp +++ b/src/core/SkScan_Antihair.cpp @@ -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 index 0000000..66301fc --- /dev/null +++ b/tests/ClipperTest.cpp @@ -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) diff --git a/tests/tests_files.mk b/tests/tests_files.mk index 04fc013..79e7efe 100644 --- a/tests/tests_files.mk +++ b/tests/tests_files.mk @@ -1,4 +1,5 @@ SOURCE := \ + ClipperTest.cpp \ GeometryTest.cpp \ MathTest.cpp \ MatrixTest.cpp \