1 /*
2 * Copyright 2006 The Android Open Source Project
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/SkCornerPathEffect.h"
9
10 #include "include/core/SkFlattenable.h"
11 #include "include/core/SkPath.h"
12 #include "include/core/SkPathEffect.h"
13 #include "include/core/SkPoint.h"
14 #include "include/core/SkRefCnt.h"
15 #include "include/core/SkScalar.h"
16 #include "include/core/SkTypes.h"
17 #include "src/core/SkPathEffectBase.h"
18 #include "src/core/SkReadBuffer.h"
19 #include "src/core/SkWriteBuffer.h"
20
21 class SkMatrix;
22 class SkStrokeRec;
23 struct SkRect;
24
ComputeStep(const SkPoint & a,const SkPoint & b,SkScalar radius,SkPoint * step)25 static bool ComputeStep(const SkPoint& a, const SkPoint& b, SkScalar radius,
26 SkPoint* step) {
27 SkScalar dist = SkPoint::Distance(a, b);
28
29 *step = b - a;
30 if (dist <= radius * 2) {
31 *step *= SK_ScalarHalf;
32 return false;
33 } else {
34 *step *= radius / dist;
35 return true;
36 }
37 }
38
39 class SkCornerPathEffectImpl : public SkPathEffectBase {
40 public:
SkCornerPathEffectImpl(SkScalar radius)41 explicit SkCornerPathEffectImpl(SkScalar radius) : fRadius(radius) {
42 SkASSERT(radius > 0);
43 }
44
onFilterPath(SkPath * dst,const SkPath & src,SkStrokeRec *,const SkRect *,const SkMatrix &) const45 bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*,
46 const SkMatrix&) const override {
47 if (fRadius <= 0) {
48 return false;
49 }
50
51 SkPath::Iter iter(src, false);
52 SkPath::Verb verb, prevVerb = SkPath::kDone_Verb;
53 SkPoint pts[4];
54
55 bool closed;
56 SkPoint moveTo, lastCorner;
57 SkVector firstStep, step;
58 bool prevIsValid = true;
59
60 // to avoid warnings
61 step.set(0, 0);
62 moveTo.set(0, 0);
63 firstStep.set(0, 0);
64 lastCorner.set(0, 0);
65
66 for (;;) {
67 switch (verb = iter.next(pts)) {
68 case SkPath::kMove_Verb:
69 // close out the previous (open) contour
70 if (SkPath::kLine_Verb == prevVerb) {
71 dst->lineTo(lastCorner);
72 }
73 closed = iter.isClosedContour();
74 if (closed) {
75 moveTo = pts[0];
76 prevIsValid = false;
77 } else {
78 dst->moveTo(pts[0]);
79 prevIsValid = true;
80 }
81 break;
82 case SkPath::kLine_Verb: {
83 bool drawSegment = ComputeStep(pts[0], pts[1], fRadius, &step);
84 // prev corner
85 if (!prevIsValid) {
86 dst->moveTo(moveTo + step);
87 prevIsValid = true;
88 } else {
89 dst->quadTo(pts[0].fX, pts[0].fY, pts[0].fX + step.fX,
90 pts[0].fY + step.fY);
91 }
92 if (drawSegment) {
93 dst->lineTo(pts[1].fX - step.fX, pts[1].fY - step.fY);
94 }
95 lastCorner = pts[1];
96 prevIsValid = true;
97 break;
98 }
99 case SkPath::kQuad_Verb:
100 // TBD - just replicate the curve for now
101 if (!prevIsValid) {
102 dst->moveTo(pts[0]);
103 prevIsValid = true;
104 }
105 dst->quadTo(pts[1], pts[2]);
106 lastCorner = pts[2];
107 firstStep.set(0, 0);
108 break;
109 case SkPath::kConic_Verb:
110 // TBD - just replicate the curve for now
111 if (!prevIsValid) {
112 dst->moveTo(pts[0]);
113 prevIsValid = true;
114 }
115 dst->conicTo(pts[1], pts[2], iter.conicWeight());
116 lastCorner = pts[2];
117 firstStep.set(0, 0);
118 break;
119 case SkPath::kCubic_Verb:
120 if (!prevIsValid) {
121 dst->moveTo(pts[0]);
122 prevIsValid = true;
123 }
124 // TBD - just replicate the curve for now
125 dst->cubicTo(pts[1], pts[2], pts[3]);
126 lastCorner = pts[3];
127 firstStep.set(0, 0);
128 break;
129 case SkPath::kClose_Verb:
130 if (firstStep.fX || firstStep.fY) {
131 dst->quadTo(lastCorner.fX, lastCorner.fY,
132 lastCorner.fX + firstStep.fX,
133 lastCorner.fY + firstStep.fY);
134 }
135 dst->close();
136 prevIsValid = false;
137 break;
138 case SkPath::kDone_Verb:
139 if (prevIsValid) {
140 dst->lineTo(lastCorner);
141 }
142 return true;
143 default:
144 SkDEBUGFAIL("default should not be reached");
145 return false;
146 }
147
148 if (SkPath::kMove_Verb == prevVerb) {
149 firstStep = step;
150 }
151 prevVerb = verb;
152 }
153 }
154
computeFastBounds(SkRect *) const155 bool computeFastBounds(SkRect*) const override {
156 // Rounding sharp corners within a path produces a new path that is still contained within
157 // the original's bounds, so leave 'bounds' unmodified.
158 return true;
159 }
160
CreateProc(SkReadBuffer & buffer)161 static sk_sp<SkFlattenable> CreateProc(SkReadBuffer& buffer) {
162 return SkCornerPathEffect::Make(buffer.readScalar());
163 }
164
flatten(SkWriteBuffer & buffer) const165 void flatten(SkWriteBuffer& buffer) const override {
166 buffer.writeScalar(fRadius);
167 }
168
getFactory() const169 Factory getFactory() const override { return CreateProc; }
getTypeName() const170 const char* getTypeName() const override { return "SkCornerPathEffect"; }
171
172 private:
173 const SkScalar fRadius;
174
175 using INHERITED = SkPathEffectBase;
176 };
177
178 //////////////////////////////////////////////////////////////////////////////////////////////////
179
Make(SkScalar radius)180 sk_sp<SkPathEffect> SkCornerPathEffect::Make(SkScalar radius) {
181 return SkScalarIsFinite(radius) && (radius > 0) ?
182 sk_sp<SkPathEffect>(new SkCornerPathEffectImpl(radius)) : nullptr;
183 }
184
RegisterFlattenables()185 void SkCornerPathEffect::RegisterFlattenables() {
186 SkFlattenable::Register("SkCornerPathEffect", SkCornerPathEffectImpl::CreateProc);
187 }
188