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 "src/gpu/ccpr/GrCCStrokeGeometry.h" |
9 | |
10 | #include "include/core/SkStrokeRec.h" |
11 | #include "include/private/SkNx.h" |
12 | #include "src/core/SkGeometry.h" |
13 | #include "src/core/SkMathPriv.h" |
14 | |
15 | // This is the maximum distance in pixels that we can stray from the edge of a stroke when |
16 | // converting it to flat line segments. |
17 | static constexpr float kMaxErrorFromLinearization = 1/8.f; |
18 | |
19 | static inline float length(const Sk2f& n) { |
20 | Sk2f nn = n*n; |
21 | return SkScalarSqrt(nn[0] + nn[1]); |
22 | } |
23 | |
24 | static inline Sk2f normalize(const Sk2f& v) { |
25 | Sk2f vv = v*v; |
26 | vv += SkNx_shuffle<1,0>(vv); |
27 | return v * vv.rsqrt(); |
28 | } |
29 | |
30 | static inline void transpose(const Sk2f& a, const Sk2f& b, Sk2f* X, Sk2f* Y) { |
31 | float transpose[4]; |
32 | a.store(transpose); |
33 | b.store(transpose+2); |
34 | Sk2f::Load2(transpose, X, Y); |
35 | } |
36 | |
37 | static inline void normalize2(const Sk2f& v0, const Sk2f& v1, SkPoint out[2]) { |
38 | Sk2f X, Y; |
39 | transpose(v0, v1, &X, &Y); |
40 | Sk2f invlength = (X*X + Y*Y).rsqrt(); |
41 | Sk2f::Store2(out, Y * invlength, -X * invlength); |
42 | } |
43 | |
44 | static inline float calc_curvature_costheta(const Sk2f& leftTan, const Sk2f& rightTan) { |
45 | Sk2f X, Y; |
46 | transpose(leftTan, rightTan, &X, &Y); |
47 | Sk2f invlength = (X*X + Y*Y).rsqrt(); |
48 | Sk2f dotprod = leftTan * rightTan; |
49 | return (dotprod[0] + dotprod[1]) * invlength[0] * invlength[1]; |
50 | } |
51 | |
52 | static GrCCStrokeGeometry::Verb join_verb_from_join(SkPaint::Join join) { |
53 | using Verb = GrCCStrokeGeometry::Verb; |
54 | switch (join) { |
55 | case SkPaint::kBevel_Join: |
56 | return Verb::kBevelJoin; |
57 | case SkPaint::kMiter_Join: |
58 | return Verb::kMiterJoin; |
59 | case SkPaint::kRound_Join: |
60 | return Verb::kRoundJoin; |
61 | } |
62 | SK_ABORT("Invalid SkPaint::Join." ); |
63 | } |
64 | |
65 | void GrCCStrokeGeometry::beginPath(const SkStrokeRec& stroke, float strokeDevWidth, |
66 | InstanceTallies* tallies) { |
67 | SkASSERT(!fInsideContour); |
68 | // Client should have already converted the stroke to device space (i.e. width=1 for hairline). |
69 | SkASSERT(strokeDevWidth > 0); |
70 | |
71 | fCurrStrokeRadius = strokeDevWidth/2; |
72 | fCurrStrokeJoinVerb = join_verb_from_join(stroke.getJoin()); |
73 | fCurrStrokeCapType = stroke.getCap(); |
74 | fCurrStrokeTallies = tallies; |
75 | |
76 | if (Verb::kMiterJoin == fCurrStrokeJoinVerb) { |
77 | // We implement miters by placing a triangle-shaped cap on top of a bevel join. Convert the |
78 | // "miter limit" to how tall that triangle cap can be. |
79 | float m = stroke.getMiter(); |
80 | fMiterMaxCapHeightOverWidth = .5f * SkScalarSqrt(m*m - 1); |
81 | } |
82 | |
83 | // Find the angle of curvature where the arc height above a simple line from point A to point B |
84 | // is equal to kMaxErrorFromLinearization. |
85 | float r = std::max(1 - kMaxErrorFromLinearization / fCurrStrokeRadius, 0.f); |
86 | fMaxCurvatureCosTheta = 2*r*r - 1; |
87 | |
88 | fCurrContourFirstPtIdx = -1; |
89 | fCurrContourFirstNormalIdx = -1; |
90 | |
91 | fVerbs.push_back(Verb::kBeginPath); |
92 | } |
93 | |
94 | void GrCCStrokeGeometry::moveTo(SkPoint pt) { |
95 | SkASSERT(!fInsideContour); |
96 | fCurrContourFirstPtIdx = fPoints.count(); |
97 | fCurrContourFirstNormalIdx = fNormals.count(); |
98 | fPoints.push_back(pt); |
99 | SkDEBUGCODE(fInsideContour = true); |
100 | } |
101 | |
102 | void GrCCStrokeGeometry::lineTo(SkPoint pt) { |
103 | SkASSERT(fInsideContour); |
104 | this->lineTo(fCurrStrokeJoinVerb, pt); |
105 | } |
106 | |
107 | void GrCCStrokeGeometry::lineTo(Verb leftJoinVerb, SkPoint pt) { |
108 | Sk2f tan = Sk2f::Load(&pt) - Sk2f::Load(&fPoints.back()); |
109 | if ((tan == 0).allTrue()) { |
110 | return; |
111 | } |
112 | |
113 | tan = normalize(tan); |
114 | SkVector n = SkVector::Make(tan[1], -tan[0]); |
115 | |
116 | this->recordLeftJoinIfNotEmpty(leftJoinVerb, n); |
117 | fNormals.push_back(n); |
118 | |
119 | this->recordStroke(Verb::kLinearStroke, 0); |
120 | fPoints.push_back(pt); |
121 | } |
122 | |
123 | void GrCCStrokeGeometry::quadraticTo(const SkPoint P[3]) { |
124 | SkASSERT(fInsideContour); |
125 | this->quadraticTo(fCurrStrokeJoinVerb, P, SkFindQuadMaxCurvature(P)); |
126 | } |
127 | |
128 | // Wang's formula for quadratics (1985) gives us the number of evenly spaced (in the parametric |
129 | // sense) line segments that are guaranteed to be within a distance of "kMaxErrorFromLinearization" |
130 | // from the actual curve. |
131 | static inline float wangs_formula_quadratic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2) { |
132 | static constexpr float k = 2 / (8 * kMaxErrorFromLinearization); |
133 | float f = SkScalarSqrt(k * length(p2 - p1*2 + p0)); |
134 | return SkScalarCeilToInt(f); |
135 | } |
136 | |
137 | void GrCCStrokeGeometry::quadraticTo(Verb leftJoinVerb, const SkPoint P[3], float maxCurvatureT) { |
138 | Sk2f p0 = Sk2f::Load(P); |
139 | Sk2f p1 = Sk2f::Load(P+1); |
140 | Sk2f p2 = Sk2f::Load(P+2); |
141 | |
142 | Sk2f tan0 = p1 - p0; |
143 | Sk2f tan1 = p2 - p1; |
144 | |
145 | // Snap to a "lineTo" if the control point is so close to an endpoint that FP error will become |
146 | // an issue. |
147 | if ((tan0.abs() < SK_ScalarNearlyZero).allTrue() || // p0 ~= p1 |
148 | (tan1.abs() < SK_ScalarNearlyZero).allTrue()) { // p1 ~= p2 |
149 | this->lineTo(leftJoinVerb, P[2]); |
150 | return; |
151 | } |
152 | |
153 | SkPoint normals[2]; |
154 | normalize2(tan0, tan1, normals); |
155 | |
156 | // Decide how many flat line segments to chop the curve into. |
157 | int numSegments = wangs_formula_quadratic(p0, p1, p2); |
158 | numSegments = std::min(numSegments, 1 << kMaxNumLinearSegmentsLog2); |
159 | if (numSegments <= 1) { |
160 | this->rotateTo(leftJoinVerb, normals[0]); |
161 | this->lineTo(Verb::kInternalRoundJoin, P[2]); |
162 | this->rotateTo(Verb::kInternalRoundJoin, normals[1]); |
163 | return; |
164 | } |
165 | |
166 | // At + B gives a vector tangent to the quadratic. |
167 | Sk2f A = p0 - p1*2 + p2; |
168 | Sk2f B = p1 - p0; |
169 | |
170 | // Find a line segment that crosses max curvature. |
171 | float segmentLength = SkScalarInvert(numSegments); |
172 | float leftT = maxCurvatureT - segmentLength/2; |
173 | float rightT = maxCurvatureT + segmentLength/2; |
174 | Sk2f leftTan, rightTan; |
175 | if (leftT <= 0) { |
176 | leftT = 0; |
177 | leftTan = tan0; |
178 | rightT = segmentLength; |
179 | rightTan = A*rightT + B; |
180 | } else if (rightT >= 1) { |
181 | leftT = 1 - segmentLength; |
182 | leftTan = A*leftT + B; |
183 | rightT = 1; |
184 | rightTan = tan1; |
185 | } else { |
186 | leftTan = A*leftT + B; |
187 | rightTan = A*rightT + B; |
188 | } |
189 | |
190 | // Check if curvature is too strong for a triangle strip on the line segment that crosses max |
191 | // curvature. If it is, we will chop and convert the segment to a "lineTo" with round joins. |
192 | // |
193 | // FIXME: This is quite costly and the vast majority of curves only have moderate curvature. We |
194 | // would benefit significantly from a quick reject that detects curves that don't need special |
195 | // treatment for strong curvature. |
196 | bool isCurvatureTooStrong = calc_curvature_costheta(leftTan, rightTan) < fMaxCurvatureCosTheta; |
197 | if (isCurvatureTooStrong) { |
198 | SkPoint ptsBuffer[5]; |
199 | const SkPoint* currQuadratic = P; |
200 | |
201 | if (leftT > 0) { |
202 | SkChopQuadAt(currQuadratic, ptsBuffer, leftT); |
203 | this->quadraticTo(leftJoinVerb, ptsBuffer, /*maxCurvatureT=*/1); |
204 | if (rightT < 1) { |
205 | rightT = (rightT - leftT) / (1 - leftT); |
206 | } |
207 | currQuadratic = ptsBuffer + 2; |
208 | } else { |
209 | this->rotateTo(leftJoinVerb, normals[0]); |
210 | } |
211 | |
212 | if (rightT < 1) { |
213 | SkChopQuadAt(currQuadratic, ptsBuffer, rightT); |
214 | this->lineTo(Verb::kInternalRoundJoin, ptsBuffer[2]); |
215 | this->quadraticTo(Verb::kInternalRoundJoin, ptsBuffer + 2, /*maxCurvatureT=*/0); |
216 | } else { |
217 | this->lineTo(Verb::kInternalRoundJoin, currQuadratic[2]); |
218 | this->rotateTo(Verb::kInternalRoundJoin, normals[1]); |
219 | } |
220 | return; |
221 | } |
222 | |
223 | this->recordLeftJoinIfNotEmpty(leftJoinVerb, normals[0]); |
224 | fNormals.push_back_n(2, normals); |
225 | |
226 | this->recordStroke(Verb::kQuadraticStroke, SkNextLog2(numSegments)); |
227 | p1.store(&fPoints.push_back()); |
228 | p2.store(&fPoints.push_back()); |
229 | } |
230 | |
231 | void GrCCStrokeGeometry::cubicTo(const SkPoint P[4]) { |
232 | SkASSERT(fInsideContour); |
233 | float roots[3]; |
234 | int numRoots = SkFindCubicMaxCurvature(P, roots); |
235 | this->cubicTo(fCurrStrokeJoinVerb, P, |
236 | numRoots > 0 ? roots[numRoots/2] : 0, |
237 | numRoots > 1 ? roots[0] : kLeftMaxCurvatureNone, |
238 | numRoots > 2 ? roots[2] : kRightMaxCurvatureNone); |
239 | } |
240 | |
241 | // Wang's formula for cubics (1985) gives us the number of evenly spaced (in the parametric sense) |
242 | // line segments that are guaranteed to be within a distance of "kMaxErrorFromLinearization" |
243 | // from the actual curve. |
244 | static inline float wangs_formula_cubic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, |
245 | const Sk2f& p3) { |
246 | static constexpr float k = (3 * 2) / (8 * kMaxErrorFromLinearization); |
247 | float f = SkScalarSqrt(k * length(Sk2f::Max((p2 - p1*2 + p0).abs(), |
248 | (p3 - p2*2 + p1).abs()))); |
249 | return SkScalarCeilToInt(f); |
250 | } |
251 | |
252 | void GrCCStrokeGeometry::cubicTo(Verb leftJoinVerb, const SkPoint P[4], float maxCurvatureT, |
253 | float leftMaxCurvatureT, float rightMaxCurvatureT) { |
254 | Sk2f p0 = Sk2f::Load(P); |
255 | Sk2f p1 = Sk2f::Load(P+1); |
256 | Sk2f p2 = Sk2f::Load(P+2); |
257 | Sk2f p3 = Sk2f::Load(P+3); |
258 | |
259 | Sk2f tan0 = p1 - p0; |
260 | Sk2f tan1 = p3 - p2; |
261 | |
262 | // Snap control points to endpoints if they are so close that FP error will become an issue. |
263 | if ((tan0.abs() < SK_ScalarNearlyZero).allTrue()) { // p0 ~= p1 |
264 | p1 = p0; |
265 | tan0 = p2 - p0; |
266 | if ((tan0.abs() < SK_ScalarNearlyZero).allTrue()) { // p0 ~= p1 ~= p2 |
267 | this->lineTo(leftJoinVerb, P[3]); |
268 | return; |
269 | } |
270 | } |
271 | if ((tan1.abs() < SK_ScalarNearlyZero).allTrue()) { // p2 ~= p3 |
272 | p2 = p3; |
273 | tan1 = p3 - p1; |
274 | if ((tan1.abs() < SK_ScalarNearlyZero).allTrue() || // p1 ~= p2 ~= p3 |
275 | (p0 == p1).allTrue()) { // p0 ~= p1 AND p2 ~= p3 |
276 | this->lineTo(leftJoinVerb, P[3]); |
277 | return; |
278 | } |
279 | } |
280 | |
281 | SkPoint normals[2]; |
282 | normalize2(tan0, tan1, normals); |
283 | |
284 | // Decide how many flat line segments to chop the curve into. |
285 | int numSegments = wangs_formula_cubic(p0, p1, p2, p3); |
286 | numSegments = std::min(numSegments, 1 << kMaxNumLinearSegmentsLog2); |
287 | if (numSegments <= 1) { |
288 | this->rotateTo(leftJoinVerb, normals[0]); |
289 | this->lineTo(leftJoinVerb, P[3]); |
290 | this->rotateTo(Verb::kInternalRoundJoin, normals[1]); |
291 | return; |
292 | } |
293 | |
294 | // At^2 + Bt + C gives a vector tangent to the cubic. (More specifically, it's the derivative |
295 | // minus an irrelevant scale by 3, since all we care about is the direction.) |
296 | Sk2f A = p3 + (p1 - p2)*3 - p0; |
297 | Sk2f B = (p0 - p1*2 + p2)*2; |
298 | Sk2f C = p1 - p0; |
299 | |
300 | // Find a line segment that crosses max curvature. |
301 | float segmentLength = SkScalarInvert(numSegments); |
302 | float leftT = maxCurvatureT - segmentLength/2; |
303 | float rightT = maxCurvatureT + segmentLength/2; |
304 | Sk2f leftTan, rightTan; |
305 | if (leftT <= 0) { |
306 | leftT = 0; |
307 | leftTan = tan0; |
308 | rightT = segmentLength; |
309 | rightTan = A*rightT*rightT + B*rightT + C; |
310 | } else if (rightT >= 1) { |
311 | leftT = 1 - segmentLength; |
312 | leftTan = A*leftT*leftT + B*leftT + C; |
313 | rightT = 1; |
314 | rightTan = tan1; |
315 | } else { |
316 | leftTan = A*leftT*leftT + B*leftT + C; |
317 | rightTan = A*rightT*rightT + B*rightT + C; |
318 | } |
319 | |
320 | // Check if curvature is too strong for a triangle strip on the line segment that crosses max |
321 | // curvature. If it is, we will chop and convert the segment to a "lineTo" with round joins. |
322 | // |
323 | // FIXME: This is quite costly and the vast majority of curves only have moderate curvature. We |
324 | // would benefit significantly from a quick reject that detects curves that don't need special |
325 | // treatment for strong curvature. |
326 | bool isCurvatureTooStrong = calc_curvature_costheta(leftTan, rightTan) < fMaxCurvatureCosTheta; |
327 | if (isCurvatureTooStrong) { |
328 | SkPoint ptsBuffer[7]; |
329 | p0.store(ptsBuffer); |
330 | p1.store(ptsBuffer + 1); |
331 | p2.store(ptsBuffer + 2); |
332 | p3.store(ptsBuffer + 3); |
333 | const SkPoint* currCubic = ptsBuffer; |
334 | |
335 | if (leftT > 0) { |
336 | SkChopCubicAt(currCubic, ptsBuffer, leftT); |
337 | this->cubicTo(leftJoinVerb, ptsBuffer, /*maxCurvatureT=*/1, |
338 | (kLeftMaxCurvatureNone != leftMaxCurvatureT) |
339 | ? leftMaxCurvatureT/leftT : kLeftMaxCurvatureNone, |
340 | kRightMaxCurvatureNone); |
341 | if (rightT < 1) { |
342 | rightT = (rightT - leftT) / (1 - leftT); |
343 | } |
344 | if (rightMaxCurvatureT < 1 && kRightMaxCurvatureNone != rightMaxCurvatureT) { |
345 | rightMaxCurvatureT = (rightMaxCurvatureT - leftT) / (1 - leftT); |
346 | } |
347 | currCubic = ptsBuffer + 3; |
348 | } else { |
349 | this->rotateTo(leftJoinVerb, normals[0]); |
350 | } |
351 | |
352 | if (rightT < 1) { |
353 | SkChopCubicAt(currCubic, ptsBuffer, rightT); |
354 | this->lineTo(Verb::kInternalRoundJoin, ptsBuffer[3]); |
355 | currCubic = ptsBuffer + 3; |
356 | this->cubicTo(Verb::kInternalRoundJoin, currCubic, /*maxCurvatureT=*/0, |
357 | kLeftMaxCurvatureNone, kRightMaxCurvatureNone); |
358 | } else { |
359 | this->lineTo(Verb::kInternalRoundJoin, currCubic[3]); |
360 | this->rotateTo(Verb::kInternalRoundJoin, normals[1]); |
361 | } |
362 | return; |
363 | } |
364 | |
365 | // Recurse and check the other two points of max curvature, if any. |
366 | if (kRightMaxCurvatureNone != rightMaxCurvatureT) { |
367 | this->cubicTo(leftJoinVerb, P, rightMaxCurvatureT, leftMaxCurvatureT, |
368 | kRightMaxCurvatureNone); |
369 | return; |
370 | } |
371 | if (kLeftMaxCurvatureNone != leftMaxCurvatureT) { |
372 | SkASSERT(kRightMaxCurvatureNone == rightMaxCurvatureT); |
373 | this->cubicTo(leftJoinVerb, P, leftMaxCurvatureT, kLeftMaxCurvatureNone, |
374 | kRightMaxCurvatureNone); |
375 | return; |
376 | } |
377 | |
378 | this->recordLeftJoinIfNotEmpty(leftJoinVerb, normals[0]); |
379 | fNormals.push_back_n(2, normals); |
380 | |
381 | this->recordStroke(Verb::kCubicStroke, SkNextLog2(numSegments)); |
382 | p1.store(&fPoints.push_back()); |
383 | p2.store(&fPoints.push_back()); |
384 | p3.store(&fPoints.push_back()); |
385 | } |
386 | |
387 | void GrCCStrokeGeometry::recordStroke(Verb verb, int numSegmentsLog2) { |
388 | SkASSERT(Verb::kLinearStroke != verb || 0 == numSegmentsLog2); |
389 | SkASSERT(numSegmentsLog2 <= kMaxNumLinearSegmentsLog2); |
390 | fVerbs.push_back(verb); |
391 | if (Verb::kLinearStroke != verb) { |
392 | SkASSERT(numSegmentsLog2 > 0); |
393 | fParams.push_back().fNumLinearSegmentsLog2 = numSegmentsLog2; |
394 | } |
395 | ++fCurrStrokeTallies->fStrokes[numSegmentsLog2]; |
396 | } |
397 | |
398 | void GrCCStrokeGeometry::rotateTo(Verb leftJoinVerb, SkVector normal) { |
399 | this->recordLeftJoinIfNotEmpty(leftJoinVerb, normal); |
400 | fNormals.push_back(normal); |
401 | } |
402 | |
403 | void GrCCStrokeGeometry::recordLeftJoinIfNotEmpty(Verb joinVerb, SkVector nextNormal) { |
404 | if (fNormals.count() <= fCurrContourFirstNormalIdx) { |
405 | // The contour is empty. Nothing to join with. |
406 | SkASSERT(fNormals.count() == fCurrContourFirstNormalIdx); |
407 | return; |
408 | } |
409 | |
410 | if (Verb::kBevelJoin == joinVerb) { |
411 | this->recordBevelJoin(Verb::kBevelJoin); |
412 | return; |
413 | } |
414 | |
415 | Sk2f n0 = Sk2f::Load(&fNormals.back()); |
416 | Sk2f n1 = Sk2f::Load(&nextNormal); |
417 | Sk2f base = n1 - n0; |
418 | if ((base.abs() * fCurrStrokeRadius < kMaxErrorFromLinearization).allTrue()) { |
419 | // Treat any join as a bevel when the outside corners of the two adjoining strokes are |
420 | // close enough to each other. This is important because "miterCapHeightOverWidth" becomes |
421 | // unstable when n0 and n1 are nearly equal. |
422 | this->recordBevelJoin(joinVerb); |
423 | return; |
424 | } |
425 | |
426 | // We implement miters and round joins by placing a triangle-shaped cap on top of a bevel join. |
427 | // (For round joins this triangle cap comprises the conic control points.) Find how tall to make |
428 | // this triangle cap, relative to its width. |
429 | // |
430 | // NOTE: This value would be infinite at 180 degrees, but we clamp miterCapHeightOverWidth at |
431 | // near-infinity. 180-degree round joins still look perfectly acceptable like this (though |
432 | // technically not pure arcs). |
433 | Sk2f cross = base * SkNx_shuffle<1,0>(n0); |
434 | Sk2f dot = base * n0; |
435 | float miterCapHeight = SkScalarAbs(dot[0] + dot[1]); |
436 | float miterCapWidth = SkScalarAbs(cross[0] - cross[1]) * 2; |
437 | |
438 | if (Verb::kMiterJoin == joinVerb) { |
439 | if (miterCapHeight > fMiterMaxCapHeightOverWidth * miterCapWidth) { |
440 | // This join is tighter than the miter limit. Treat it as a bevel. |
441 | this->recordBevelJoin(Verb::kMiterJoin); |
442 | return; |
443 | } |
444 | this->recordMiterJoin(miterCapHeight / miterCapWidth); |
445 | return; |
446 | } |
447 | |
448 | SkASSERT(Verb::kRoundJoin == joinVerb || Verb::kInternalRoundJoin == joinVerb); |
449 | |
450 | // Conic arcs become unstable when they approach 180 degrees. When the conic control point |
451 | // begins shooting off to infinity (i.e., height/width > 32), split the conic into two. |
452 | static constexpr float kAlmost180Degrees = 32; |
453 | if (miterCapHeight > kAlmost180Degrees * miterCapWidth) { |
454 | Sk2f bisect = normalize(n0 - n1); |
455 | this->rotateTo(joinVerb, SkVector::Make(-bisect[1], bisect[0])); |
456 | this->recordLeftJoinIfNotEmpty(joinVerb, nextNormal); |
457 | return; |
458 | } |
459 | |
460 | float miterCapHeightOverWidth = miterCapHeight / miterCapWidth; |
461 | |
462 | // Find the heights of this round join's conic control point as well as the arc itself. |
463 | Sk2f X, Y; |
464 | transpose(base * base, n0 * n1, &X, &Y); |
465 | Sk2f r = Sk2f::Max(X + Y + Sk2f(0, 1), 0.f).sqrt(); |
466 | Sk2f heights = SkNx_fma(r, Sk2f(miterCapHeightOverWidth, -SK_ScalarRoot2Over2), Sk2f(0, 1)); |
467 | float controlPointHeight = SkScalarAbs(heights[0]); |
468 | float curveHeight = heights[1]; |
469 | if (curveHeight * fCurrStrokeRadius < kMaxErrorFromLinearization) { |
470 | // Treat round joins as bevels when their curvature is nearly flat. |
471 | this->recordBevelJoin(joinVerb); |
472 | return; |
473 | } |
474 | |
475 | float w = curveHeight / (controlPointHeight - curveHeight); |
476 | this->recordRoundJoin(joinVerb, miterCapHeightOverWidth, w); |
477 | } |
478 | |
479 | void GrCCStrokeGeometry::recordBevelJoin(Verb originalJoinVerb) { |
480 | if (!IsInternalJoinVerb(originalJoinVerb)) { |
481 | fVerbs.push_back(Verb::kBevelJoin); |
482 | ++fCurrStrokeTallies->fTriangles; |
483 | } else { |
484 | fVerbs.push_back(Verb::kInternalBevelJoin); |
485 | fCurrStrokeTallies->fTriangles += 2; |
486 | } |
487 | } |
488 | |
489 | void GrCCStrokeGeometry::recordMiterJoin(float miterCapHeightOverWidth) { |
490 | fVerbs.push_back(Verb::kMiterJoin); |
491 | fParams.push_back().fMiterCapHeightOverWidth = miterCapHeightOverWidth; |
492 | fCurrStrokeTallies->fTriangles += 2; |
493 | } |
494 | |
495 | void GrCCStrokeGeometry::recordRoundJoin(Verb joinVerb, float miterCapHeightOverWidth, |
496 | float conicWeight) { |
497 | fVerbs.push_back(joinVerb); |
498 | fParams.push_back().fConicWeight = conicWeight; |
499 | fParams.push_back().fMiterCapHeightOverWidth = miterCapHeightOverWidth; |
500 | if (Verb::kRoundJoin == joinVerb) { |
501 | ++fCurrStrokeTallies->fTriangles; |
502 | ++fCurrStrokeTallies->fConics; |
503 | } else { |
504 | SkASSERT(Verb::kInternalRoundJoin == joinVerb); |
505 | fCurrStrokeTallies->fTriangles += 2; |
506 | fCurrStrokeTallies->fConics += 2; |
507 | } |
508 | } |
509 | |
510 | void GrCCStrokeGeometry::closeContour() { |
511 | SkASSERT(fInsideContour); |
512 | SkASSERT(fPoints.count() > fCurrContourFirstPtIdx); |
513 | if (fPoints.back() != fPoints[fCurrContourFirstPtIdx]) { |
514 | // Draw a line back to the beginning. |
515 | this->lineTo(fCurrStrokeJoinVerb, fPoints[fCurrContourFirstPtIdx]); |
516 | } |
517 | if (fNormals.count() > fCurrContourFirstNormalIdx) { |
518 | // Join the first and last lines. |
519 | this->rotateTo(fCurrStrokeJoinVerb,fNormals[fCurrContourFirstNormalIdx]); |
520 | } else { |
521 | // This contour is empty. Add a bogus normal since the iterator always expects one. |
522 | SkASSERT(fNormals.count() == fCurrContourFirstNormalIdx); |
523 | fNormals.push_back({0, 0}); |
524 | } |
525 | fVerbs.push_back(Verb::kEndContour); |
526 | SkDEBUGCODE(fInsideContour = false); |
527 | } |
528 | |
529 | void GrCCStrokeGeometry::capContourAndExit() { |
530 | SkASSERT(fInsideContour); |
531 | if (fCurrContourFirstNormalIdx >= fNormals.count()) { |
532 | // This contour is empty. Add a normal in the direction that caps orient on empty geometry. |
533 | SkASSERT(fNormals.count() == fCurrContourFirstNormalIdx); |
534 | fNormals.push_back({1, 0}); |
535 | } |
536 | |
537 | this->recordCapsIfAny(); |
538 | fVerbs.push_back(Verb::kEndContour); |
539 | |
540 | SkDEBUGCODE(fInsideContour = false); |
541 | } |
542 | |
543 | void GrCCStrokeGeometry::recordCapsIfAny() { |
544 | SkASSERT(fInsideContour); |
545 | SkASSERT(fCurrContourFirstNormalIdx < fNormals.count()); |
546 | |
547 | if (SkPaint::kButt_Cap == fCurrStrokeCapType) { |
548 | return; |
549 | } |
550 | |
551 | Verb capVerb; |
552 | if (SkPaint::kSquare_Cap == fCurrStrokeCapType) { |
553 | if (fCurrStrokeRadius * SK_ScalarRoot2Over2 < kMaxErrorFromLinearization) { |
554 | return; |
555 | } |
556 | capVerb = Verb::kSquareCap; |
557 | fCurrStrokeTallies->fStrokes[0] += 2; |
558 | } else { |
559 | SkASSERT(SkPaint::kRound_Cap == fCurrStrokeCapType); |
560 | if (fCurrStrokeRadius < kMaxErrorFromLinearization) { |
561 | return; |
562 | } |
563 | capVerb = Verb::kRoundCap; |
564 | fCurrStrokeTallies->fTriangles += 2; |
565 | fCurrStrokeTallies->fConics += 4; |
566 | } |
567 | |
568 | fVerbs.push_back(capVerb); |
569 | fVerbs.push_back(Verb::kEndContour); |
570 | |
571 | fVerbs.push_back(capVerb); |
572 | |
573 | // Reserve the space first, since push_back() takes the point by reference and might |
574 | // invalidate the reference if the array grows. |
575 | fPoints.reserve(fPoints.count() + 1); |
576 | fPoints.push_back(fPoints[fCurrContourFirstPtIdx]); |
577 | |
578 | // Reserve the space first, since push_back() takes the normal by reference and might |
579 | // invalidate the reference if the array grows. (Although in this case we should be fine |
580 | // since there is a negate operator.) |
581 | fNormals.reserve(fNormals.count() + 1); |
582 | fNormals.push_back(-fNormals[fCurrContourFirstNormalIdx]); |
583 | } |
584 | |