skia2/tests/PathMeasureTest.cpp
Mike Reed f1f1e7dd36 Hide shrinkToFit -- not needed now that we have pathbuilder
Step one is to make it private -- only skottie needs it at the moment
Stpe two is to modify pathops to use builders, and then we can likely
remove it shrinkToFit entirely (since builder.snapshot() is already snug).

bug: skia:9000

Change-Id: I9126bcb6fc2094fbeede2acb1f211b0ab771feba
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/327341
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Mike Reed <reed@google.com>
2020-10-15 19:47:06 +00:00

330 lines
11 KiB
C++

/*
* 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);
}