1 /*
2 * Copyright 2018 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "include/core/SkPathMeasure.h"
9 #include "include/effects/SkTrimPathEffect.h"
10 #include "include/private/SkTPin.h"
11 #include "src/core/SkReadBuffer.h"
12 #include "src/core/SkWriteBuffer.h"
13 #include "src/effects/SkTrimPE.h"
14
15 namespace {
16
17 // Returns the number of contours iterated to satisfy the request.
add_segments(const SkPath & src,SkScalar start,SkScalar stop,SkPath * dst,bool requires_moveto=true)18 static size_t add_segments(const SkPath& src, SkScalar start, SkScalar stop, SkPath* dst,
19 bool requires_moveto = true) {
20 SkASSERT(start < stop);
21
22 SkPathMeasure measure(src, false);
23
24 SkScalar current_segment_offset = 0;
25 size_t contour_count = 1;
26
27 do {
28 const auto next_offset = current_segment_offset + measure.getLength();
29
30 if (start < next_offset) {
31 measure.getSegment(start - current_segment_offset,
32 stop - current_segment_offset,
33 dst, requires_moveto);
34
35 if (stop <= next_offset)
36 break;
37 }
38
39 contour_count++;
40 current_segment_offset = next_offset;
41 } while (measure.nextContour());
42
43 return contour_count;
44 }
45
46 } // namespace
47
SkTrimPE(SkScalar startT,SkScalar stopT,SkTrimPathEffect::Mode mode)48 SkTrimPE::SkTrimPE(SkScalar startT, SkScalar stopT, SkTrimPathEffect::Mode mode)
49 : fStartT(startT), fStopT(stopT), fMode(mode) {}
50
onFilterPath(SkPath * dst,const SkPath & src,SkStrokeRec *,const SkRect *,const SkMatrix &) const51 bool SkTrimPE::onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*,
52 const SkMatrix&) const {
53 if (fStartT >= fStopT) {
54 SkASSERT(fMode == SkTrimPathEffect::Mode::kNormal);
55 return true;
56 }
57
58 // First pass: compute the total len.
59 SkScalar len = 0;
60 SkPathMeasure meas(src, false);
61 do {
62 len += meas.getLength();
63 } while (meas.nextContour());
64
65 const auto arcStart = len * fStartT,
66 arcStop = len * fStopT;
67
68 // Second pass: actually add segments.
69 if (fMode == SkTrimPathEffect::Mode::kNormal) {
70 // Normal mode -> one span.
71 if (arcStart < arcStop) {
72 add_segments(src, arcStart, arcStop, dst);
73 }
74 } else {
75 // Inverted mode -> one logical span which wraps around at the end -> two actual spans.
76 // In order to preserve closed path continuity:
77 //
78 // 1) add the second/tail span first
79 //
80 // 2) skip the head span move-to for single-closed-contour paths
81
82 bool requires_moveto = true;
83 if (arcStop < len) {
84 // since we're adding the "tail" first, this is the total number of contours
85 const auto contour_count = add_segments(src, arcStop, len, dst);
86
87 // if the path consists of a single closed contour, we don't want to disconnect
88 // the two parts with a moveto.
89 if (contour_count == 1 && src.isLastContourClosed()) {
90 requires_moveto = false;
91 }
92 }
93 if (0 < arcStart) {
94 add_segments(src, 0, arcStart, dst, requires_moveto);
95 }
96 }
97
98 return true;
99 }
100
flatten(SkWriteBuffer & buffer) const101 void SkTrimPE::flatten(SkWriteBuffer& buffer) const {
102 buffer.writeScalar(fStartT);
103 buffer.writeScalar(fStopT);
104 buffer.writeUInt(static_cast<uint32_t>(fMode));
105 }
106
CreateProc(SkReadBuffer & buffer)107 sk_sp<SkFlattenable> SkTrimPE::CreateProc(SkReadBuffer& buffer) {
108 const auto start = buffer.readScalar(),
109 stop = buffer.readScalar();
110 const auto mode = buffer.readUInt();
111
112 return SkTrimPathEffect::Make(start, stop,
113 (mode & 1) ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal);
114 }
115
116 //////////////////////////////////////////////////////////////////////////////////////////////////
117
Make(SkScalar startT,SkScalar stopT,Mode mode)118 sk_sp<SkPathEffect> SkTrimPathEffect::Make(SkScalar startT, SkScalar stopT, Mode mode) {
119 if (!SkScalarsAreFinite(startT, stopT)) {
120 return nullptr;
121 }
122
123 if (startT <= 0 && stopT >= 1 && mode == Mode::kNormal) {
124 return nullptr;
125 }
126
127 startT = SkTPin(startT, 0.f, 1.f);
128 stopT = SkTPin(stopT, 0.f, 1.f);
129
130 if (startT >= stopT && mode == Mode::kInverted) {
131 return nullptr;
132 }
133
134 return sk_sp<SkPathEffect>(new SkTrimPE(startT, stopT, mode));
135 }
136