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