[SkTrimPathEffect] Preserve wrap-around continuity

In inverted mode (Mode::kInverted), the trim result represents the
logical segment [stop..start] (wrapping around at the path's end).

We currently emit two segments [0..start] and [stop..1], in that
exact order.  This behavior breaks continuity for single closed
contour paths.

Update SkTrimPath to

1) emit the segments in the correct order ([stop..1],[0..start])

2) skip the connecting moveTo for closed paths

Bug: skia:10107
Change-Id: Icd280554ba7291c985f504793feff104df2a4a99
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/281882
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Florin Malita <fmalita@chromium.org>
This commit is contained in:
Florin Malita 2020-04-07 08:56:47 -04:00 committed by Skia Commit-Bot
parent 860e2cf104
commit 0022f5cf1b
3 changed files with 49 additions and 34 deletions

View File

@ -33,7 +33,7 @@ public:
*
* Note: for Mode::kNormal, this will return one (logical) segment (even if it is spread
* across multiple contours). For Mode::kInverted, this will return 2 logical
* segments: 0...stopT and startT...1
* segments: stopT..1 and 0...startT, in this order.
*/
static sk_sp<SkPathEffect> Make(SkScalar startT, SkScalar stopT, Mode = Mode::kNormal);
};

File diff suppressed because one or more lines are too long

View File

@ -13,48 +13,41 @@
namespace {
class Segmentator : public SkNoncopyable {
public:
Segmentator(const SkPath& src, SkPath* dst)
: fMeasure(src, false)
, fDst(dst) {}
// Returns the number of contours iterated to satisfy the request.
static size_t add_segments(const SkPath& src, SkScalar start, SkScalar stop, SkPath* dst,
bool requires_moveto = true) {
SkASSERT(start < stop);
void add(SkScalar start, SkScalar stop) {
SkASSERT(start < stop);
SkPathMeasure measure(src, false);
// TODO: we appear to skip zero-length contours.
do {
const auto nextOffset = fCurrentSegmentOffset + fMeasure.getLength();
SkScalar current_segment_offset = 0;
size_t contour_count = 1;
if (start < nextOffset) {
fMeasure.getSegment(start - fCurrentSegmentOffset,
stop - fCurrentSegmentOffset,
fDst, true);
do {
const auto next_offset = current_segment_offset + measure.getLength();
if (stop < nextOffset)
break;
}
if (start < next_offset) {
measure.getSegment(start - current_segment_offset,
stop - current_segment_offset,
dst, requires_moveto);
fCurrentSegmentOffset = nextOffset;
} while (fMeasure.nextContour());
}
if (stop <= next_offset)
break;
}
private:
SkPathMeasure fMeasure;
SkPath* fDst;
contour_count++;
current_segment_offset = next_offset;
} while (measure.nextContour());
SkScalar fCurrentSegmentOffset = 0;
using INHERITED = SkNoncopyable;
};
return contour_count;
}
} // namespace
SkTrimPE::SkTrimPE(SkScalar startT, SkScalar stopT, SkTrimPathEffect::Mode mode)
: fStartT(startT), fStopT(stopT), fMode(mode) {}
bool SkTrimPE::onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec,
const SkRect* cullRect) const {
bool SkTrimPE::onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*) const {
if (fStartT >= fStopT) {
SkASSERT(fMode == SkTrimPathEffect::Mode::kNormal);
return true;
@ -71,12 +64,33 @@ bool SkTrimPE::onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec,
arcStop = len * fStopT;
// Second pass: actually add segments.
Segmentator segmentator(src, dst);
if (fMode == SkTrimPathEffect::Mode::kNormal) {
if (arcStart < arcStop) segmentator.add(arcStart, arcStop);
// Normal mode -> one span.
if (arcStart < arcStop) {
add_segments(src, arcStart, arcStop, dst);
}
} else {
if (0 < arcStart) segmentator.add(0, arcStart);
if (arcStop < len) segmentator.add(arcStop, len);
// Inverted mode -> one logical span which wraps around at the end -> two actual spans.
// In order to preserve closed path continuity:
//
// 1) add the second/tail span first
//
// 2) skip the head span move-to for single-closed-contour paths
bool requires_moveto = true;
if (arcStop < len) {
// since we're adding the "tail" first, this is the total number of contours
const auto contour_count = add_segments(src, arcStop, len, dst);
// if the path consists of a single closed contour, we don't want to disconnect
// the two parts with a moveto.
if (contour_count == 1 && src.isLastContourClosed()) {
requires_moveto = false;
}
}
if (0 < arcStart) {
add_segments(src, 0, arcStart, dst, requires_moveto);
}
}
return true;