|  | /* | 
|  | * Copyright 2011 Google Inc. | 
|  | * | 
|  | * Use of this source code is governed by a BSD-style license that can be | 
|  | * found in the LICENSE file. | 
|  | */ | 
|  |  | 
|  | #include "include/core/SkPathMeasure.h" | 
|  | #include "src/core/SkPathPriv.h" | 
|  | #include "tests/Test.h" | 
|  |  | 
|  | static void test_small_segment3() { | 
|  | SkPath path; | 
|  | const SkPoint pts[] = { | 
|  | { 0, 0 }, | 
|  | { 100000000000.0f, 100000000000.0f }, { 0, 0 }, { 10, 10 }, | 
|  | { 10, 10 }, { 0, 0 }, { 10, 10 } | 
|  | }; | 
|  |  | 
|  | path.moveTo(pts[0]); | 
|  | for (size_t i = 1; i < SK_ARRAY_COUNT(pts); i += 3) { | 
|  | path.cubicTo(pts[i], pts[i + 1], pts[i + 2]); | 
|  | } | 
|  |  | 
|  | SkPathMeasure meas(path, false); | 
|  | meas.getLength(); | 
|  | } | 
|  |  | 
|  | static void test_small_segment2() { | 
|  | SkPath path; | 
|  | const SkPoint pts[] = { | 
|  | { 0, 0 }, | 
|  | { 100000000000.0f, 100000000000.0f }, { 0, 0 }, | 
|  | { 10, 10 }, { 0, 0 }, | 
|  | }; | 
|  |  | 
|  | path.moveTo(pts[0]); | 
|  | for (size_t i = 1; i < SK_ARRAY_COUNT(pts); i += 2) { | 
|  | path.quadTo(pts[i], pts[i + 1]); | 
|  | } | 
|  | SkPathMeasure meas(path, false); | 
|  | meas.getLength(); | 
|  | } | 
|  |  | 
|  | static void test_small_segment() { | 
|  | SkPath path; | 
|  | const SkPoint pts[] = { | 
|  | { 100000, 100000}, | 
|  | // big jump between these points, makes a big segment | 
|  | { 1.0005f, 0.9999f }, | 
|  | // tiny (non-zero) jump between these points | 
|  | { SK_Scalar1, SK_Scalar1 }, | 
|  | }; | 
|  |  | 
|  | path.moveTo(pts[0]); | 
|  | for (size_t i = 1; i < SK_ARRAY_COUNT(pts); ++i) { | 
|  | path.lineTo(pts[i]); | 
|  | } | 
|  | SkPathMeasure meas(path, false); | 
|  |  | 
|  | /*  this would assert (before a fix) because we added a segment with | 
|  | the same length as the prev segment, due to the follow (bad) pattern | 
|  |  | 
|  | d = distance(pts[0], pts[1]); | 
|  | distance += d; | 
|  | seg->fDistance = distance; | 
|  |  | 
|  | SkASSERT(d > 0);    // TRUE | 
|  | SkASSERT(seg->fDistance > prevSeg->fDistance);  // FALSE | 
|  |  | 
|  | This 2nd assert failes because (distance += d) didn't affect distance | 
|  | because distance >>> d. | 
|  | */ | 
|  | meas.getLength(); | 
|  | } | 
|  |  | 
|  | DEF_TEST(PathMeasure, reporter) { | 
|  | SkPath  path; | 
|  |  | 
|  | path.moveTo(0, 0); | 
|  | path.lineTo(SK_Scalar1, 0); | 
|  | path.lineTo(SK_Scalar1, SK_Scalar1); | 
|  | path.lineTo(0, SK_Scalar1); | 
|  |  | 
|  | SkPathMeasure   meas(path, true); | 
|  | SkScalar        length = meas.getLength(); | 
|  | SkASSERT(length == SK_Scalar1*4); | 
|  |  | 
|  | path.reset(); | 
|  | path.moveTo(0, 0); | 
|  | path.lineTo(SK_Scalar1*3, SK_Scalar1*4); | 
|  | meas.setPath(&path, false); | 
|  | length = meas.getLength(); | 
|  | REPORTER_ASSERT(reporter, length == SK_Scalar1*5); | 
|  |  | 
|  | path.reset(); | 
|  | path.addCircle(0, 0, SK_Scalar1); | 
|  | meas.setPath(&path, true); | 
|  | length = meas.getLength(); | 
|  | //    SkDebugf("circle arc-length = %g\n", length); | 
|  |  | 
|  | // Test the behavior following a close not followed by a move. | 
|  | path.reset(); | 
|  | path.lineTo(SK_Scalar1, 0); | 
|  | path.lineTo(SK_Scalar1, SK_Scalar1); | 
|  | path.lineTo(0, SK_Scalar1); | 
|  | path.close(); | 
|  | path.lineTo(-SK_Scalar1, 0); | 
|  | meas.setPath(&path, false); | 
|  | length = meas.getLength(); | 
|  | REPORTER_ASSERT(reporter, length == SK_Scalar1 * 4); | 
|  | meas.nextContour(); | 
|  | length = meas.getLength(); | 
|  | REPORTER_ASSERT(reporter, length == SK_Scalar1); | 
|  | SkPoint position; | 
|  | SkVector tangent; | 
|  | REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent)); | 
|  | REPORTER_ASSERT(reporter, | 
|  | SkScalarNearlyEqual(position.fX, | 
|  | -SK_ScalarHalf, | 
|  | 0.0001f)); | 
|  | REPORTER_ASSERT(reporter, position.fY == 0); | 
|  | REPORTER_ASSERT(reporter, tangent.fX == -SK_Scalar1); | 
|  | REPORTER_ASSERT(reporter, tangent.fY == 0); | 
|  |  | 
|  | // Test degenerate paths | 
|  | path.reset(); | 
|  | path.moveTo(0, 0); | 
|  | path.lineTo(0, 0); | 
|  | path.lineTo(SK_Scalar1, 0); | 
|  | path.quadTo(SK_Scalar1, 0, SK_Scalar1, 0); | 
|  | path.quadTo(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1 * 2); | 
|  | path.cubicTo(SK_Scalar1, SK_Scalar1 * 2, | 
|  | SK_Scalar1, SK_Scalar1 * 2, | 
|  | SK_Scalar1, SK_Scalar1 * 2); | 
|  | path.cubicTo(SK_Scalar1*2, SK_Scalar1 * 2, | 
|  | SK_Scalar1*3, SK_Scalar1 * 2, | 
|  | SK_Scalar1*4, SK_Scalar1 * 2); | 
|  | meas.setPath(&path, false); | 
|  | length = meas.getLength(); | 
|  | REPORTER_ASSERT(reporter, length == SK_Scalar1 * 6); | 
|  | REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent)); | 
|  | REPORTER_ASSERT(reporter, | 
|  | SkScalarNearlyEqual(position.fX, | 
|  | SK_ScalarHalf, | 
|  | 0.0001f)); | 
|  | REPORTER_ASSERT(reporter, position.fY == 0); | 
|  | REPORTER_ASSERT(reporter, tangent.fX == SK_Scalar1); | 
|  | REPORTER_ASSERT(reporter, tangent.fY == 0); | 
|  | REPORTER_ASSERT(reporter, meas.getPosTan(2.5f, &position, &tangent)); | 
|  | REPORTER_ASSERT(reporter, | 
|  | SkScalarNearlyEqual(position.fX, SK_Scalar1, 0.0001f)); | 
|  | REPORTER_ASSERT(reporter, | 
|  | SkScalarNearlyEqual(position.fY, 1.5f)); | 
|  | REPORTER_ASSERT(reporter, tangent.fX == 0); | 
|  | REPORTER_ASSERT(reporter, tangent.fY == SK_Scalar1); | 
|  | REPORTER_ASSERT(reporter, meas.getPosTan(4.5f, &position, &tangent)); | 
|  | REPORTER_ASSERT(reporter, | 
|  | SkScalarNearlyEqual(position.fX, | 
|  | 2.5f, | 
|  | 0.0001f)); | 
|  | REPORTER_ASSERT(reporter, | 
|  | SkScalarNearlyEqual(position.fY, | 
|  | 2.0f, | 
|  | 0.0001f)); | 
|  | REPORTER_ASSERT(reporter, tangent.fX == SK_Scalar1); | 
|  | REPORTER_ASSERT(reporter, tangent.fY == 0); | 
|  |  | 
|  | path.reset(); | 
|  | path.moveTo(0, 0); | 
|  | path.lineTo(SK_Scalar1, 0); | 
|  | path.moveTo(SK_Scalar1, SK_Scalar1); | 
|  | path.moveTo(SK_Scalar1 * 2, SK_Scalar1 * 2); | 
|  | path.lineTo(SK_Scalar1, SK_Scalar1 * 2); | 
|  | meas.setPath(&path, false); | 
|  | length = meas.getLength(); | 
|  | REPORTER_ASSERT(reporter, length == SK_Scalar1); | 
|  | REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent)); | 
|  | REPORTER_ASSERT(reporter, | 
|  | SkScalarNearlyEqual(position.fX, | 
|  | SK_ScalarHalf, | 
|  | 0.0001f)); | 
|  | REPORTER_ASSERT(reporter, position.fY == 0); | 
|  | REPORTER_ASSERT(reporter, tangent.fX == SK_Scalar1); | 
|  | REPORTER_ASSERT(reporter, tangent.fY == 0); | 
|  | meas.nextContour(); | 
|  | length = meas.getLength(); | 
|  | REPORTER_ASSERT(reporter, length == SK_Scalar1); | 
|  | REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent)); | 
|  | REPORTER_ASSERT(reporter, | 
|  | SkScalarNearlyEqual(position.fX, | 
|  | 1.5f, | 
|  | 0.0001f)); | 
|  | REPORTER_ASSERT(reporter, | 
|  | SkScalarNearlyEqual(position.fY, | 
|  | 2.0f, | 
|  | 0.0001f)); | 
|  | REPORTER_ASSERT(reporter, tangent.fX == -SK_Scalar1); | 
|  | REPORTER_ASSERT(reporter, tangent.fY == 0); | 
|  |  | 
|  | test_small_segment(); | 
|  | test_small_segment2(); | 
|  | test_small_segment3(); | 
|  | } | 
|  |  | 
|  | DEF_TEST(PathMeasureConic, reporter) { | 
|  | SkPoint stdP, hiP, pts[] = {{0,0}, {100,0}, {100,0}}; | 
|  | SkPath p; | 
|  | p.moveTo(0, 0); | 
|  | p.conicTo(pts[1], pts[2], 1); | 
|  | SkPathMeasure stdm(p, false); | 
|  | REPORTER_ASSERT(reporter, stdm.getPosTan(20, &stdP, nullptr)); | 
|  | p.reset(); | 
|  | p.moveTo(0, 0); | 
|  | p.conicTo(pts[1], pts[2], 10); | 
|  | stdm.setPath(&p, false); | 
|  | REPORTER_ASSERT(reporter, stdm.getPosTan(20, &hiP, nullptr)); | 
|  | REPORTER_ASSERT(reporter, 19.5f < stdP.fX && stdP.fX < 20.5f); | 
|  | REPORTER_ASSERT(reporter, 19.5f < hiP.fX && hiP.fX < 20.5f); | 
|  | } | 
|  |  | 
|  | // Regression test for b/26425223 | 
|  | DEF_TEST(PathMeasure_nextctr, reporter) { | 
|  | SkPath path; | 
|  | path.moveTo(0, 0); path.lineTo(100, 0); | 
|  |  | 
|  | SkPathMeasure meas(path, false); | 
|  | // only expect 1 contour, even if we didn't explicitly call getLength() ourselves | 
|  | REPORTER_ASSERT(reporter, !meas.nextContour()); | 
|  | } | 
|  |  | 
|  | #include "include/core/SkContourMeasure.h" | 
|  |  | 
|  | static void test_90_degrees(sk_sp<SkContourMeasure> cm, SkScalar radius, | 
|  | skiatest::Reporter* reporter) { | 
|  | SkPoint pos; | 
|  | SkVector tan; | 
|  | SkScalar distance = cm->length() / 4; | 
|  | bool success = cm->getPosTan(distance, &pos, &tan); | 
|  |  | 
|  | REPORTER_ASSERT(reporter, success); | 
|  | REPORTER_ASSERT(reporter, SkScalarNearlyEqual(pos.fX, 0)); | 
|  | REPORTER_ASSERT(reporter, SkScalarNearlyEqual(pos.fY, radius)); | 
|  | REPORTER_ASSERT(reporter, SkScalarNearlyEqual(tan.fX, -1)); | 
|  | REPORTER_ASSERT(reporter, SkScalarNearlyEqual(tan.fY, 0)); | 
|  | } | 
|  |  | 
|  | static void test_empty_contours(skiatest::Reporter* reporter) { | 
|  | SkPath path; | 
|  |  | 
|  | path.moveTo(0, 0).lineTo(100, 100).lineTo(200, 100); | 
|  | path.moveTo(2, 2).moveTo(3, 3);                 // zero-length(s) | 
|  | path.moveTo(4, 4).close().close().close();      // zero-length | 
|  | path.moveTo(5, 5).lineTo(5, 5);                 // zero-length | 
|  | path.moveTo(5, 5).lineTo(5, 5).close();         // zero-length | 
|  | path.moveTo(5, 5).lineTo(5, 5).close().close(); // zero-length | 
|  | path.moveTo(6, 6).lineTo(7, 7); | 
|  | path.moveTo(10, 10);                            // zero-length | 
|  |  | 
|  | SkContourMeasureIter fact(path, false); | 
|  |  | 
|  | // given the above construction, we expect only 2 contours (the rest are "empty") | 
|  |  | 
|  | REPORTER_ASSERT(reporter, fact.next()); | 
|  | REPORTER_ASSERT(reporter, fact.next()); | 
|  | REPORTER_ASSERT(reporter, !fact.next()); | 
|  | } | 
|  |  | 
|  | static void test_MLM_contours(skiatest::Reporter* reporter) { | 
|  | SkPath path; | 
|  |  | 
|  | // This odd sequence (with a trailing moveTo) used to return a 2nd contour, which is | 
|  | // wrong, since the contract for a measure is to only return non-zero length contours. | 
|  | path.moveTo(10, 10).lineTo(20, 20).moveTo(30, 30); | 
|  |  | 
|  | for (bool forceClosed : {false, true}) { | 
|  | SkContourMeasureIter fact(path, forceClosed); | 
|  | REPORTER_ASSERT(reporter, fact.next()); | 
|  | REPORTER_ASSERT(reporter, !fact.next()); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void test_shrink(skiatest::Reporter* reporter) { | 
|  | SkPath path; | 
|  | path.addRect({1, 2, 3, 4}); | 
|  | path.incReserve(100);   // give shrinkToFit() something to do | 
|  |  | 
|  | SkContourMeasureIter iter(path, false); | 
|  |  | 
|  | // shrinks the allocation, possibly relocating the underlying arrays. | 
|  | // The contouremasureiter needs to have safely copied path, to be unaffected by this | 
|  | // change to "path". | 
|  | SkPathPriv::ShrinkToFit(&path); | 
|  |  | 
|  | // Note, this failed (before the fix) on an ASAN build, which notices that we were | 
|  | // using an internal iterator of the passed-in path, not our copy. | 
|  | while (iter.next()) | 
|  | ; | 
|  | } | 
|  |  | 
|  | DEF_TEST(contour_measure, reporter) { | 
|  | SkPath path; | 
|  | path.addCircle(0, 0, 100); | 
|  | path.addCircle(0, 0, 10); | 
|  |  | 
|  | SkContourMeasureIter fact(path, false); | 
|  | path.reset();   // we should not need the path avert we created the factory | 
|  |  | 
|  | auto cm0 = fact.next(); | 
|  | auto cm1 = fact.next(); | 
|  |  | 
|  | REPORTER_ASSERT(reporter, cm0->isClosed()); | 
|  | REPORTER_ASSERT(reporter, SkScalarNearlyEqual(cm0->length(), 200 * SK_ScalarPI, 1.5f)); | 
|  |  | 
|  | test_90_degrees(cm0, 100, reporter); | 
|  |  | 
|  | REPORTER_ASSERT(reporter, cm1->isClosed()); | 
|  | REPORTER_ASSERT(reporter, SkScalarNearlyEqual(cm1->length(), 20 * SK_ScalarPI, 0.5f)); | 
|  |  | 
|  | test_90_degrees(cm1, 10, reporter); | 
|  |  | 
|  | auto cm2 = fact.next(); | 
|  | REPORTER_ASSERT(reporter, !cm2); | 
|  |  | 
|  | test_empty_contours(reporter); | 
|  | test_MLM_contours(reporter); | 
|  |  | 
|  | test_shrink(reporter); | 
|  | } |