xref: /aosp_15_r20/external/skia/tools/viewer/SimpleStrokerSlide.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2020 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkBitmap.h"
9*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkCanvas.h"
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPath.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPathUtils.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/utils/SkParsePath.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "tools/viewer/ClickHandlerSlide.h"
14*c8dee2aaSAndroid Build Coastguard Worker 
15*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkGeometry.h"
16*c8dee2aaSAndroid Build Coastguard Worker 
17*c8dee2aaSAndroid Build Coastguard Worker #include <vector>
18*c8dee2aaSAndroid Build Coastguard Worker 
19*c8dee2aaSAndroid Build Coastguard Worker namespace {
20*c8dee2aaSAndroid Build Coastguard Worker 
21*c8dee2aaSAndroid Build Coastguard Worker //////////////////////////////////////////////////////////////////////////////
22*c8dee2aaSAndroid Build Coastguard Worker 
rotate90(const SkPoint & p)23*c8dee2aaSAndroid Build Coastguard Worker static SkPoint rotate90(const SkPoint& p) { return {p.fY, -p.fX}; }
rotate180(const SkPoint & p)24*c8dee2aaSAndroid Build Coastguard Worker static SkPoint rotate180(const SkPoint& p) { return p * -1; }
setLength(SkPoint p,float len)25*c8dee2aaSAndroid Build Coastguard Worker static SkPoint setLength(SkPoint p, float len) {
26*c8dee2aaSAndroid Build Coastguard Worker     if (!p.setLength(len)) {
27*c8dee2aaSAndroid Build Coastguard Worker         SkDebugf("Failed to set point length\n");
28*c8dee2aaSAndroid Build Coastguard Worker     }
29*c8dee2aaSAndroid Build Coastguard Worker     return p;
30*c8dee2aaSAndroid Build Coastguard Worker }
isClockwise(const SkPoint & a,const SkPoint & b)31*c8dee2aaSAndroid Build Coastguard Worker static bool isClockwise(const SkPoint& a, const SkPoint& b) { return a.cross(b) > 0; }
32*c8dee2aaSAndroid Build Coastguard Worker 
33*c8dee2aaSAndroid Build Coastguard Worker //////////////////////////////////////////////////////////////////////////////
34*c8dee2aaSAndroid Build Coastguard Worker // Testing ground for a new stroker implementation
35*c8dee2aaSAndroid Build Coastguard Worker 
36*c8dee2aaSAndroid Build Coastguard Worker /** Helper class for constructing paths, with undo support */
37*c8dee2aaSAndroid Build Coastguard Worker class PathRecorder {
38*c8dee2aaSAndroid Build Coastguard Worker public:
getPath() const39*c8dee2aaSAndroid Build Coastguard Worker     SkPath getPath() const {
40*c8dee2aaSAndroid Build Coastguard Worker         return SkPath::Make(fPoints.data(), fPoints.size(), fVerbs.data(), fVerbs.size(), nullptr,
41*c8dee2aaSAndroid Build Coastguard Worker                             0, SkPathFillType::kWinding);
42*c8dee2aaSAndroid Build Coastguard Worker     }
43*c8dee2aaSAndroid Build Coastguard Worker 
moveTo(SkPoint p)44*c8dee2aaSAndroid Build Coastguard Worker     void moveTo(SkPoint p) {
45*c8dee2aaSAndroid Build Coastguard Worker         fVerbs.push_back(SkPath::kMove_Verb);
46*c8dee2aaSAndroid Build Coastguard Worker         fPoints.push_back(p);
47*c8dee2aaSAndroid Build Coastguard Worker     }
48*c8dee2aaSAndroid Build Coastguard Worker 
lineTo(SkPoint p)49*c8dee2aaSAndroid Build Coastguard Worker     void lineTo(SkPoint p) {
50*c8dee2aaSAndroid Build Coastguard Worker         fVerbs.push_back(SkPath::kLine_Verb);
51*c8dee2aaSAndroid Build Coastguard Worker         fPoints.push_back(p);
52*c8dee2aaSAndroid Build Coastguard Worker     }
53*c8dee2aaSAndroid Build Coastguard Worker 
close()54*c8dee2aaSAndroid Build Coastguard Worker     void close() { fVerbs.push_back(SkPath::kClose_Verb); }
55*c8dee2aaSAndroid Build Coastguard Worker 
rewind()56*c8dee2aaSAndroid Build Coastguard Worker     void rewind() {
57*c8dee2aaSAndroid Build Coastguard Worker         fVerbs.clear();
58*c8dee2aaSAndroid Build Coastguard Worker         fPoints.clear();
59*c8dee2aaSAndroid Build Coastguard Worker     }
60*c8dee2aaSAndroid Build Coastguard Worker 
countPoints() const61*c8dee2aaSAndroid Build Coastguard Worker     int countPoints() const { return fPoints.size(); }
62*c8dee2aaSAndroid Build Coastguard Worker 
countVerbs() const63*c8dee2aaSAndroid Build Coastguard Worker     int countVerbs() const { return fVerbs.size(); }
64*c8dee2aaSAndroid Build Coastguard Worker 
getLastPt(SkPoint * lastPt) const65*c8dee2aaSAndroid Build Coastguard Worker     bool getLastPt(SkPoint* lastPt) const {
66*c8dee2aaSAndroid Build Coastguard Worker         if (fPoints.empty()) {
67*c8dee2aaSAndroid Build Coastguard Worker             return false;
68*c8dee2aaSAndroid Build Coastguard Worker         }
69*c8dee2aaSAndroid Build Coastguard Worker         *lastPt = fPoints.back();
70*c8dee2aaSAndroid Build Coastguard Worker         return true;
71*c8dee2aaSAndroid Build Coastguard Worker     }
72*c8dee2aaSAndroid Build Coastguard Worker 
setLastPt(SkPoint lastPt)73*c8dee2aaSAndroid Build Coastguard Worker     void setLastPt(SkPoint lastPt) {
74*c8dee2aaSAndroid Build Coastguard Worker         if (fPoints.empty()) {
75*c8dee2aaSAndroid Build Coastguard Worker             moveTo(lastPt);
76*c8dee2aaSAndroid Build Coastguard Worker         } else {
77*c8dee2aaSAndroid Build Coastguard Worker             fPoints.back().set(lastPt.fX, lastPt.fY);
78*c8dee2aaSAndroid Build Coastguard Worker         }
79*c8dee2aaSAndroid Build Coastguard Worker     }
80*c8dee2aaSAndroid Build Coastguard Worker 
verbs() const81*c8dee2aaSAndroid Build Coastguard Worker     const std::vector<uint8_t>& verbs() const { return fVerbs; }
82*c8dee2aaSAndroid Build Coastguard Worker 
points() const83*c8dee2aaSAndroid Build Coastguard Worker     const std::vector<SkPoint>& points() const { return fPoints; }
84*c8dee2aaSAndroid Build Coastguard Worker 
85*c8dee2aaSAndroid Build Coastguard Worker private:
86*c8dee2aaSAndroid Build Coastguard Worker     std::vector<uint8_t> fVerbs;
87*c8dee2aaSAndroid Build Coastguard Worker     std::vector<SkPoint> fPoints;
88*c8dee2aaSAndroid Build Coastguard Worker };
89*c8dee2aaSAndroid Build Coastguard Worker 
90*c8dee2aaSAndroid Build Coastguard Worker class SkPathStroker2 {
91*c8dee2aaSAndroid Build Coastguard Worker public:
92*c8dee2aaSAndroid Build Coastguard Worker     // Returns the fill path
93*c8dee2aaSAndroid Build Coastguard Worker     SkPath getFillPath(const SkPath& path, const SkPaint& paint);
94*c8dee2aaSAndroid Build Coastguard Worker 
95*c8dee2aaSAndroid Build Coastguard Worker private:
96*c8dee2aaSAndroid Build Coastguard Worker     struct PathSegment {
97*c8dee2aaSAndroid Build Coastguard Worker         SkPath::Verb fVerb;
98*c8dee2aaSAndroid Build Coastguard Worker         SkPoint fPoints[4];
99*c8dee2aaSAndroid Build Coastguard Worker     };
100*c8dee2aaSAndroid Build Coastguard Worker 
101*c8dee2aaSAndroid Build Coastguard Worker     float fRadius;
102*c8dee2aaSAndroid Build Coastguard Worker     SkPaint::Cap fCap;
103*c8dee2aaSAndroid Build Coastguard Worker     SkPaint::Join fJoin;
104*c8dee2aaSAndroid Build Coastguard Worker     PathRecorder fInner, fOuter;
105*c8dee2aaSAndroid Build Coastguard Worker 
106*c8dee2aaSAndroid Build Coastguard Worker     // Initialize stroker state
107*c8dee2aaSAndroid Build Coastguard Worker     void initForPath(const SkPath& path, const SkPaint& paint);
108*c8dee2aaSAndroid Build Coastguard Worker 
109*c8dee2aaSAndroid Build Coastguard Worker     // Strokes a line segment
110*c8dee2aaSAndroid Build Coastguard Worker     void strokeLine(const PathSegment& line, bool needsMove);
111*c8dee2aaSAndroid Build Coastguard Worker 
112*c8dee2aaSAndroid Build Coastguard Worker     // Adds an endcap to fOuter
113*c8dee2aaSAndroid Build Coastguard Worker     enum class CapLocation { Start, End };
114*c8dee2aaSAndroid Build Coastguard Worker     void endcap(CapLocation loc);
115*c8dee2aaSAndroid Build Coastguard Worker 
116*c8dee2aaSAndroid Build Coastguard Worker     // Adds a join between the two segments
117*c8dee2aaSAndroid Build Coastguard Worker     void join(const PathSegment& prev, const PathSegment& curr);
118*c8dee2aaSAndroid Build Coastguard Worker 
119*c8dee2aaSAndroid Build Coastguard Worker     // Appends path in reverse to result
120*c8dee2aaSAndroid Build Coastguard Worker     static void appendPathReversed(const PathRecorder& path, PathRecorder* result);
121*c8dee2aaSAndroid Build Coastguard Worker 
122*c8dee2aaSAndroid Build Coastguard Worker     // Returns the segment unit normal
123*c8dee2aaSAndroid Build Coastguard Worker     static SkPoint unitNormal(const PathSegment& seg, float t);
124*c8dee2aaSAndroid Build Coastguard Worker 
125*c8dee2aaSAndroid Build Coastguard Worker     // Returns squared magnitude of line segments.
126*c8dee2aaSAndroid Build Coastguard Worker     static float squaredLineLength(const PathSegment& lineSeg);
127*c8dee2aaSAndroid Build Coastguard Worker };
128*c8dee2aaSAndroid Build Coastguard Worker 
initForPath(const SkPath & path,const SkPaint & paint)129*c8dee2aaSAndroid Build Coastguard Worker void SkPathStroker2::initForPath(const SkPath& path, const SkPaint& paint) {
130*c8dee2aaSAndroid Build Coastguard Worker     fRadius = paint.getStrokeWidth() / 2;
131*c8dee2aaSAndroid Build Coastguard Worker     fCap = paint.getStrokeCap();
132*c8dee2aaSAndroid Build Coastguard Worker     fJoin = paint.getStrokeJoin();
133*c8dee2aaSAndroid Build Coastguard Worker     fInner.rewind();
134*c8dee2aaSAndroid Build Coastguard Worker     fOuter.rewind();
135*c8dee2aaSAndroid Build Coastguard Worker }
136*c8dee2aaSAndroid Build Coastguard Worker 
getFillPath(const SkPath & path,const SkPaint & paint)137*c8dee2aaSAndroid Build Coastguard Worker SkPath SkPathStroker2::getFillPath(const SkPath& path, const SkPaint& paint) {
138*c8dee2aaSAndroid Build Coastguard Worker     initForPath(path, paint);
139*c8dee2aaSAndroid Build Coastguard Worker 
140*c8dee2aaSAndroid Build Coastguard Worker     // Trace the inner and outer paths simultaneously. Inner will therefore be
141*c8dee2aaSAndroid Build Coastguard Worker     // recorded in reverse from how we trace the outline.
142*c8dee2aaSAndroid Build Coastguard Worker     SkPath::Iter it(path, false);
143*c8dee2aaSAndroid Build Coastguard Worker     PathSegment segment, prevSegment;
144*c8dee2aaSAndroid Build Coastguard Worker     bool firstSegment = true;
145*c8dee2aaSAndroid Build Coastguard Worker     while ((segment.fVerb = it.next(segment.fPoints)) != SkPath::kDone_Verb) {
146*c8dee2aaSAndroid Build Coastguard Worker         // Join to the previous segment
147*c8dee2aaSAndroid Build Coastguard Worker         if (!firstSegment) {
148*c8dee2aaSAndroid Build Coastguard Worker             join(prevSegment, segment);
149*c8dee2aaSAndroid Build Coastguard Worker         }
150*c8dee2aaSAndroid Build Coastguard Worker 
151*c8dee2aaSAndroid Build Coastguard Worker         // Stroke the current segment
152*c8dee2aaSAndroid Build Coastguard Worker         switch (segment.fVerb) {
153*c8dee2aaSAndroid Build Coastguard Worker             case SkPath::kLine_Verb:
154*c8dee2aaSAndroid Build Coastguard Worker                 strokeLine(segment, firstSegment);
155*c8dee2aaSAndroid Build Coastguard Worker                 break;
156*c8dee2aaSAndroid Build Coastguard Worker             case SkPath::kMove_Verb:
157*c8dee2aaSAndroid Build Coastguard Worker                 // Don't care about multiple contours currently
158*c8dee2aaSAndroid Build Coastguard Worker                 continue;
159*c8dee2aaSAndroid Build Coastguard Worker             default:
160*c8dee2aaSAndroid Build Coastguard Worker                 SkDebugf("Unhandled path verb %d\n", segment.fVerb);
161*c8dee2aaSAndroid Build Coastguard Worker                 break;
162*c8dee2aaSAndroid Build Coastguard Worker         }
163*c8dee2aaSAndroid Build Coastguard Worker 
164*c8dee2aaSAndroid Build Coastguard Worker         std::swap(segment, prevSegment);
165*c8dee2aaSAndroid Build Coastguard Worker         firstSegment = false;
166*c8dee2aaSAndroid Build Coastguard Worker     }
167*c8dee2aaSAndroid Build Coastguard Worker 
168*c8dee2aaSAndroid Build Coastguard Worker     // Open contour => endcap at the end
169*c8dee2aaSAndroid Build Coastguard Worker     const bool isClosed = path.isLastContourClosed();
170*c8dee2aaSAndroid Build Coastguard Worker     if (isClosed) {
171*c8dee2aaSAndroid Build Coastguard Worker         SkDebugf("Unhandled closed contour\n");
172*c8dee2aaSAndroid Build Coastguard Worker     } else {
173*c8dee2aaSAndroid Build Coastguard Worker         endcap(CapLocation::End);
174*c8dee2aaSAndroid Build Coastguard Worker     }
175*c8dee2aaSAndroid Build Coastguard Worker 
176*c8dee2aaSAndroid Build Coastguard Worker     // Walk inner path in reverse, appending to result
177*c8dee2aaSAndroid Build Coastguard Worker     appendPathReversed(fInner, &fOuter);
178*c8dee2aaSAndroid Build Coastguard Worker     endcap(CapLocation::Start);
179*c8dee2aaSAndroid Build Coastguard Worker 
180*c8dee2aaSAndroid Build Coastguard Worker     return fOuter.getPath();
181*c8dee2aaSAndroid Build Coastguard Worker }
182*c8dee2aaSAndroid Build Coastguard Worker 
strokeLine(const PathSegment & line,bool needsMove)183*c8dee2aaSAndroid Build Coastguard Worker void SkPathStroker2::strokeLine(const PathSegment& line, bool needsMove) {
184*c8dee2aaSAndroid Build Coastguard Worker     const SkPoint tangent = line.fPoints[1] - line.fPoints[0];
185*c8dee2aaSAndroid Build Coastguard Worker     const SkPoint normal = rotate90(tangent);
186*c8dee2aaSAndroid Build Coastguard Worker     const SkPoint offset = setLength(normal, fRadius);
187*c8dee2aaSAndroid Build Coastguard Worker     if (needsMove) {
188*c8dee2aaSAndroid Build Coastguard Worker         fOuter.moveTo(line.fPoints[0] + offset);
189*c8dee2aaSAndroid Build Coastguard Worker         fInner.moveTo(line.fPoints[0] - offset);
190*c8dee2aaSAndroid Build Coastguard Worker     }
191*c8dee2aaSAndroid Build Coastguard Worker     fOuter.lineTo(line.fPoints[1] + offset);
192*c8dee2aaSAndroid Build Coastguard Worker     fInner.lineTo(line.fPoints[1] - offset);
193*c8dee2aaSAndroid Build Coastguard Worker }
194*c8dee2aaSAndroid Build Coastguard Worker 
endcap(CapLocation loc)195*c8dee2aaSAndroid Build Coastguard Worker void SkPathStroker2::endcap(CapLocation loc) {
196*c8dee2aaSAndroid Build Coastguard Worker     const auto buttCap = [this](CapLocation loc) {
197*c8dee2aaSAndroid Build Coastguard Worker         if (loc == CapLocation::Start) {
198*c8dee2aaSAndroid Build Coastguard Worker             // Back at the start of the path: just close the stroked outline
199*c8dee2aaSAndroid Build Coastguard Worker             fOuter.close();
200*c8dee2aaSAndroid Build Coastguard Worker         } else {
201*c8dee2aaSAndroid Build Coastguard Worker             // Inner last pt == first pt when appending in reverse
202*c8dee2aaSAndroid Build Coastguard Worker             SkPoint innerLastPt;
203*c8dee2aaSAndroid Build Coastguard Worker             fInner.getLastPt(&innerLastPt);
204*c8dee2aaSAndroid Build Coastguard Worker             fOuter.lineTo(innerLastPt);
205*c8dee2aaSAndroid Build Coastguard Worker         }
206*c8dee2aaSAndroid Build Coastguard Worker     };
207*c8dee2aaSAndroid Build Coastguard Worker 
208*c8dee2aaSAndroid Build Coastguard Worker     switch (fCap) {
209*c8dee2aaSAndroid Build Coastguard Worker         case SkPaint::kButt_Cap:
210*c8dee2aaSAndroid Build Coastguard Worker             buttCap(loc);
211*c8dee2aaSAndroid Build Coastguard Worker             break;
212*c8dee2aaSAndroid Build Coastguard Worker         default:
213*c8dee2aaSAndroid Build Coastguard Worker             SkDebugf("Unhandled endcap %d\n", fCap);
214*c8dee2aaSAndroid Build Coastguard Worker             buttCap(loc);
215*c8dee2aaSAndroid Build Coastguard Worker             break;
216*c8dee2aaSAndroid Build Coastguard Worker     }
217*c8dee2aaSAndroid Build Coastguard Worker }
218*c8dee2aaSAndroid Build Coastguard Worker 
join(const PathSegment & prev,const PathSegment & curr)219*c8dee2aaSAndroid Build Coastguard Worker void SkPathStroker2::join(const PathSegment& prev, const PathSegment& curr) {
220*c8dee2aaSAndroid Build Coastguard Worker     const auto miterJoin = [this](const PathSegment& prev, const PathSegment& curr) {
221*c8dee2aaSAndroid Build Coastguard Worker         // Common path endpoint of the two segments is the midpoint of the miter line.
222*c8dee2aaSAndroid Build Coastguard Worker         const SkPoint miterMidpt = curr.fPoints[0];
223*c8dee2aaSAndroid Build Coastguard Worker 
224*c8dee2aaSAndroid Build Coastguard Worker         SkPoint before = unitNormal(prev, 1);
225*c8dee2aaSAndroid Build Coastguard Worker         SkPoint after = unitNormal(curr, 0);
226*c8dee2aaSAndroid Build Coastguard Worker 
227*c8dee2aaSAndroid Build Coastguard Worker         // Check who's inside and who's outside.
228*c8dee2aaSAndroid Build Coastguard Worker         PathRecorder *outer = &fOuter, *inner = &fInner;
229*c8dee2aaSAndroid Build Coastguard Worker         if (!isClockwise(before, after)) {
230*c8dee2aaSAndroid Build Coastguard Worker             std::swap(inner, outer);
231*c8dee2aaSAndroid Build Coastguard Worker             before = rotate180(before);
232*c8dee2aaSAndroid Build Coastguard Worker             after = rotate180(after);
233*c8dee2aaSAndroid Build Coastguard Worker         }
234*c8dee2aaSAndroid Build Coastguard Worker 
235*c8dee2aaSAndroid Build Coastguard Worker         const float cosTheta = before.dot(after);
236*c8dee2aaSAndroid Build Coastguard Worker         if (SkScalarNearlyZero(1 - cosTheta)) {
237*c8dee2aaSAndroid Build Coastguard Worker             // Nearly identical normals: don't bother.
238*c8dee2aaSAndroid Build Coastguard Worker             return;
239*c8dee2aaSAndroid Build Coastguard Worker         }
240*c8dee2aaSAndroid Build Coastguard Worker 
241*c8dee2aaSAndroid Build Coastguard Worker         // Before and after have the same origin and magnitude, so before+after is the diagonal of
242*c8dee2aaSAndroid Build Coastguard Worker         // their rhombus. Origin of this vector is the midpoint of the miter line.
243*c8dee2aaSAndroid Build Coastguard Worker         SkPoint miterVec = before + after;
244*c8dee2aaSAndroid Build Coastguard Worker 
245*c8dee2aaSAndroid Build Coastguard Worker         // Note the relationship (draw a right triangle with the miter line as its hypoteneuse):
246*c8dee2aaSAndroid Build Coastguard Worker         //     sin(theta/2) = strokeWidth / miterLength
247*c8dee2aaSAndroid Build Coastguard Worker         // so miterLength = strokeWidth / sin(theta/2)
248*c8dee2aaSAndroid Build Coastguard Worker         // where miterLength is the length of the miter from outer point to inner corner.
249*c8dee2aaSAndroid Build Coastguard Worker         // miterVec's origin is the midpoint of the miter line, so we use strokeWidth/2.
250*c8dee2aaSAndroid Build Coastguard Worker         // Sqrt is just an application of half-angle identities.
251*c8dee2aaSAndroid Build Coastguard Worker         const float sinHalfTheta = sqrtf(0.5 * (1 + cosTheta));
252*c8dee2aaSAndroid Build Coastguard Worker         const float halfMiterLength = fRadius / sinHalfTheta;
253*c8dee2aaSAndroid Build Coastguard Worker         miterVec.setLength(halfMiterLength);  // TODO: miter length limit
254*c8dee2aaSAndroid Build Coastguard Worker 
255*c8dee2aaSAndroid Build Coastguard Worker         // Outer: connect to the miter point, and then to t=0 (on outside stroke) of next segment.
256*c8dee2aaSAndroid Build Coastguard Worker         const SkPoint dest = setLength(after, fRadius);
257*c8dee2aaSAndroid Build Coastguard Worker         outer->lineTo(miterMidpt + miterVec);
258*c8dee2aaSAndroid Build Coastguard Worker         outer->lineTo(miterMidpt + dest);
259*c8dee2aaSAndroid Build Coastguard Worker 
260*c8dee2aaSAndroid Build Coastguard Worker         // Inner miter is more involved. We're already at t=1 (on inside stroke) of 'prev'.
261*c8dee2aaSAndroid Build Coastguard Worker         // Check 2 cases to see we can directly connect to the inner miter point
262*c8dee2aaSAndroid Build Coastguard Worker         // (midpoint - miterVec), or if we need to add extra "loop" geometry.
263*c8dee2aaSAndroid Build Coastguard Worker         const SkPoint prevUnitTangent = rotate90(before);
264*c8dee2aaSAndroid Build Coastguard Worker         const float radiusSquared = fRadius * fRadius;
265*c8dee2aaSAndroid Build Coastguard Worker         // 'alpha' is angle between prev tangent and the curr inwards normal
266*c8dee2aaSAndroid Build Coastguard Worker         const float cosAlpha = prevUnitTangent.dot(-after);
267*c8dee2aaSAndroid Build Coastguard Worker         // Solve triangle for len^2:  radius^2 = len^2 + (radius * sin(alpha))^2
268*c8dee2aaSAndroid Build Coastguard Worker         // This is the point at which the inside "corner" of curr at t=0 will lie on a
269*c8dee2aaSAndroid Build Coastguard Worker         // line connecting the inner and outer corners of prev at t=0. If len is below
270*c8dee2aaSAndroid Build Coastguard Worker         // this threshold, the inside corner of curr will "poke through" the start of prev,
271*c8dee2aaSAndroid Build Coastguard Worker         // and we'll need the inner loop geometry.
272*c8dee2aaSAndroid Build Coastguard Worker         const float threshold1 = radiusSquared * cosAlpha * cosAlpha;
273*c8dee2aaSAndroid Build Coastguard Worker         // Solve triangle for len^2:  halfMiterLen^2 = radius^2 + len^2
274*c8dee2aaSAndroid Build Coastguard Worker         // This is the point at which the inner miter point will lie on the inner stroke
275*c8dee2aaSAndroid Build Coastguard Worker         // boundary of the curr segment. If len is below this threshold, the miter point
276*c8dee2aaSAndroid Build Coastguard Worker         // moves 'inside' of the stroked outline, and we'll need the inner loop geometry.
277*c8dee2aaSAndroid Build Coastguard Worker         const float threshold2 = halfMiterLength * halfMiterLength - radiusSquared;
278*c8dee2aaSAndroid Build Coastguard Worker         // If a segment length is smaller than the larger of the two thresholds,
279*c8dee2aaSAndroid Build Coastguard Worker         // we'll have to add the inner loop geometry.
280*c8dee2aaSAndroid Build Coastguard Worker         const float maxLenSqd = std::max(threshold1, threshold2);
281*c8dee2aaSAndroid Build Coastguard Worker         const bool needsInnerLoop =
282*c8dee2aaSAndroid Build Coastguard Worker                 squaredLineLength(prev) < maxLenSqd || squaredLineLength(curr) < maxLenSqd;
283*c8dee2aaSAndroid Build Coastguard Worker         if (needsInnerLoop) {
284*c8dee2aaSAndroid Build Coastguard Worker             // Connect to the miter midpoint (common path endpoint of the two segments),
285*c8dee2aaSAndroid Build Coastguard Worker             // and then to t=0 (on inside) of the next segment. This adds an interior "loop" of
286*c8dee2aaSAndroid Build Coastguard Worker             // geometry that handles edge cases where segment lengths are shorter than the
287*c8dee2aaSAndroid Build Coastguard Worker             // stroke width.
288*c8dee2aaSAndroid Build Coastguard Worker             inner->lineTo(miterMidpt);
289*c8dee2aaSAndroid Build Coastguard Worker             inner->lineTo(miterMidpt - dest);
290*c8dee2aaSAndroid Build Coastguard Worker         } else {
291*c8dee2aaSAndroid Build Coastguard Worker             // Directly connect to inner miter point.
292*c8dee2aaSAndroid Build Coastguard Worker             inner->setLastPt(miterMidpt - miterVec);
293*c8dee2aaSAndroid Build Coastguard Worker         }
294*c8dee2aaSAndroid Build Coastguard Worker     };
295*c8dee2aaSAndroid Build Coastguard Worker 
296*c8dee2aaSAndroid Build Coastguard Worker     switch (fJoin) {
297*c8dee2aaSAndroid Build Coastguard Worker         case SkPaint::kMiter_Join:
298*c8dee2aaSAndroid Build Coastguard Worker             miterJoin(prev, curr);
299*c8dee2aaSAndroid Build Coastguard Worker             break;
300*c8dee2aaSAndroid Build Coastguard Worker         default:
301*c8dee2aaSAndroid Build Coastguard Worker             SkDebugf("Unhandled join %d\n", fJoin);
302*c8dee2aaSAndroid Build Coastguard Worker             miterJoin(prev, curr);
303*c8dee2aaSAndroid Build Coastguard Worker             break;
304*c8dee2aaSAndroid Build Coastguard Worker     }
305*c8dee2aaSAndroid Build Coastguard Worker }
306*c8dee2aaSAndroid Build Coastguard Worker 
appendPathReversed(const PathRecorder & path,PathRecorder * result)307*c8dee2aaSAndroid Build Coastguard Worker void SkPathStroker2::appendPathReversed(const PathRecorder& path, PathRecorder* result) {
308*c8dee2aaSAndroid Build Coastguard Worker     const int numVerbs = path.countVerbs();
309*c8dee2aaSAndroid Build Coastguard Worker     const int numPoints = path.countPoints();
310*c8dee2aaSAndroid Build Coastguard Worker     const std::vector<uint8_t>& verbs = path.verbs();
311*c8dee2aaSAndroid Build Coastguard Worker     const std::vector<SkPoint>& points = path.points();
312*c8dee2aaSAndroid Build Coastguard Worker 
313*c8dee2aaSAndroid Build Coastguard Worker     for (int i = numVerbs - 1, j = numPoints; i >= 0; i--) {
314*c8dee2aaSAndroid Build Coastguard Worker         auto verb = static_cast<SkPath::Verb>(verbs[i]);
315*c8dee2aaSAndroid Build Coastguard Worker         switch (verb) {
316*c8dee2aaSAndroid Build Coastguard Worker             case SkPath::kLine_Verb: {
317*c8dee2aaSAndroid Build Coastguard Worker                 j -= 1;
318*c8dee2aaSAndroid Build Coastguard Worker                 SkASSERT(j >= 1);
319*c8dee2aaSAndroid Build Coastguard Worker                 result->lineTo(points[j - 1]);
320*c8dee2aaSAndroid Build Coastguard Worker                 break;
321*c8dee2aaSAndroid Build Coastguard Worker             }
322*c8dee2aaSAndroid Build Coastguard Worker             case SkPath::kMove_Verb:
323*c8dee2aaSAndroid Build Coastguard Worker                 // Ignore
324*c8dee2aaSAndroid Build Coastguard Worker                 break;
325*c8dee2aaSAndroid Build Coastguard Worker             default:
326*c8dee2aaSAndroid Build Coastguard Worker                 SkASSERT(false);
327*c8dee2aaSAndroid Build Coastguard Worker                 break;
328*c8dee2aaSAndroid Build Coastguard Worker         }
329*c8dee2aaSAndroid Build Coastguard Worker     }
330*c8dee2aaSAndroid Build Coastguard Worker }
331*c8dee2aaSAndroid Build Coastguard Worker 
unitNormal(const PathSegment & seg,float t)332*c8dee2aaSAndroid Build Coastguard Worker SkPoint SkPathStroker2::unitNormal(const PathSegment& seg, float t) {
333*c8dee2aaSAndroid Build Coastguard Worker     if (seg.fVerb != SkPath::kLine_Verb) {
334*c8dee2aaSAndroid Build Coastguard Worker         SkDebugf("Unhandled verb for unit normal %d\n", seg.fVerb);
335*c8dee2aaSAndroid Build Coastguard Worker     }
336*c8dee2aaSAndroid Build Coastguard Worker 
337*c8dee2aaSAndroid Build Coastguard Worker     (void)t;  // Not needed for lines
338*c8dee2aaSAndroid Build Coastguard Worker     const SkPoint tangent = seg.fPoints[1] - seg.fPoints[0];
339*c8dee2aaSAndroid Build Coastguard Worker     const SkPoint normal = rotate90(tangent);
340*c8dee2aaSAndroid Build Coastguard Worker     return setLength(normal, 1);
341*c8dee2aaSAndroid Build Coastguard Worker }
342*c8dee2aaSAndroid Build Coastguard Worker 
squaredLineLength(const PathSegment & lineSeg)343*c8dee2aaSAndroid Build Coastguard Worker float SkPathStroker2::squaredLineLength(const PathSegment& lineSeg) {
344*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(lineSeg.fVerb == SkPath::kLine_Verb);
345*c8dee2aaSAndroid Build Coastguard Worker     const SkPoint diff = lineSeg.fPoints[1] - lineSeg.fPoints[0];
346*c8dee2aaSAndroid Build Coastguard Worker     return diff.dot(diff);
347*c8dee2aaSAndroid Build Coastguard Worker }
348*c8dee2aaSAndroid Build Coastguard Worker 
349*c8dee2aaSAndroid Build Coastguard Worker }  // namespace
350*c8dee2aaSAndroid Build Coastguard Worker 
351*c8dee2aaSAndroid Build Coastguard Worker //////////////////////////////////////////////////////////////////////////////
352*c8dee2aaSAndroid Build Coastguard Worker 
353*c8dee2aaSAndroid Build Coastguard Worker class SimpleStrokerSlide : public ClickHandlerSlide {
354*c8dee2aaSAndroid Build Coastguard Worker     bool fShowSkiaStroke, fShowHidden, fShowSkeleton;
355*c8dee2aaSAndroid Build Coastguard Worker     float fWidth = 175;
356*c8dee2aaSAndroid Build Coastguard Worker     SkPaint fPtsPaint, fStrokePaint, fMirrorStrokePaint, fNewFillPaint, fHiddenPaint,
357*c8dee2aaSAndroid Build Coastguard Worker             fSkeletonPaint;
358*c8dee2aaSAndroid Build Coastguard Worker     inline static constexpr int kN = 3;
359*c8dee2aaSAndroid Build Coastguard Worker 
360*c8dee2aaSAndroid Build Coastguard Worker public:
361*c8dee2aaSAndroid Build Coastguard Worker     SkPoint fPts[kN];
362*c8dee2aaSAndroid Build Coastguard Worker 
SimpleStrokerSlide()363*c8dee2aaSAndroid Build Coastguard Worker     SimpleStrokerSlide() : fShowSkiaStroke(true), fShowHidden(true), fShowSkeleton(true) {
364*c8dee2aaSAndroid Build Coastguard Worker         fPts[0] = {500, 200};
365*c8dee2aaSAndroid Build Coastguard Worker         fPts[1] = {300, 200};
366*c8dee2aaSAndroid Build Coastguard Worker         fPts[2] = {100, 100};
367*c8dee2aaSAndroid Build Coastguard Worker 
368*c8dee2aaSAndroid Build Coastguard Worker         fPtsPaint.setAntiAlias(true);
369*c8dee2aaSAndroid Build Coastguard Worker         fPtsPaint.setStrokeWidth(10);
370*c8dee2aaSAndroid Build Coastguard Worker         fPtsPaint.setStrokeCap(SkPaint::kRound_Cap);
371*c8dee2aaSAndroid Build Coastguard Worker 
372*c8dee2aaSAndroid Build Coastguard Worker         fStrokePaint.setAntiAlias(true);
373*c8dee2aaSAndroid Build Coastguard Worker         fStrokePaint.setStyle(SkPaint::kStroke_Style);
374*c8dee2aaSAndroid Build Coastguard Worker         fStrokePaint.setColor(0x80FF0000);
375*c8dee2aaSAndroid Build Coastguard Worker 
376*c8dee2aaSAndroid Build Coastguard Worker         fMirrorStrokePaint.setAntiAlias(true);
377*c8dee2aaSAndroid Build Coastguard Worker         fMirrorStrokePaint.setStyle(SkPaint::kStroke_Style);
378*c8dee2aaSAndroid Build Coastguard Worker         fMirrorStrokePaint.setColor(0x80FFFF00);
379*c8dee2aaSAndroid Build Coastguard Worker 
380*c8dee2aaSAndroid Build Coastguard Worker         fNewFillPaint.setAntiAlias(true);
381*c8dee2aaSAndroid Build Coastguard Worker         fNewFillPaint.setColor(0x8000FF00);
382*c8dee2aaSAndroid Build Coastguard Worker 
383*c8dee2aaSAndroid Build Coastguard Worker         fHiddenPaint.setAntiAlias(true);
384*c8dee2aaSAndroid Build Coastguard Worker         fHiddenPaint.setStyle(SkPaint::kStroke_Style);
385*c8dee2aaSAndroid Build Coastguard Worker         fHiddenPaint.setColor(0xFF0000FF);
386*c8dee2aaSAndroid Build Coastguard Worker 
387*c8dee2aaSAndroid Build Coastguard Worker         fSkeletonPaint.setAntiAlias(true);
388*c8dee2aaSAndroid Build Coastguard Worker         fSkeletonPaint.setStyle(SkPaint::kStroke_Style);
389*c8dee2aaSAndroid Build Coastguard Worker         fSkeletonPaint.setColor(SK_ColorRED);
390*c8dee2aaSAndroid Build Coastguard Worker         fName = "SimpleStroker";
391*c8dee2aaSAndroid Build Coastguard Worker     }
392*c8dee2aaSAndroid Build Coastguard Worker 
onChar(SkUnichar uni)393*c8dee2aaSAndroid Build Coastguard Worker     bool onChar(SkUnichar uni) override {
394*c8dee2aaSAndroid Build Coastguard Worker         switch (uni) {
395*c8dee2aaSAndroid Build Coastguard Worker             case '1':
396*c8dee2aaSAndroid Build Coastguard Worker                 this->toggle(fShowSkeleton);
397*c8dee2aaSAndroid Build Coastguard Worker                 return true;
398*c8dee2aaSAndroid Build Coastguard Worker             case '2':
399*c8dee2aaSAndroid Build Coastguard Worker                 this->toggle(fShowSkiaStroke);
400*c8dee2aaSAndroid Build Coastguard Worker                 return true;
401*c8dee2aaSAndroid Build Coastguard Worker             case '3':
402*c8dee2aaSAndroid Build Coastguard Worker                 this->toggle(fShowHidden);
403*c8dee2aaSAndroid Build Coastguard Worker                 return true;
404*c8dee2aaSAndroid Build Coastguard Worker             case '-':
405*c8dee2aaSAndroid Build Coastguard Worker                 fWidth -= 5;
406*c8dee2aaSAndroid Build Coastguard Worker                 return true;
407*c8dee2aaSAndroid Build Coastguard Worker             case '=':
408*c8dee2aaSAndroid Build Coastguard Worker                 fWidth += 5;
409*c8dee2aaSAndroid Build Coastguard Worker                 return true;
410*c8dee2aaSAndroid Build Coastguard Worker             default:
411*c8dee2aaSAndroid Build Coastguard Worker                 break;
412*c8dee2aaSAndroid Build Coastguard Worker         }
413*c8dee2aaSAndroid Build Coastguard Worker         return false;
414*c8dee2aaSAndroid Build Coastguard Worker     }
415*c8dee2aaSAndroid Build Coastguard Worker 
draw(SkCanvas * canvas)416*c8dee2aaSAndroid Build Coastguard Worker     void draw(SkCanvas* canvas) override {
417*c8dee2aaSAndroid Build Coastguard Worker         canvas->drawColor(0xFFEEEEEE);
418*c8dee2aaSAndroid Build Coastguard Worker 
419*c8dee2aaSAndroid Build Coastguard Worker         SkPath path;
420*c8dee2aaSAndroid Build Coastguard Worker         this->makePath(&path);
421*c8dee2aaSAndroid Build Coastguard Worker 
422*c8dee2aaSAndroid Build Coastguard Worker         fStrokePaint.setStrokeWidth(fWidth);
423*c8dee2aaSAndroid Build Coastguard Worker 
424*c8dee2aaSAndroid Build Coastguard Worker         // The correct result
425*c8dee2aaSAndroid Build Coastguard Worker         if (fShowSkiaStroke) {
426*c8dee2aaSAndroid Build Coastguard Worker             canvas->drawPath(path, fStrokePaint);
427*c8dee2aaSAndroid Build Coastguard Worker         }
428*c8dee2aaSAndroid Build Coastguard Worker 
429*c8dee2aaSAndroid Build Coastguard Worker         // Simple stroker result
430*c8dee2aaSAndroid Build Coastguard Worker         SkPathStroker2 stroker;
431*c8dee2aaSAndroid Build Coastguard Worker         SkPath fillPath = stroker.getFillPath(path, fStrokePaint);
432*c8dee2aaSAndroid Build Coastguard Worker         canvas->drawPath(fillPath, fNewFillPaint);
433*c8dee2aaSAndroid Build Coastguard Worker 
434*c8dee2aaSAndroid Build Coastguard Worker         if (fShowHidden) {
435*c8dee2aaSAndroid Build Coastguard Worker             canvas->drawPath(fillPath, fHiddenPaint);
436*c8dee2aaSAndroid Build Coastguard Worker         }
437*c8dee2aaSAndroid Build Coastguard Worker         if (fShowSkeleton) {
438*c8dee2aaSAndroid Build Coastguard Worker             canvas->drawPath(path, fSkeletonPaint);
439*c8dee2aaSAndroid Build Coastguard Worker         }
440*c8dee2aaSAndroid Build Coastguard Worker         canvas->drawPoints(SkCanvas::kPoints_PointMode, kN, fPts, fPtsPaint);
441*c8dee2aaSAndroid Build Coastguard Worker 
442*c8dee2aaSAndroid Build Coastguard Worker         // Draw a mirror but using Skia's stroker.
443*c8dee2aaSAndroid Build Coastguard Worker         canvas->translate(0, 400);
444*c8dee2aaSAndroid Build Coastguard Worker         fMirrorStrokePaint.setStrokeWidth(fWidth);
445*c8dee2aaSAndroid Build Coastguard Worker         canvas->drawPath(path, fMirrorStrokePaint);
446*c8dee2aaSAndroid Build Coastguard Worker         if (fShowHidden) {
447*c8dee2aaSAndroid Build Coastguard Worker             SkPath hidden;
448*c8dee2aaSAndroid Build Coastguard Worker             skpathutils::FillPathWithPaint(path, fStrokePaint, &hidden);
449*c8dee2aaSAndroid Build Coastguard Worker             canvas->drawPath(hidden, fHiddenPaint);
450*c8dee2aaSAndroid Build Coastguard Worker         }
451*c8dee2aaSAndroid Build Coastguard Worker         if (fShowSkeleton) {
452*c8dee2aaSAndroid Build Coastguard Worker             canvas->drawPath(path, fSkeletonPaint);
453*c8dee2aaSAndroid Build Coastguard Worker         }
454*c8dee2aaSAndroid Build Coastguard Worker     }
455*c8dee2aaSAndroid Build Coastguard Worker 
456*c8dee2aaSAndroid Build Coastguard Worker protected:
onFindClickHandler(SkScalar x,SkScalar y,skui::ModifierKey modi)457*c8dee2aaSAndroid Build Coastguard Worker     Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override {
458*c8dee2aaSAndroid Build Coastguard Worker         const SkScalar tol = 4;
459*c8dee2aaSAndroid Build Coastguard Worker         const SkRect r = SkRect::MakeXYWH(x - tol, y - tol, tol * 2, tol * 2);
460*c8dee2aaSAndroid Build Coastguard Worker         for (int i = 0; i < kN; ++i) {
461*c8dee2aaSAndroid Build Coastguard Worker             if (r.intersects(SkRect::MakeXYWH(fPts[i].fX, fPts[i].fY, 1, 1))) {
462*c8dee2aaSAndroid Build Coastguard Worker                 return new Click([this, i](Click* c) {
463*c8dee2aaSAndroid Build Coastguard Worker                     fPts[i] = c->fCurr;
464*c8dee2aaSAndroid Build Coastguard Worker                     return true;
465*c8dee2aaSAndroid Build Coastguard Worker                 });
466*c8dee2aaSAndroid Build Coastguard Worker             }
467*c8dee2aaSAndroid Build Coastguard Worker         }
468*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
469*c8dee2aaSAndroid Build Coastguard Worker     }
470*c8dee2aaSAndroid Build Coastguard Worker 
onClick(ClickHandlerSlide::Click *)471*c8dee2aaSAndroid Build Coastguard Worker     bool onClick(ClickHandlerSlide::Click *) override { return false; }
472*c8dee2aaSAndroid Build Coastguard Worker 
473*c8dee2aaSAndroid Build Coastguard Worker private:
toggle(bool & value)474*c8dee2aaSAndroid Build Coastguard Worker     void toggle(bool& value) { value = !value; }
475*c8dee2aaSAndroid Build Coastguard Worker 
makePath(SkPath * path)476*c8dee2aaSAndroid Build Coastguard Worker     void makePath(SkPath* path) {
477*c8dee2aaSAndroid Build Coastguard Worker         path->moveTo(fPts[0]);
478*c8dee2aaSAndroid Build Coastguard Worker         for (int i = 1; i < kN; ++i) {
479*c8dee2aaSAndroid Build Coastguard Worker             path->lineTo(fPts[i]);
480*c8dee2aaSAndroid Build Coastguard Worker         }
481*c8dee2aaSAndroid Build Coastguard Worker     }
482*c8dee2aaSAndroid Build Coastguard Worker 
483*c8dee2aaSAndroid Build Coastguard Worker };
484*c8dee2aaSAndroid Build Coastguard Worker 
485*c8dee2aaSAndroid Build Coastguard Worker DEF_SLIDE(return new SimpleStrokerSlide;)
486