xref: /aosp_15_r20/external/skia/src/effects/SkTrimPathEffect.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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