c924a88e33
When we shrink a path, we might relocate its underlying arrays. Doing so would invalidate any outstanding Iterators. The caller must handle this for its path object, but there may be copies elsewhere, which have just ref'd the underlying arrays. To keep these copys' iterators alive, we defensively "copy-on-write", so as to not relocate their buffers. Incidentally, update SkContourMeasureIter's constructor to clarify that it is iterating through its copy of the path, and not the original. Change-Id: I5c9331ab36ac8e156218532478f6d7105fd97cdc Reviewed-on: https://skia-review.googlesource.com/c/skia/+/326438 Reviewed-by: Florin Malita <fmalita@chromium.org> Commit-Queue: Mike Reed <reed@google.com>
329 lines
11 KiB
C++
329 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 "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".
|
|
path.shrinkToFit();
|
|
|
|
// 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);
|
|
}
|