Add verb measurement utils to SkContourMeasure
Change-Id: Ia0c5bfeecb462d079179516bc050004ceb8679ea
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/913916
Commit-Queue: Florin Malita <fmalita@google.com>
Reviewed-by: Daniel Dilan <danieldilan@google.com>
diff --git a/include/core/SkContourMeasure.h b/include/core/SkContourMeasure.h
index 29e33d8..eb4804b 100644
--- a/include/core/SkContourMeasure.h
+++ b/include/core/SkContourMeasure.h
@@ -11,6 +11,7 @@
#include "include/core/SkPoint.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
+#include "include/core/SkSpan.h"
#include "include/private/base/SkAPI.h"
#include "include/private/base/SkTDArray.h"
@@ -18,6 +19,7 @@
class SkMatrix;
class SkPath;
+enum class SkPathVerb;
class SK_API SkContourMeasure : public SkRefCnt {
public:
@@ -57,6 +59,47 @@
*/
bool isClosed() const { return fIsClosed; }
+ /** Measurement data for individual verbs.
+ */
+ struct VerbMeasure {
+ SkScalar fDistance; // Cumulative distance along the current contour.
+ SkPathVerb fVerb; // Verb type.
+ SkSpan<const SkPoint> fPts; // Verb points.
+ };
+
+private:
+ struct Segment;
+
+public:
+ /** Utility for iterating over the current contour verbs:
+ *
+ * for (const auto verb_measure : contour_measure) {
+ * ...
+ * }
+ */
+ class VerbIterator final {
+ public:
+ VerbIterator(const Segment* seg, SkSpan<const SkPoint> pts) : fSegment(seg), fPts(pts) {}
+
+ VerbMeasure operator*() const;
+
+ VerbIterator& operator++() { fSegment = Segment::Next(fSegment); return *this; }
+
+ bool operator==(const VerbIterator& other) { return fSegment == other.fSegment; }
+ bool operator!=(const VerbIterator& other) { return fSegment != other.fSegment; }
+
+ private:
+ const SkContourMeasure::Segment* fSegment;
+ const SkSpan<const SkPoint> fPts;
+ };
+
+ VerbIterator begin() const {
+ return VerbIterator(fSegments.begin(), SkSpan(fPts.data(), fPts.size()));
+ }
+ VerbIterator end() const {
+ return VerbIterator(fSegments.end(), SkSpan(fPts.data(), fPts.size()));
+ }
+
private:
struct Segment {
SkScalar fDistance; // total distance up to this point
diff --git a/src/core/SkContourMeasure.cpp b/src/core/SkContourMeasure.cpp
index 8c88119..41aae33 100644
--- a/src/core/SkContourMeasure.cpp
+++ b/src/core/SkContourMeasure.cpp
@@ -13,11 +13,14 @@
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkDebug.h"
#include "include/private/base/SkFloatingPoint.h"
+#include "include/private/base/SkTo.h"
#include "src/core/SkGeometry.h"
#include "src/core/SkPathMeasurePriv.h"
#include "src/core/SkPathPriv.h"
#include <algorithm>
+#include <array>
+#include <cstddef>
#include <utility>
#define kMaxTValue 0x3FFFFFFF
@@ -698,3 +701,32 @@
return true;
}
+
+SkContourMeasure::VerbMeasure SkContourMeasure::VerbIterator::operator*() const {
+ static constexpr size_t seg_pt_count[] = {
+ 2, // kLine (current_pt, 1 line pt)
+ 3, // kQuad (current_pt, 2 quad pts)
+ 4, // kCubic (current_pt, 3 cubic pts)
+ 4, // kConic (current_pt, {weight, 0}, 2 conic pts)
+ };
+ static constexpr SkPathVerb seg_verb[] = {
+ SkPathVerb::kLine,
+ SkPathVerb::kQuad,
+ SkPathVerb::kCubic,
+ SkPathVerb::kConic,
+ };
+ static_assert(std::size(seg_pt_count) == std::size(seg_verb));
+ static_assert(static_cast<size_t>(kLine_SegType) < std::size(seg_pt_count));
+ static_assert(static_cast<size_t>(kQuad_SegType) < std::size(seg_pt_count));
+ static_assert(static_cast<size_t>(kCubic_SegType) < std::size(seg_pt_count));
+ static_assert(static_cast<size_t>(kConic_SegType) < std::size(seg_pt_count));
+
+ SkASSERT(SkToSizeT(fSegment->fType) < std::size(seg_pt_count));
+ SkASSERT(fSegment->fPtIndex + seg_pt_count[fSegment->fType] <= fPts.size());
+
+ return {
+ fSegment->fDistance,
+ seg_verb[fSegment->fType],
+ SkSpan(fPts.data() + fSegment->fPtIndex, seg_pt_count[fSegment->fType]),
+ };
+}
diff --git a/tests/PathMeasureTest.cpp b/tests/PathMeasureTest.cpp
index 6d730f6..c7e2506 100644
--- a/tests/PathMeasureTest.cpp
+++ b/tests/PathMeasureTest.cpp
@@ -8,6 +8,7 @@
#include "include/core/SkContourMeasure.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathMeasure.h"
+#include "include/core/SkPathTypes.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
@@ -356,3 +357,93 @@
test_shrink(reporter);
}
+
+DEF_TEST(contour_measure_verbs, reporter) {
+ SkPath path;
+ path.moveTo(10, 10);
+ path.lineTo(10, 30);
+ path.lineTo(30, 30);
+ path.quadTo({40, 30}, {40, 40});
+ path.cubicTo({50, 40}, {50, 50}, {40, 50});
+ path.conicTo({50, 50}, {50, 60}, 1.2f);
+
+ SkContourMeasureIter measure(path, false);
+
+ sk_sp<SkContourMeasure> cmeasure = measure.next();
+ REPORTER_ASSERT(reporter, cmeasure);
+
+ SkContourMeasure::VerbIterator viter = cmeasure->begin();
+ {
+ REPORTER_ASSERT(reporter, viter != cmeasure->end());
+ const auto vmeasure = *viter;
+ REPORTER_ASSERT(reporter, vmeasure.fVerb == SkPathVerb::kLine);
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(vmeasure.fDistance, 20));
+ REPORTER_ASSERT(reporter, vmeasure.fPts.size() == 2);
+ REPORTER_ASSERT(reporter, vmeasure.fPts[0] == SkPoint::Make(10, 10));
+ REPORTER_ASSERT(reporter, vmeasure.fPts[1] == SkPoint::Make(10, 30));
+ }
+
+ ++viter;
+ {
+ REPORTER_ASSERT(reporter, viter != cmeasure->end());
+ const auto vmeasure = *viter;
+ REPORTER_ASSERT(reporter, vmeasure.fVerb == SkPathVerb::kLine);
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(vmeasure.fDistance, 40));
+ REPORTER_ASSERT(reporter, vmeasure.fPts.size() == 2);
+ REPORTER_ASSERT(reporter, vmeasure.fPts[0] == SkPoint::Make(10, 30));
+ REPORTER_ASSERT(reporter, vmeasure.fPts[1] == SkPoint::Make(30, 30));
+ }
+
+ ++viter;
+ {
+ REPORTER_ASSERT(reporter, viter != cmeasure->end());
+ const auto vmeasure = *viter;
+ REPORTER_ASSERT(reporter, vmeasure.fVerb == SkPathVerb::kQuad);
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(vmeasure.fDistance, 44.419418f));
+ REPORTER_ASSERT(reporter, vmeasure.fPts.size() == 3);
+ REPORTER_ASSERT(reporter, vmeasure.fPts[0] == SkPoint::Make(30, 30));
+ REPORTER_ASSERT(reporter, vmeasure.fPts[1] == SkPoint::Make(40, 30));
+ REPORTER_ASSERT(reporter, vmeasure.fPts[2] == SkPoint::Make(40, 40));
+ }
+
+ ++viter;
+ {
+ REPORTER_ASSERT(reporter, viter != cmeasure->end());
+ const auto vmeasure = *viter;
+ REPORTER_ASSERT(reporter, vmeasure.fVerb == SkPathVerb::kCubic);
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(vmeasure.fDistance, 59.436790f));
+ REPORTER_ASSERT(reporter, vmeasure.fPts.size() == 4);
+ REPORTER_ASSERT(reporter, vmeasure.fPts[0] == SkPoint::Make(40, 40));
+ REPORTER_ASSERT(reporter, vmeasure.fPts[1] == SkPoint::Make(50, 40));
+ REPORTER_ASSERT(reporter, vmeasure.fPts[2] == SkPoint::Make(50, 50));
+ REPORTER_ASSERT(reporter, vmeasure.fPts[3] == SkPoint::Make(40, 50));
+ }
+
+ ++viter;
+ {
+ REPORTER_ASSERT(reporter, viter != cmeasure->end());
+ const auto vmeasure = *viter;
+ REPORTER_ASSERT(reporter, vmeasure.fVerb == SkPathVerb::kConic);
+ REPORTER_ASSERT(reporter, SkScalarNearlyEqual(vmeasure.fDistance, 80.807449f));
+ REPORTER_ASSERT(reporter, vmeasure.fPts.size() == 4);
+ REPORTER_ASSERT(reporter, vmeasure.fPts[0] == SkPoint::Make(40, 50));
+ REPORTER_ASSERT(reporter, vmeasure.fPts[1] == SkPoint::Make(1.2f, 0));
+ REPORTER_ASSERT(reporter, vmeasure.fPts[2] == SkPoint::Make(50, 50));
+ REPORTER_ASSERT(reporter, vmeasure.fPts[3] == SkPoint::Make(50, 60));
+ }
+
+ ++viter;
+ {
+ REPORTER_ASSERT(reporter, viter == cmeasure->end());
+ }
+
+ // Exercise the range iterator form.
+ float current_distance = 0;
+ size_t verb_count = 0;
+ for (const auto vmeasure : *cmeasure) {
+ REPORTER_ASSERT(reporter, vmeasure.fDistance > current_distance);
+ current_distance = vmeasure.fDistance;
+ verb_count++;
+ }
+ REPORTER_ASSERT(reporter, verb_count == 5);
+}