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 *) const51 bool SkTrimPE::onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*) const {
52 if (fStartT >= fStopT) {
53 SkASSERT(fMode == SkTrimPathEffect::Mode::kNormal);
54 return true;
55 }
56
57 // First pass: compute the total len.
58 SkScalar len = 0;
59 SkPathMeasure meas(src, false);
60 do {
61 len += meas.getLength();
62 } while (meas.nextContour());
63
64 const auto arcStart = len * fStartT,
65 arcStop = len * fStopT;
66
67 // Second pass: actually add segments.
68 if (fMode == SkTrimPathEffect::Mode::kNormal) {
69 // Normal mode -> one span.
70 if (arcStart < arcStop) {
71 add_segments(src, arcStart, arcStop, dst);
72 }
73 } else {
74 // Inverted mode -> one logical span which wraps around at the end -> two actual spans.
75 // In order to preserve closed path continuity:
76 //
77 // 1) add the second/tail span first
78 //
79 // 2) skip the head span move-to for single-closed-contour paths
80
81 bool requires_moveto = true;
82 if (arcStop < len) {
83 // since we're adding the "tail" first, this is the total number of contours
84 const auto contour_count = add_segments(src, arcStop, len, dst);
85
86 // if the path consists of a single closed contour, we don't want to disconnect
87 // the two parts with a moveto.
88 if (contour_count == 1 && src.isLastContourClosed()) {
89 requires_moveto = false;
90 }
91 }
92 if (0 < arcStart) {
93 add_segments(src, 0, arcStart, dst, requires_moveto);
94 }
95 }
96
97 return true;
98 }
99
flatten(SkWriteBuffer & buffer) const100 void SkTrimPE::flatten(SkWriteBuffer& buffer) const {
101 buffer.writeScalar(fStartT);
102 buffer.writeScalar(fStopT);
103 buffer.writeUInt(static_cast<uint32_t>(fMode));
104 }
105
CreateProc(SkReadBuffer & buffer)106 sk_sp<SkFlattenable> SkTrimPE::CreateProc(SkReadBuffer& buffer) {
107 const auto start = buffer.readScalar(),
108 stop = buffer.readScalar();
109 const auto mode = buffer.readUInt();
110
111 return SkTrimPathEffect::Make(start, stop,
112 (mode & 1) ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal);
113 }
114
115 //////////////////////////////////////////////////////////////////////////////////////////////////
116
Make(SkScalar startT,SkScalar stopT,Mode mode)117 sk_sp<SkPathEffect> SkTrimPathEffect::Make(SkScalar startT, SkScalar stopT, Mode mode) {
118 if (!SkScalarsAreFinite(startT, stopT)) {
119 return nullptr;
120 }
121
122 if (startT <= 0 && stopT >= 1 && mode == Mode::kNormal) {
123 return nullptr;
124 }
125
126 startT = SkTPin(startT, 0.f, 1.f);
127 stopT = SkTPin(stopT, 0.f, 1.f);
128
129 if (startT >= stopT && mode == Mode::kInverted) {
130 return nullptr;
131 }
132
133 return sk_sp<SkPathEffect>(new SkTrimPE(startT, stopT, mode));
134 }
135