f1f1e7dd36
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>
330 lines
11 KiB
C++
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);
|
|
}
|