[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:
parent
860e2cf104
commit
0022f5cf1b
@ -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);
|
||||
};
|
||||
|
1
resources/skottie/skottie-trimpath-fill.json
Normal file
1
resources/skottie/skottie-trimpath-fill.json
Normal file
File diff suppressed because one or more lines are too long
@ -13,48 +13,41 @@
|
||||
|
||||
namespace {
|
||||
|
||||
class Segmentator : public SkNoncopyable {
|
||||
public:
|
||||
Segmentator(const SkPath& src, SkPath* dst)
|
||||
: fMeasure(src, false)
|
||||
, fDst(dst) {}
|
||||
|
||||
void add(SkScalar start, SkScalar stop) {
|
||||
// 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);
|
||||
|
||||
// TODO: we appear to skip zero-length contours.
|
||||
SkPathMeasure measure(src, false);
|
||||
|
||||
SkScalar current_segment_offset = 0;
|
||||
size_t contour_count = 1;
|
||||
|
||||
do {
|
||||
const auto nextOffset = fCurrentSegmentOffset + fMeasure.getLength();
|
||||
const auto next_offset = current_segment_offset + measure.getLength();
|
||||
|
||||
if (start < nextOffset) {
|
||||
fMeasure.getSegment(start - fCurrentSegmentOffset,
|
||||
stop - fCurrentSegmentOffset,
|
||||
fDst, true);
|
||||
if (start < next_offset) {
|
||||
measure.getSegment(start - current_segment_offset,
|
||||
stop - current_segment_offset,
|
||||
dst, requires_moveto);
|
||||
|
||||
if (stop < nextOffset)
|
||||
if (stop <= next_offset)
|
||||
break;
|
||||
}
|
||||
|
||||
fCurrentSegmentOffset = nextOffset;
|
||||
} while (fMeasure.nextContour());
|
||||
contour_count++;
|
||||
current_segment_offset = next_offset;
|
||||
} while (measure.nextContour());
|
||||
|
||||
return contour_count;
|
||||
}
|
||||
|
||||
private:
|
||||
SkPathMeasure fMeasure;
|
||||
SkPath* fDst;
|
||||
|
||||
SkScalar fCurrentSegmentOffset = 0;
|
||||
|
||||
using INHERITED = SkNoncopyable;
|
||||
};
|
||||
|
||||
} // 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;
|
||||
|
Loading…
Reference in New Issue
Block a user