blob: 9a0377c0b0532ca13996267488d9b81fe9b6a3aa [file] [log] [blame]
Tyler Denniston324578b2020-07-17 14:03:42 -04001/*
2 * Copyright 2020 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/SkBitmap.h"
9#include "include/core/SkCanvas.h"
10#include "include/core/SkPath.h"
11#include "include/utils/SkParsePath.h"
12#include "samplecode/Sample.h"
13
14#include "src/core/SkGeometry.h"
15
16namespace {
17
18//////////////////////////////////////////////////////////////////////////////
19
Tyler Dennistonf219aad2020-07-21 16:16:55 -040020static SkPoint rotate90(const SkPoint& p) { return {p.fY, -p.fX}; }
Tyler Denniston324578b2020-07-17 14:03:42 -040021static SkPoint rotate180(const SkPoint& p) { return p * -1; }
22static SkPoint setLength(SkPoint p, float len) {
23 if (!p.setLength(len)) {
24 SkDebugf("Failed to set point length\n");
25 }
26 return p;
27}
28static bool isClockwise(const SkPoint& a, const SkPoint& b) { return a.cross(b) > 0; }
29
30//////////////////////////////////////////////////////////////////////////////
31
32// Testing ground for a new stroker implementation
33class SkPathStroker2 {
34public:
35 // Returns the fill path
36 SkPath getFillPath(const SkPath& path, const SkPaint& paint);
37
38private:
39 struct PathSegment {
40 SkPath::Verb fVerb;
41 SkPoint fPoints[4];
42 };
43
44 float fRadius;
45 SkPaint::Cap fCap;
46 SkPaint::Join fJoin;
47 SkPath fInnerPath, fOuterPath;
48 SkPath *fInner = &fInnerPath, *fOuter = &fOuterPath;
49
50 // Initialize stroker state
51 void initForPath(const SkPath& path, const SkPaint& paint);
52
53 // Strokes a line segment
54 void strokeLine(const PathSegment& line, bool needsMove);
55
56 // Adds an endcap to fOuter
57 enum class CapLocation { Start, End };
58 void endcap(CapLocation loc);
59
60 // Adds a join between the two segments
61 void join(const PathSegment& prev, const PathSegment& curr);
62
63 // Appends path in reverse to result
64 static void appendPathReversed(const SkPath* path, SkPath* result);
65
66 // Returns the segment unit normal
67 static SkPoint unitNormal(const PathSegment& seg, float t);
68};
69
70void SkPathStroker2::initForPath(const SkPath& path, const SkPaint& paint) {
71 fRadius = paint.getStrokeWidth() / 2;
72 fCap = paint.getStrokeCap();
73 fJoin = paint.getStrokeJoin();
74 fInnerPath.rewind();
75 fOuterPath.rewind();
76 fInner = &fInnerPath;
77 fOuter = &fOuterPath;
78}
79
80SkPath SkPathStroker2::getFillPath(const SkPath& path, const SkPaint& paint) {
81 initForPath(path, paint);
82
83 // Trace the inner and outer paths simultaneously. Inner will therefore be
84 // recorded in reverse from how we trace the outline.
85 SkPath::Iter it(path, false);
86 PathSegment segment, prevSegment;
87 bool firstSegment = true;
88 while ((segment.fVerb = it.next(segment.fPoints)) != SkPath::kDone_Verb) {
89 // Join to the previous segment
90 if (!firstSegment) {
91 join(prevSegment, segment);
92 }
93
94 // Stroke the current segment
95 switch (segment.fVerb) {
96 case SkPath::kLine_Verb:
97 strokeLine(segment, firstSegment);
98 break;
99 case SkPath::kMove_Verb:
100 // Don't care about multiple contours currently
101 continue;
102 default:
103 SkDebugf("Unhandled path verb %d\n", segment.fVerb);
104 break;
105 }
106
107 std::swap(segment, prevSegment);
108 firstSegment = false;
109 }
110
111 // Open contour => endcap at the end
112 const bool isClosed = path.isLastContourClosed();
113 if (isClosed) {
114 SkDebugf("Unhandled closed contour\n");
115 } else {
116 endcap(CapLocation::End);
117 }
118
119 // Walk inner path in reverse, appending to result
120 appendPathReversed(fInner, fOuter);
121 endcap(CapLocation::Start);
122
123 return fOuterPath;
124}
125
126void SkPathStroker2::strokeLine(const PathSegment& line, bool needsMove) {
127 const SkPoint tangent = line.fPoints[1] - line.fPoints[0];
128 const SkPoint normal = rotate90(tangent);
129 const SkPoint offset = setLength(normal, fRadius);
130 if (needsMove) {
131 fOuter->moveTo(line.fPoints[0] + offset);
132 fInner->moveTo(line.fPoints[0] - offset);
133 }
134 fOuter->lineTo(line.fPoints[1] + offset);
135 fInner->lineTo(line.fPoints[1] - offset);
136}
137
138void SkPathStroker2::endcap(CapLocation loc) {
139 const auto buttCap = [this](CapLocation loc) {
140 if (loc == CapLocation::Start) {
141 // Back at the start of the path: just close the stroked outline
142 fOuter->close();
143 } else {
144 // Inner last pt == first pt when appending in reverse
145 SkPoint innerLastPt;
146 fInner->getLastPt(&innerLastPt);
147 fOuter->lineTo(innerLastPt);
148 }
149 };
150
151 switch (fCap) {
152 case SkPaint::kButt_Cap:
153 buttCap(loc);
154 break;
155 default:
156 SkDebugf("Unhandled endcap %d\n", fCap);
157 buttCap(loc);
158 break;
159 }
160}
161
162void SkPathStroker2::join(const PathSegment& prev, const PathSegment& curr) {
163 const auto miterJoin = [this](const PathSegment& prev, const PathSegment& curr) {
Tyler Dennistonf219aad2020-07-21 16:16:55 -0400164 // Common path endpoint of the two segments is the midpoint of the miter line.
Tyler Denniston324578b2020-07-17 14:03:42 -0400165 const SkPoint miterMidpt = curr.fPoints[0];
Tyler Dennistonf219aad2020-07-21 16:16:55 -0400166
Tyler Denniston324578b2020-07-17 14:03:42 -0400167 SkPoint before = unitNormal(prev, 1);
168 SkPoint after = unitNormal(curr, 0);
169
170 // Check who's inside and who's outside.
171 SkPath *outer = fOuter, *inner = fInner;
172 if (!isClockwise(before, after)) {
173 std::swap(inner, outer);
174 before = rotate180(before);
175 after = rotate180(after);
176 }
177
178 const float cosTheta = before.dot(after);
179 if (SkScalarNearlyZero(1 - cosTheta)) {
180 // Nearly identical normals: don't bother.
181 return;
182 }
183
184 // Before and after have the same origin and magnitude, so before+after is the diagonal of
185 // their rhombus. Origin of this vector is the midpoint of the miter line.
186 SkPoint miterVec = before + after;
187
188 // Note the relationship (draw a right triangle with the miter line as its hypoteneuse):
189 // sin(theta/2) = strokeWidth / miterLength
190 // so miterLength = strokeWidth / sin(theta/2)
191 // where miterLength is the length of the miter from outer point to inner corner.
192 // miterVec's origin is the midpoint of the miter line, so we use strokeWidth/2.
193 // Sqrt is just an application of half-angle identities.
194 const float sinHalfTheta = sqrtf(0.5 * (1 + cosTheta));
195 const float halfMiterLength = fRadius / sinHalfTheta;
196 miterVec.setLength(halfMiterLength); // TODO: miter length limit
197
Tyler Dennistonf219aad2020-07-21 16:16:55 -0400198 // Outer: connect to the miter point, and then to t=0 (on outside) of next segment.
199 const SkPoint dest = setLength(after, fRadius);
Tyler Denniston324578b2020-07-17 14:03:42 -0400200 outer->lineTo(miterMidpt + miterVec);
Tyler Dennistonf219aad2020-07-21 16:16:55 -0400201 outer->lineTo(miterMidpt + dest);
202
203 // Inner: we're already at t=1 (on inside) of 'prev'.
204 // Connect to the miter midpoint (common path endpoint of the two segments),
205 // and then to t=0 (on inside) of the next segment. This adds an interior "loop" of
206 // geometry that in many cases is unnecessary, but handles several edge cases.
207 inner->lineTo(miterMidpt);
208 inner->lineTo(miterMidpt - dest);
Tyler Denniston324578b2020-07-17 14:03:42 -0400209 };
210
211 switch (fJoin) {
212 case SkPaint::kMiter_Join:
213 miterJoin(prev, curr);
214 break;
215 default:
216 SkDebugf("Unhandled join %d\n", fJoin);
217 miterJoin(prev, curr);
218 break;
219 }
220}
221
222void SkPathStroker2::appendPathReversed(const SkPath* path, SkPath* result) {
223 const int numVerbs = path->countVerbs();
224 const int numPoints = path->countPoints();
225 std::unique_ptr<uint8_t[]> verbs = std::make_unique<uint8_t[]>(numVerbs);
226 std::unique_ptr<SkPoint[]> points = std::make_unique<SkPoint[]>(numPoints);
227
228 path->getVerbs(verbs.get(), numVerbs);
229 path->getPoints(points.get(), numPoints);
230
231 for (int i = numVerbs - 1, j = numPoints; i >= 0; i--) {
232 auto verb = static_cast<SkPath::Verb>(verbs[i]);
233 switch (verb) {
234 case SkPath::kLine_Verb: {
235 j -= 1;
236 SkASSERT(j >= 1);
237 result->lineTo(points[j - 1]);
238 break;
239 }
240 case SkPath::kMove_Verb:
241 // Ignore
242 break;
243 default:
244 SkASSERT(false);
245 break;
246 }
247 }
248}
249
250SkPoint SkPathStroker2::unitNormal(const PathSegment& seg, float t) {
251 if (seg.fVerb != SkPath::kLine_Verb) {
252 SkDebugf("Unhandled verb for unit normal %d\n", seg.fVerb);
253 }
254
255 (void)t; // Not needed for lines
256 const SkPoint tangent = seg.fPoints[1] - seg.fPoints[0];
257 const SkPoint normal = rotate90(tangent);
258 return setLength(normal, 1);
259}
260
261} // namespace
262
263//////////////////////////////////////////////////////////////////////////////
264
265class SimpleStroker : public Sample {
266 bool fShowSkiaStroke, fShowHidden;
267 float fWidth = 175;
268 SkPaint fPtsPaint, fStrokePaint, fNewFillPaint, fHiddenPaint;
269 static constexpr int kN = 3;
270
271public:
272 SkPoint fPts[kN];
273
274 SimpleStroker() : fShowSkiaStroke(true), fShowHidden(false) {
275 fPts[0] = {500, 200};
276 fPts[1] = {300, 200};
277 fPts[2] = {100, 100};
278
279 fPtsPaint.setAntiAlias(true);
280 fPtsPaint.setStrokeWidth(10);
281 fPtsPaint.setStrokeCap(SkPaint::kRound_Cap);
282
283 fStrokePaint.setAntiAlias(true);
284 fStrokePaint.setStyle(SkPaint::kStroke_Style);
285 fStrokePaint.setColor(0x80FF0000);
286
287 fNewFillPaint.setAntiAlias(true);
288 fNewFillPaint.setColor(0x8000FF00);
289
290 fHiddenPaint.setAntiAlias(true);
291 fHiddenPaint.setStyle(SkPaint::kStroke_Style);
292 fHiddenPaint.setColor(0xFF0000FF);
293 }
294
295 void toggle(bool& value) { value = !value; }
296
297protected:
298 SkString name() override { return SkString("SimpleStroker"); }
299
300 bool onChar(SkUnichar uni) override {
301 switch (uni) {
302 case '1':
303 this->toggle(fShowSkiaStroke);
304 return true;
305 case '2':
306 this->toggle(fShowHidden);
307 return true;
308 case '-':
309 fWidth -= 5;
310 return true;
311 case '=':
312 fWidth += 5;
313 return true;
314 default:
315 break;
316 }
317 return false;
318 }
319
320 void makePath(SkPath* path) {
321 path->moveTo(fPts[0]);
322 for (int i = 1; i < kN; ++i) {
323 path->lineTo(fPts[i]);
324 }
325 }
326
327 void onDrawContent(SkCanvas* canvas) override {
328 canvas->drawColor(0xFFEEEEEE);
329
330 SkPath path;
331 this->makePath(&path);
332
333 fStrokePaint.setStrokeWidth(fWidth);
334
335 // The correct result
336 if (fShowSkiaStroke) {
337 canvas->drawPath(path, fStrokePaint);
338 }
339
340 // Simple stroker result
341 SkPathStroker2 stroker;
342 SkPath fillPath = stroker.getFillPath(path, fStrokePaint);
343 canvas->drawPath(fillPath, fNewFillPaint);
344
345 if (fShowHidden) {
346 canvas->drawPath(fillPath, fHiddenPaint);
347 }
348
349 canvas->drawPoints(SkCanvas::kPoints_PointMode, kN, fPts, fPtsPaint);
350 }
351
352 Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override {
353 const SkScalar tol = 4;
354 const SkRect r = SkRect::MakeXYWH(x - tol, y - tol, tol * 2, tol * 2);
355 for (int i = 0; i < kN; ++i) {
356 if (r.intersects(SkRect::MakeXYWH(fPts[i].fX, fPts[i].fY, 1, 1))) {
357 return new Click([this, i](Click* c) {
358 fPts[i] = c->fCurr;
359 return true;
360 });
361 }
362 }
363 return nullptr;
364 }
365
366private:
367 typedef Sample INHERITED;
368};
369
370DEF_SAMPLE(return new SimpleStroker;)