/src/skia/src/core/SkScan_Hairline.cpp
Line | Count | Source (jump to first uncovered line) |
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/core/SkPaint.h" |
9 | | #include "src/core/SkBlitter.h" |
10 | | #include "src/core/SkFDot6.h" |
11 | | #include "src/core/SkLineClipper.h" |
12 | | #include "src/core/SkMathPriv.h" |
13 | | #include "src/core/SkPathPriv.h" |
14 | | #include "src/core/SkRasterClip.h" |
15 | | #include "src/core/SkScan.h" |
16 | | |
17 | | #include <utility> |
18 | | |
19 | | static void horiline(int x, int stopx, SkFixed fy, SkFixed dy, |
20 | 131k | SkBlitter* blitter) { |
21 | 131k | SkASSERT(x < stopx); |
22 | | |
23 | 300k | do { |
24 | 300k | blitter->blitH(x, fy >> 16, 1); |
25 | 300k | fy += dy; |
26 | 300k | } while (++x < stopx); |
27 | 131k | } |
28 | | |
29 | | static void vertline(int y, int stopy, SkFixed fx, SkFixed dx, |
30 | 175k | SkBlitter* blitter) { |
31 | 175k | SkASSERT(y < stopy); |
32 | | |
33 | 948k | do { |
34 | 948k | blitter->blitH(fx >> 16, y, 1); |
35 | 948k | fx += dx; |
36 | 948k | } while (++y < stopy); |
37 | 175k | } |
38 | | |
39 | | #ifdef SK_DEBUG |
40 | 0 | static bool canConvertFDot6ToFixed(SkFDot6 x) { |
41 | 0 | const int maxDot6 = SK_MaxS32 >> (16 - 6); |
42 | 0 | return SkAbs32(x) <= maxDot6; |
43 | 0 | } |
44 | | #endif |
45 | | |
46 | | void SkScan::HairLineRgn(const SkPoint array[], int arrayCount, const SkRegion* clip, |
47 | 8.03M | SkBlitter* origBlitter) { |
48 | 8.03M | SkBlitterClipper clipper; |
49 | 8.03M | SkIRect clipR, ptsR; |
50 | | |
51 | 8.03M | const SkScalar max = SkIntToScalar(32767); |
52 | 8.03M | const SkRect fixedBounds = SkRect::MakeLTRB(-max, -max, max, max); |
53 | | |
54 | 8.03M | SkRect clipBounds; |
55 | 8.03M | if (clip) { |
56 | 7.81M | clipBounds.set(clip->getBounds()); |
57 | 7.81M | } |
58 | | |
59 | 16.7M | for (int i = 0; i < arrayCount - 1; ++i) { |
60 | 8.70M | SkBlitter* blitter = origBlitter; |
61 | | |
62 | 8.70M | SkPoint pts[2]; |
63 | | |
64 | | // We have to pre-clip the line to fit in a SkFixed, so we just chop |
65 | | // the line. TODO find a way to actually draw beyond that range. |
66 | 8.70M | if (!SkLineClipper::IntersectLine(&array[i], fixedBounds, pts)) { |
67 | 6.96M | continue; |
68 | 6.96M | } |
69 | | |
70 | | // Perform a clip in scalar space, so we catch huge values which might |
71 | | // be missed after we convert to SkFDot6 (overflow) |
72 | 1.74M | if (clip && !SkLineClipper::IntersectLine(pts, clipBounds, pts)) { |
73 | 983k | continue; |
74 | 983k | } |
75 | | |
76 | 760k | SkFDot6 x0 = SkScalarToFDot6(pts[0].fX); |
77 | 760k | SkFDot6 y0 = SkScalarToFDot6(pts[0].fY); |
78 | 760k | SkFDot6 x1 = SkScalarToFDot6(pts[1].fX); |
79 | 760k | SkFDot6 y1 = SkScalarToFDot6(pts[1].fY); |
80 | | |
81 | 760k | SkASSERT(canConvertFDot6ToFixed(x0)); |
82 | 760k | SkASSERT(canConvertFDot6ToFixed(y0)); |
83 | 760k | SkASSERT(canConvertFDot6ToFixed(x1)); |
84 | 760k | SkASSERT(canConvertFDot6ToFixed(y1)); |
85 | | |
86 | 760k | if (clip) { |
87 | | // now perform clipping again, as the rounding to dot6 can wiggle us |
88 | | // our rects are really dot6 rects, but since we've already used |
89 | | // lineclipper, we know they will fit in 32bits (26.6) |
90 | 378k | const SkIRect& bounds = clip->getBounds(); |
91 | | |
92 | 378k | clipR.setLTRB(SkIntToFDot6(bounds.fLeft), SkIntToFDot6(bounds.fTop), |
93 | 378k | SkIntToFDot6(bounds.fRight), SkIntToFDot6(bounds.fBottom)); |
94 | 378k | ptsR.setLTRB(x0, y0, x1, y1); |
95 | 378k | ptsR.sort(); |
96 | | |
97 | | // outset the right and bottom, to account for how hairlines are |
98 | | // actually drawn, which may hit the pixel to the right or below of |
99 | | // the coordinate |
100 | 378k | ptsR.fRight += SK_FDot6One; |
101 | 378k | ptsR.fBottom += SK_FDot6One; |
102 | | |
103 | 378k | if (!SkIRect::Intersects(ptsR, clipR)) { |
104 | 93 | continue; |
105 | 93 | } |
106 | 378k | if (!clip->isRect() || !clipR.contains(ptsR)) { |
107 | 2.30k | blitter = clipper.apply(origBlitter, clip); |
108 | 2.30k | } |
109 | 378k | } |
110 | | |
111 | 760k | SkFDot6 dx = x1 - x0; |
112 | 760k | SkFDot6 dy = y1 - y0; |
113 | | |
114 | 760k | if (SkAbs32(dx) > SkAbs32(dy)) { // mostly horizontal |
115 | 202k | if (x0 > x1) { // we want to go left-to-right |
116 | 123k | using std::swap; |
117 | 123k | swap(x0, x1); |
118 | 123k | swap(y0, y1); |
119 | 123k | } |
120 | 202k | int ix0 = SkFDot6Round(x0); |
121 | 202k | int ix1 = SkFDot6Round(x1); |
122 | 202k | if (ix0 == ix1) {// too short to draw |
123 | 70.9k | continue; |
124 | 70.9k | } |
125 | | #if defined(SK_BUILD_FOR_FUZZER) |
126 | | if ((ix1 - ix0) > 100000 || (ix1 - ix0) < 0) { |
127 | | continue; // too big to draw |
128 | | } |
129 | | #endif |
130 | 131k | SkFixed slope = SkFixedDiv(dy, dx); |
131 | 131k | SkFixed startY = SkFDot6ToFixed(y0) + (slope * ((32 - x0) & 63) >> 6); |
132 | | |
133 | 131k | horiline(ix0, ix1, startY, slope, blitter); |
134 | 558k | } else { // mostly vertical |
135 | 558k | if (y0 > y1) { // we want to go top-to-bottom |
136 | 147k | using std::swap; |
137 | 147k | swap(x0, x1); |
138 | 147k | swap(y0, y1); |
139 | 147k | } |
140 | 558k | int iy0 = SkFDot6Round(y0); |
141 | 558k | int iy1 = SkFDot6Round(y1); |
142 | 558k | if (iy0 == iy1) { // too short to draw |
143 | 382k | continue; |
144 | 382k | } |
145 | | #if defined(SK_BUILD_FOR_FUZZER) |
146 | | if ((iy1 - iy0) > 100000 || (iy1 - iy0) < 0) { |
147 | | continue; // too big to draw |
148 | | } |
149 | | #endif |
150 | 175k | SkFixed slope = SkFixedDiv(dx, dy); |
151 | 175k | SkFixed startX = SkFDot6ToFixed(x0) + (slope * ((32 - y0) & 63) >> 6); |
152 | | |
153 | 175k | vertline(iy0, iy1, startX, slope, blitter); |
154 | 175k | } |
155 | 760k | } |
156 | 8.03M | } |
157 | | |
158 | | // we don't just draw 4 lines, 'cause that can leave a gap in the bottom-right |
159 | | // and double-hit the top-left. |
160 | 20 | void SkScan::HairRect(const SkRect& rect, const SkRasterClip& clip, SkBlitter* blitter) { |
161 | 20 | SkAAClipBlitterWrapper wrapper; |
162 | 20 | SkBlitterClipper clipper; |
163 | | // Create the enclosing bounds of the hairrect. i.e. we will stroke the interior of r. |
164 | 20 | SkIRect r = SkIRect::MakeLTRB(SkScalarFloorToInt(rect.fLeft), |
165 | 20 | SkScalarFloorToInt(rect.fTop), |
166 | 20 | SkScalarFloorToInt(rect.fRight + 1), |
167 | 20 | SkScalarFloorToInt(rect.fBottom + 1)); |
168 | | |
169 | | // Note: r might be crazy big, if rect was huge, possibly getting pinned to max/min s32. |
170 | | // We need to trim it back to something reasonable before we can query its width etc. |
171 | | // since r.fRight - r.fLeft might wrap around to negative even if fRight > fLeft. |
172 | | // |
173 | | // We outset the clip bounds by 1 before intersecting, since r is being stroked and not filled |
174 | | // so we don't want to pin an edge of it to the clip. The intersect's job is mostly to just |
175 | | // get the actual edge values into a reasonable range (e.g. so width() can't overflow). |
176 | 20 | if (!r.intersect(clip.getBounds().makeOutset(1, 1))) { |
177 | 0 | return; |
178 | 0 | } |
179 | | |
180 | 20 | if (clip.quickReject(r)) { |
181 | 1 | return; |
182 | 1 | } |
183 | 19 | if (!clip.quickContains(r)) { |
184 | 9 | const SkRegion* clipRgn; |
185 | 9 | if (clip.isBW()) { |
186 | 9 | clipRgn = &clip.bwRgn(); |
187 | 0 | } else { |
188 | 0 | wrapper.init(clip, blitter); |
189 | 0 | clipRgn = &wrapper.getRgn(); |
190 | 0 | blitter = wrapper.getBlitter(); |
191 | 0 | } |
192 | 9 | blitter = clipper.apply(blitter, clipRgn); |
193 | 9 | } |
194 | | |
195 | 19 | int width = r.width(); |
196 | 19 | int height = r.height(); |
197 | | |
198 | 19 | if ((width | height) == 0) { |
199 | 0 | return; |
200 | 0 | } |
201 | 19 | if (width <= 2 || height <= 2) { |
202 | 12 | blitter->blitRect(r.fLeft, r.fTop, width, height); |
203 | 12 | return; |
204 | 12 | } |
205 | | // if we get here, we know we have 4 segments to draw |
206 | 7 | blitter->blitH(r.fLeft, r.fTop, width); // top |
207 | 7 | blitter->blitRect(r.fLeft, r.fTop + 1, 1, height - 2); // left |
208 | 7 | blitter->blitRect(r.fRight - 1, r.fTop + 1, 1, height - 2); // right |
209 | 7 | blitter->blitH(r.fLeft, r.fBottom - 1, width); // bottom |
210 | 7 | } |
211 | | |
212 | | /////////////////////////////////////////////////////////////////////////////// |
213 | | |
214 | | #include "include/core/SkPath.h" |
215 | | #include "include/private/SkNx.h" |
216 | | #include "src/core/SkGeometry.h" |
217 | | |
218 | 887k | #define kMaxCubicSubdivideLevel 9 |
219 | 21.9M | #define kMaxQuadSubdivideLevel 5 |
220 | | |
221 | 21.5M | static uint32_t compute_int_quad_dist(const SkPoint pts[3]) { |
222 | | // compute the vector between the control point ([1]) and the middle of the |
223 | | // line connecting the start and end ([0] and [2]) |
224 | 21.5M | SkScalar dx = SkScalarHalf(pts[0].fX + pts[2].fX) - pts[1].fX; |
225 | 21.5M | SkScalar dy = SkScalarHalf(pts[0].fY + pts[2].fY) - pts[1].fY; |
226 | | // we want everyone to be positive |
227 | 21.5M | dx = SkScalarAbs(dx); |
228 | 21.5M | dy = SkScalarAbs(dy); |
229 | | // convert to whole pixel values (use ceiling to be conservative). |
230 | | // assign to unsigned so we can safely add 1/2 of the smaller and still fit in |
231 | | // uint32_t, since SkScalarCeilToInt() returns 31 bits at most. |
232 | 21.5M | uint32_t idx = SkScalarCeilToInt(dx); |
233 | 21.5M | uint32_t idy = SkScalarCeilToInt(dy); |
234 | | // use the cheap approx for distance |
235 | 21.5M | if (idx > idy) { |
236 | 3.26M | return idx + (idy >> 1); |
237 | 18.2M | } else { |
238 | 18.2M | return idy + (idx >> 1); |
239 | 18.2M | } |
240 | 21.5M | } |
241 | | |
242 | | static void hair_quad(const SkPoint pts[3], const SkRegion* clip, |
243 | 495k | SkBlitter* blitter, int level, SkScan::HairRgnProc lineproc) { |
244 | 495k | SkASSERT(level <= kMaxQuadSubdivideLevel); |
245 | | |
246 | 495k | SkQuadCoeff coeff(pts); |
247 | | |
248 | 495k | const int lines = 1 << level; |
249 | 495k | Sk2s t(0); |
250 | 495k | Sk2s dt(SK_Scalar1 / lines); |
251 | | |
252 | 495k | SkPoint tmp[(1 << kMaxQuadSubdivideLevel) + 1]; |
253 | 495k | SkASSERT((unsigned)lines < SK_ARRAY_COUNT(tmp)); |
254 | | |
255 | 495k | tmp[0] = pts[0]; |
256 | 495k | Sk2s A = coeff.fA; |
257 | 495k | Sk2s B = coeff.fB; |
258 | 495k | Sk2s C = coeff.fC; |
259 | 1.65M | for (int i = 1; i < lines; ++i) { |
260 | 1.16M | t = t + dt; |
261 | 1.16M | ((A * t + B) * t + C).store(&tmp[i]); |
262 | 1.16M | } |
263 | 495k | tmp[lines] = pts[2]; |
264 | 495k | lineproc(tmp, lines + 1, clip, blitter); |
265 | 495k | } |
266 | | |
267 | 21.5M | static SkRect compute_nocheck_quad_bounds(const SkPoint pts[3]) { |
268 | 21.5M | SkASSERT(SkScalarsAreFinite(&pts[0].fX, 6)); |
269 | | |
270 | 21.5M | Sk2s min = Sk2s::Load(pts); |
271 | 21.5M | Sk2s max = min; |
272 | 64.7M | for (int i = 1; i < 3; ++i) { |
273 | 43.1M | Sk2s pair = Sk2s::Load(pts+i); |
274 | 43.1M | min = Sk2s::Min(min, pair); |
275 | 43.1M | max = Sk2s::Max(max, pair); |
276 | 43.1M | } |
277 | 21.5M | return { min[0], min[1], max[0], max[1] }; |
278 | 21.5M | } |
279 | | |
280 | 15.3k | static bool is_inverted(const SkRect& r) { |
281 | 15.3k | return r.fLeft > r.fRight || r.fTop > r.fBottom; |
282 | 15.3k | } |
283 | | |
284 | | // Can't call SkRect::intersects, since it cares about empty, and we don't (since we tracking |
285 | | // something to be stroked, so empty can still draw something (e.g. horizontal line) |
286 | 22.3M | static bool geometric_overlap(const SkRect& a, const SkRect& b) { |
287 | 22.3M | SkASSERT(!is_inverted(a) && !is_inverted(b)); |
288 | 22.3M | return a.fLeft < b.fRight && b.fLeft < a.fRight && |
289 | 2.57M | a.fTop < b.fBottom && b.fTop < a.fBottom; |
290 | 22.3M | } |
291 | | |
292 | | // Can't call SkRect::contains, since it cares about empty, and we don't (since we tracking |
293 | | // something to be stroked, so empty can still draw something (e.g. horizontal line) |
294 | 549k | static bool geometric_contains(const SkRect& outer, const SkRect& inner) { |
295 | 549k | SkASSERT(!is_inverted(outer) && !is_inverted(inner)); |
296 | 549k | return inner.fRight <= outer.fRight && inner.fLeft >= outer.fLeft && |
297 | 469k | inner.fBottom <= outer.fBottom && inner.fTop >= outer.fTop; |
298 | 549k | } |
299 | | |
300 | | static inline void hairquad(const SkPoint pts[3], const SkRegion* clip, const SkRect* insetClip, const SkRect* outsetClip, |
301 | 21.5M | SkBlitter* blitter, int level, SkScan::HairRgnProc lineproc) { |
302 | 21.5M | if (insetClip) { |
303 | 21.5M | SkASSERT(outsetClip); |
304 | 21.5M | SkRect bounds = compute_nocheck_quad_bounds(pts); |
305 | 21.5M | if (!geometric_overlap(*outsetClip, bounds)) { |
306 | 21.0M | return; |
307 | 494k | } else if (geometric_contains(*insetClip, bounds)) { |
308 | 237k | clip = nullptr; |
309 | 237k | } |
310 | 21.5M | } |
311 | | |
312 | 495k | hair_quad(pts, clip, blitter, level, lineproc); |
313 | 495k | } |
314 | | |
315 | 121k | static inline Sk2s abs(const Sk2s& value) { |
316 | 121k | return Sk2s::Max(value, Sk2s(0)-value); |
317 | 121k | } |
318 | | |
319 | 60.9k | static inline SkScalar max_component(const Sk2s& value) { |
320 | 60.9k | SkScalar components[2]; |
321 | 60.9k | value.store(components); |
322 | 60.9k | return std::max(components[0], components[1]); |
323 | 60.9k | } |
324 | | |
325 | 60.9k | static inline int compute_cubic_segs(const SkPoint pts[4]) { |
326 | 60.9k | Sk2s p0 = from_point(pts[0]); |
327 | 60.9k | Sk2s p1 = from_point(pts[1]); |
328 | 60.9k | Sk2s p2 = from_point(pts[2]); |
329 | 60.9k | Sk2s p3 = from_point(pts[3]); |
330 | | |
331 | 60.9k | const Sk2s oneThird(1.0f / 3.0f); |
332 | 60.9k | const Sk2s twoThird(2.0f / 3.0f); |
333 | | |
334 | 60.9k | Sk2s p13 = oneThird * p3 + twoThird * p0; |
335 | 60.9k | Sk2s p23 = oneThird * p0 + twoThird * p3; |
336 | | |
337 | 60.9k | SkScalar diff = max_component(Sk2s::Max(abs(p1 - p13), abs(p2 - p23))); |
338 | 60.9k | SkScalar tol = SK_Scalar1 / 8; |
339 | | |
340 | 131k | for (int i = 0; i < kMaxCubicSubdivideLevel; ++i) { |
341 | 129k | if (diff < tol) { |
342 | 58.6k | return 1 << i; |
343 | 58.6k | } |
344 | 70.7k | tol *= 4; |
345 | 70.7k | } |
346 | 2.32k | return 1 << kMaxCubicSubdivideLevel; |
347 | 60.9k | } |
348 | | |
349 | 211k | static bool lt_90(SkPoint p0, SkPoint pivot, SkPoint p2) { |
350 | 211k | return SkVector::DotProduct(p0 - pivot, p2 - pivot) >= 0; |
351 | 211k | } |
352 | | |
353 | | // The off-curve points are "inside" the limits of the on-curve pts |
354 | 55.6k | static bool quick_cubic_niceness_check(const SkPoint pts[4]) { |
355 | 55.6k | return lt_90(pts[1], pts[0], pts[3]) && |
356 | 53.9k | lt_90(pts[2], pts[0], pts[3]) && |
357 | 51.3k | lt_90(pts[1], pts[3], pts[0]) && |
358 | 50.5k | lt_90(pts[2], pts[3], pts[0]); |
359 | 55.6k | } |
360 | | |
361 | | typedef SkNx<2, uint32_t> Sk2x32; |
362 | | |
363 | 1.35M | static inline Sk2x32 sk2s_is_finite(const Sk2s& x) { |
364 | 1.35M | const Sk2x32 exp_mask = Sk2x32(0xFF << 23); |
365 | 1.35M | return (Sk2x32::Load(&x) & exp_mask) != exp_mask; |
366 | 1.35M | } |
367 | | |
368 | | static void hair_cubic(const SkPoint pts[4], const SkRegion* clip, SkBlitter* blitter, |
369 | 60.9k | SkScan::HairRgnProc lineproc) { |
370 | 60.9k | const int lines = compute_cubic_segs(pts); |
371 | 60.9k | SkASSERT(lines > 0); |
372 | 60.9k | if (1 == lines) { |
373 | 28.4k | SkPoint tmp[2] = { pts[0], pts[3] }; |
374 | 28.4k | lineproc(tmp, 2, clip, blitter); |
375 | 28.4k | return; |
376 | 28.4k | } |
377 | | |
378 | 32.4k | SkCubicCoeff coeff(pts); |
379 | | |
380 | 32.4k | const Sk2s dt(SK_Scalar1 / lines); |
381 | 32.4k | Sk2s t(0); |
382 | | |
383 | 32.4k | SkPoint tmp[(1 << kMaxCubicSubdivideLevel) + 1]; |
384 | 32.4k | SkASSERT((unsigned)lines < SK_ARRAY_COUNT(tmp)); |
385 | | |
386 | 32.4k | tmp[0] = pts[0]; |
387 | 32.4k | Sk2s A = coeff.fA; |
388 | 32.4k | Sk2s B = coeff.fB; |
389 | 32.4k | Sk2s C = coeff.fC; |
390 | 32.4k | Sk2s D = coeff.fD; |
391 | 32.4k | Sk2x32 is_finite(~0); // start out as true |
392 | 1.38M | for (int i = 1; i < lines; ++i) { |
393 | 1.35M | t = t + dt; |
394 | 1.35M | Sk2s p = ((A * t + B) * t + C) * t + D; |
395 | 1.35M | is_finite &= sk2s_is_finite(p); |
396 | 1.35M | p.store(&tmp[i]); |
397 | 1.35M | } |
398 | 32.4k | if (is_finite.allTrue()) { |
399 | 32.4k | tmp[lines] = pts[3]; |
400 | 32.4k | lineproc(tmp, lines + 1, clip, blitter); |
401 | 32.4k | } // else some point(s) are non-finite, so don't draw |
402 | 32.4k | } |
403 | | |
404 | 753k | static SkRect compute_nocheck_cubic_bounds(const SkPoint pts[4]) { |
405 | 753k | SkASSERT(SkScalarsAreFinite(&pts[0].fX, 8)); |
406 | | |
407 | 753k | Sk2s min = Sk2s::Load(pts); |
408 | 753k | Sk2s max = min; |
409 | 3.01M | for (int i = 1; i < 4; ++i) { |
410 | 2.25M | Sk2s pair = Sk2s::Load(pts+i); |
411 | 2.25M | min = Sk2s::Min(min, pair); |
412 | 2.25M | max = Sk2s::Max(max, pair); |
413 | 2.25M | } |
414 | 753k | return { min[0], min[1], max[0], max[1] }; |
415 | 753k | } |
416 | | |
417 | | static inline void haircubic(const SkPoint pts[4], const SkRegion* clip, const SkRect* insetClip, const SkRect* outsetClip, |
418 | 753k | SkBlitter* blitter, int level, SkScan::HairRgnProc lineproc) { |
419 | 753k | if (insetClip) { |
420 | 753k | SkASSERT(outsetClip); |
421 | 753k | SkRect bounds = compute_nocheck_cubic_bounds(pts); |
422 | 753k | if (!geometric_overlap(*outsetClip, bounds)) { |
423 | 698k | return; |
424 | 55.1k | } else if (geometric_contains(*insetClip, bounds)) { |
425 | 42.7k | clip = nullptr; |
426 | 42.7k | } |
427 | 753k | } |
428 | | |
429 | 55.6k | if (quick_cubic_niceness_check(pts)) { |
430 | 50.1k | hair_cubic(pts, clip, blitter, lineproc); |
431 | 5.53k | } else { |
432 | 5.53k | SkPoint tmp[13]; |
433 | 5.53k | SkScalar tValues[3]; |
434 | | |
435 | 5.53k | int count = SkChopCubicAtMaxCurvature(pts, tmp, tValues); |
436 | 16.3k | for (int i = 0; i < count; i++) { |
437 | 10.8k | hair_cubic(&tmp[i * 3], clip, blitter, lineproc); |
438 | 10.8k | } |
439 | 5.53k | } |
440 | 55.6k | } |
441 | | |
442 | 21.5M | static int compute_quad_level(const SkPoint pts[3]) { |
443 | 21.5M | uint32_t d = compute_int_quad_dist(pts); |
444 | | /* quadratics approach the line connecting their start and end points |
445 | | 4x closer with each subdivision, so we compute the number of |
446 | | subdivisions to be the minimum need to get that distance to be less |
447 | | than a pixel. |
448 | | */ |
449 | 21.5M | int level = (33 - SkCLZ(d)) >> 1; |
450 | | // safety check on level (from the previous version) |
451 | 21.5M | if (level > kMaxQuadSubdivideLevel) { |
452 | 396k | level = kMaxQuadSubdivideLevel; |
453 | 396k | } |
454 | 21.5M | return level; |
455 | 21.5M | } |
456 | | |
457 | | /* Extend the points in the direction of the starting or ending tangent by 1/2 unit to |
458 | | account for a round or square cap. If there's no distance between the end point and |
459 | | the control point, use the next control point to create a tangent. If the curve |
460 | | is degenerate, move the cap out 1/2 unit horizontally. */ |
461 | | template <SkPaint::Cap capStyle> |
462 | 28.8M | void extend_pts(SkPath::Verb prevVerb, SkPath::Verb nextVerb, SkPoint* pts, int ptCount) { |
463 | 28.8M | SkASSERT(SkPaint::kSquare_Cap == capStyle || SkPaint::kRound_Cap == capStyle); |
464 | | // The area of a circle is PI*R*R. For a unit circle, R=1/2, and the cap covers half of that. |
465 | 18.8M | const SkScalar capOutset = SkPaint::kSquare_Cap == capStyle ? 0.5f : SK_ScalarPI / 8; |
466 | 28.8M | if (SkPath::kMove_Verb == prevVerb) { |
467 | 16.8M | SkPoint* first = pts; |
468 | 16.8M | SkPoint* ctrl = first; |
469 | 16.8M | int controls = ptCount - 1; |
470 | 16.8M | SkVector tangent; |
471 | 16.8M | do { |
472 | 16.8M | tangent = *first - *++ctrl; |
473 | 16.8M | } while (tangent.isZero() && --controls > 0); |
474 | 16.8M | if (tangent.isZero()) { |
475 | 301k | tangent.set(1, 0); |
476 | 301k | controls = ptCount - 1; // If all points are equal, move all but one |
477 | 16.5M | } else { |
478 | 16.5M | tangent.normalize(); |
479 | 16.5M | } |
480 | 16.8M | do { // If the end point and control points are equal, loop to move them in tandem. |
481 | 16.8M | first->fX += tangent.fX * capOutset; |
482 | 16.8M | first->fY += tangent.fY * capOutset; |
483 | 16.8M | ++first; |
484 | 16.8M | } while (++controls < ptCount); |
485 | 16.8M | } |
486 | 28.8M | if (SkPath::kMove_Verb == nextVerb || SkPath::kDone_Verb == nextVerb |
487 | 16.8M | || SkPath::kClose_Verb == nextVerb) { |
488 | 16.8M | SkPoint* last = &pts[ptCount - 1]; |
489 | 16.8M | SkPoint* ctrl = last; |
490 | 16.8M | int controls = ptCount - 1; |
491 | 16.8M | SkVector tangent; |
492 | 16.8M | do { |
493 | 16.8M | tangent = *last - *--ctrl; |
494 | 16.8M | } while (tangent.isZero() && --controls > 0); |
495 | 16.8M | if (tangent.isZero()) { |
496 | 240k | tangent.set(-1, 0); |
497 | 240k | controls = ptCount - 1; |
498 | 16.6M | } else { |
499 | 16.6M | tangent.normalize(); |
500 | 16.6M | } |
501 | 16.8M | do { |
502 | 16.8M | last->fX += tangent.fX * capOutset; |
503 | 16.8M | last->fY += tangent.fY * capOutset; |
504 | 16.8M | --last; |
505 | 16.8M | } while (++controls < ptCount); |
506 | 16.8M | } |
507 | 28.8M | } void extend_pts<(SkPaint::Cap)2>(SkPath::Verb, SkPath::Verb, SkPoint*, int) Line | Count | Source | 462 | 9.93M | void extend_pts(SkPath::Verb prevVerb, SkPath::Verb nextVerb, SkPoint* pts, int ptCount) { | 463 | 9.93M | SkASSERT(SkPaint::kSquare_Cap == capStyle || SkPaint::kRound_Cap == capStyle); | 464 | | // The area of a circle is PI*R*R. For a unit circle, R=1/2, and the cap covers half of that. | 465 | 9.93M | const SkScalar capOutset = SkPaint::kSquare_Cap == capStyle ? 0.5f : SK_ScalarPI / 8; | 466 | 9.93M | if (SkPath::kMove_Verb == prevVerb) { | 467 | 475k | SkPoint* first = pts; | 468 | 475k | SkPoint* ctrl = first; | 469 | 475k | int controls = ptCount - 1; | 470 | 475k | SkVector tangent; | 471 | 505k | do { | 472 | 505k | tangent = *first - *++ctrl; | 473 | 505k | } while (tangent.isZero() && --controls > 0); | 474 | 475k | if (tangent.isZero()) { | 475 | 258k | tangent.set(1, 0); | 476 | 258k | controls = ptCount - 1; // If all points are equal, move all but one | 477 | 217k | } else { | 478 | 217k | tangent.normalize(); | 479 | 217k | } | 480 | 478k | do { // If the end point and control points are equal, loop to move them in tandem. | 481 | 478k | first->fX += tangent.fX * capOutset; | 482 | 478k | first->fY += tangent.fY * capOutset; | 483 | 478k | ++first; | 484 | 478k | } while (++controls < ptCount); | 485 | 475k | } | 486 | 9.93M | if (SkPath::kMove_Verb == nextVerb || SkPath::kDone_Verb == nextVerb | 487 | 9.48M | || SkPath::kClose_Verb == nextVerb) { | 488 | 475k | SkPoint* last = &pts[ptCount - 1]; | 489 | 475k | SkPoint* ctrl = last; | 490 | 475k | int controls = ptCount - 1; | 491 | 475k | SkVector tangent; | 492 | 504k | do { | 493 | 504k | tangent = *last - *--ctrl; | 494 | 504k | } while (tangent.isZero() && --controls > 0); | 495 | 475k | if (tangent.isZero()) { | 496 | 240k | tangent.set(-1, 0); | 497 | 240k | controls = ptCount - 1; | 498 | 235k | } else { | 499 | 235k | tangent.normalize(); | 500 | 235k | } | 501 | 482k | do { | 502 | 482k | last->fX += tangent.fX * capOutset; | 503 | 482k | last->fY += tangent.fY * capOutset; | 504 | 482k | --last; | 505 | 482k | } while (++controls < ptCount); | 506 | 475k | } | 507 | 9.93M | } |
void extend_pts<(SkPaint::Cap)1>(SkPath::Verb, SkPath::Verb, SkPoint*, int) Line | Count | Source | 462 | 18.8M | void extend_pts(SkPath::Verb prevVerb, SkPath::Verb nextVerb, SkPoint* pts, int ptCount) { | 463 | 18.8M | SkASSERT(SkPaint::kSquare_Cap == capStyle || SkPaint::kRound_Cap == capStyle); | 464 | | // The area of a circle is PI*R*R. For a unit circle, R=1/2, and the cap covers half of that. | 465 | 18.8M | const SkScalar capOutset = SkPaint::kSquare_Cap == capStyle ? 0.5f : SK_ScalarPI / 8; | 466 | 18.8M | if (SkPath::kMove_Verb == prevVerb) { | 467 | 16.3M | SkPoint* first = pts; | 468 | 16.3M | SkPoint* ctrl = first; | 469 | 16.3M | int controls = ptCount - 1; | 470 | 16.3M | SkVector tangent; | 471 | 16.3M | do { | 472 | 16.3M | tangent = *first - *++ctrl; | 473 | 16.3M | } while (tangent.isZero() && --controls > 0); | 474 | 16.3M | if (tangent.isZero()) { | 475 | 42.7k | tangent.set(1, 0); | 476 | 42.7k | controls = ptCount - 1; // If all points are equal, move all but one | 477 | 16.3M | } else { | 478 | 16.3M | tangent.normalize(); | 479 | 16.3M | } | 480 | 16.3M | do { // If the end point and control points are equal, loop to move them in tandem. | 481 | 16.3M | first->fX += tangent.fX * capOutset; | 482 | 16.3M | first->fY += tangent.fY * capOutset; | 483 | 16.3M | ++first; | 484 | 16.3M | } while (++controls < ptCount); | 485 | 16.3M | } | 486 | 18.8M | if (SkPath::kMove_Verb == nextVerb || SkPath::kDone_Verb == nextVerb | 487 | 16.3M | || SkPath::kClose_Verb == nextVerb) { | 488 | 16.3M | SkPoint* last = &pts[ptCount - 1]; | 489 | 16.3M | SkPoint* ctrl = last; | 490 | 16.3M | int controls = ptCount - 1; | 491 | 16.3M | SkVector tangent; | 492 | 16.3M | do { | 493 | 16.3M | tangent = *last - *--ctrl; | 494 | 16.3M | } while (tangent.isZero() && --controls > 0); | 495 | 16.3M | if (tangent.isZero()) { | 496 | 530 | tangent.set(-1, 0); | 497 | 530 | controls = ptCount - 1; | 498 | 16.3M | } else { | 499 | 16.3M | tangent.normalize(); | 500 | 16.3M | } | 501 | 16.3M | do { | 502 | 16.3M | last->fX += tangent.fX * capOutset; | 503 | 16.3M | last->fY += tangent.fY * capOutset; | 504 | 16.3M | --last; | 505 | 16.3M | } while (++controls < ptCount); | 506 | 16.3M | } | 507 | 18.8M | } |
Unexecuted instantiation: void extend_pts<(SkPaint::Cap)0>(SkPath::Verb, SkPath::Verb, SkPoint*, int) |
508 | | |
509 | | template <SkPaint::Cap capStyle> |
510 | | void hair_path(const SkPath& path, const SkRasterClip& rclip, SkBlitter* blitter, |
511 | 29.5k | SkScan::HairRgnProc lineproc) { |
512 | 29.5k | if (path.isEmpty()) { |
513 | 11.0k | return; |
514 | 11.0k | } |
515 | | |
516 | 18.4k | SkAAClipBlitterWrapper wrap; |
517 | 18.4k | const SkRegion* clip = nullptr; |
518 | 18.4k | SkRect insetStorage, outsetStorage; |
519 | 18.4k | const SkRect* insetClip = nullptr; |
520 | 18.4k | const SkRect* outsetClip = nullptr; |
521 | | |
522 | 18.4k | { |
523 | 18.4k | const int capOut = SkPaint::kButt_Cap == capStyle ? 1 : 2; |
524 | 18.4k | const SkIRect ibounds = path.getBounds().roundOut().makeOutset(capOut, capOut); |
525 | 18.4k | if (rclip.quickReject(ibounds)) { |
526 | 2.16k | return; |
527 | 2.16k | } |
528 | 16.2k | if (!rclip.quickContains(ibounds)) { |
529 | 15.3k | if (rclip.isBW()) { |
530 | 15.0k | clip = &rclip.bwRgn(); |
531 | 259 | } else { |
532 | 259 | wrap.init(rclip, blitter); |
533 | 259 | blitter = wrap.getBlitter(); |
534 | 259 | clip = &wrap.getRgn(); |
535 | 259 | } |
536 | | |
537 | | /* |
538 | | * We now cache two scalar rects, to use for culling per-segment (e.g. cubic). |
539 | | * Since we're hairlining, the "bounds" of the control points isn't necessairly the |
540 | | * limit of where a segment can draw (it might draw up to 1 pixel beyond in aa-hairs). |
541 | | * |
542 | | * Compute the pt-bounds per segment is easy, so we do that, and then inversely adjust |
543 | | * the culling bounds so we can just do a straight compare per segment. |
544 | | * |
545 | | * insetClip is use for quick-accept (i.e. the segment is not clipped), so we inset |
546 | | * it from the clip-bounds (since segment bounds can be off by 1). |
547 | | * |
548 | | * outsetClip is used for quick-reject (i.e. the segment is entirely outside), so we |
549 | | * outset it from the clip-bounds. |
550 | | */ |
551 | 15.3k | insetStorage.set(clip->getBounds()); |
552 | 15.3k | outsetStorage = insetStorage.makeOutset(1, 1); |
553 | 15.3k | insetStorage.inset(1, 1); |
554 | 15.3k | if (is_inverted(insetStorage)) { |
555 | | /* |
556 | | * our bounds checks assume the rects are never inverted. If insetting has |
557 | | * created that, we assume that the area is too small to safely perform a |
558 | | * quick-accept, so we just mark the rect as empty (so the quick-accept check |
559 | | * will always fail. |
560 | | */ |
561 | 476 | insetStorage.setEmpty(); // just so we don't pass an inverted rect |
562 | 476 | } |
563 | 15.3k | if (rclip.isRect()) { |
564 | 14.7k | insetClip = &insetStorage; |
565 | 14.7k | } |
566 | 15.3k | outsetClip = &outsetStorage; |
567 | 15.3k | } |
568 | 16.2k | } |
569 | | |
570 | 16.2k | SkPathPriv::RangeIter iter = SkPathPriv::Iterate(path).begin(); |
571 | 16.2k | SkPathPriv::RangeIter end = SkPathPriv::Iterate(path).end(); |
572 | 16.2k | SkPoint pts[4], firstPt, lastPt; |
573 | 16.2k | SkPath::Verb prevVerb; |
574 | 16.2k | SkAutoConicToQuads converter; |
575 | | |
576 | 16.2k | if (SkPaint::kButt_Cap != capStyle) { |
577 | 4.68k | prevVerb = SkPath::kDone_Verb; |
578 | 4.68k | } |
579 | 102M | while (iter != end) { |
580 | 102M | auto [pathVerb, pathPts, w] = *iter++; |
581 | 102M | SkPath::Verb verb = (SkPath::Verb)pathVerb; |
582 | 102M | SkPath::Verb nextVerb = (iter != end) ? (SkPath::Verb)iter.peekVerb() : SkPath::kDone_Verb; |
583 | 102M | memcpy(pts, pathPts, SkPathPriv::PtsInIter(verb) * sizeof(SkPoint)); |
584 | 102M | switch (verb) { |
585 | 24.3M | case SkPath::kMove_Verb: |
586 | 24.3M | firstPt = lastPt = pts[0]; |
587 | 24.3M | break; |
588 | 56.6M | case SkPath::kLine_Verb: |
589 | 56.6M | if (SkPaint::kButt_Cap != capStyle) { |
590 | 10.8M | extend_pts<capStyle>(prevVerb, nextVerb, pts, 2); |
591 | 10.8M | } |
592 | 56.6M | lineproc(pts, 2, clip, blitter); |
593 | 56.6M | lastPt = pts[1]; |
594 | 56.6M | break; |
595 | 14.6M | case SkPath::kQuad_Verb: |
596 | 14.6M | if (SkPaint::kButt_Cap != capStyle) { |
597 | 11.6M | extend_pts<capStyle>(prevVerb, nextVerb, pts, 3); |
598 | 11.6M | } |
599 | 14.6M | hairquad(pts, clip, insetClip, outsetClip, blitter, compute_quad_level(pts), lineproc); |
600 | 14.6M | lastPt = pts[2]; |
601 | 14.6M | break; |
602 | 6.48M | case SkPath::kConic_Verb: { |
603 | 6.48M | if (SkPaint::kButt_Cap != capStyle) { |
604 | 6.27M | extend_pts<capStyle>(prevVerb, nextVerb, pts, 3); |
605 | 6.27M | } |
606 | | // how close should the quads be to the original conic? |
607 | 6.48M | const SkScalar tol = SK_Scalar1 / 4; |
608 | 6.48M | const SkPoint* quadPts = converter.computeQuads(pts, *w, tol); |
609 | 13.3M | for (int i = 0; i < converter.countQuads(); ++i) { |
610 | 6.88M | int level = compute_quad_level(quadPts); |
611 | 6.88M | hairquad(quadPts, clip, insetClip, outsetClip, blitter, level, lineproc); |
612 | 6.88M | quadPts += 2; |
613 | 6.88M | } |
614 | 6.48M | lastPt = pts[2]; |
615 | 6.48M | break; |
616 | 0 | } |
617 | 753k | case SkPath::kCubic_Verb: { |
618 | 753k | if (SkPaint::kButt_Cap != capStyle) { |
619 | 29.7k | extend_pts<capStyle>(prevVerb, nextVerb, pts, 4); |
620 | 29.7k | } |
621 | 753k | haircubic(pts, clip, insetClip, outsetClip, blitter, kMaxCubicSubdivideLevel, lineproc); |
622 | 753k | lastPt = pts[3]; |
623 | 753k | } break; |
624 | 32.6k | case SkPath::kClose_Verb: |
625 | 32.6k | pts[0] = lastPt; |
626 | 32.6k | pts[1] = firstPt; |
627 | 32.6k | if (SkPaint::kButt_Cap != capStyle && prevVerb == SkPath::kMove_Verb) { |
628 | | // cap moveTo/close to match svg expectations for degenerate segments |
629 | 608 | extend_pts<capStyle>(prevVerb, nextVerb, pts, 2); |
630 | 608 | } |
631 | 32.6k | lineproc(pts, 2, clip, blitter); |
632 | 32.6k | break; |
633 | 0 | case SkPath::kDone_Verb: |
634 | 0 | break; |
635 | 102M | } |
636 | 102M | if (SkPaint::kButt_Cap != capStyle) { |
637 | 45.8M | if (prevVerb == SkPath::kMove_Verb && |
638 | 17.0M | verb >= SkPath::kLine_Verb && verb <= SkPath::kCubic_Verb) { |
639 | 16.8M | firstPt = pts[0]; // the curve moved the initial point, so close to it instead |
640 | 16.8M | } |
641 | 45.8M | prevVerb = verb; |
642 | 45.8M | } |
643 | 102M | } |
644 | 16.2k | } void hair_path<(SkPaint::Cap)0>(SkPath const&, SkRasterClip const&, SkBlitter*, void (*)(SkPoint const*, int, SkRegion const*, SkBlitter*)) Line | Count | Source | 511 | 19.7k | SkScan::HairRgnProc lineproc) { | 512 | 19.7k | if (path.isEmpty()) { | 513 | 7.23k | return; | 514 | 7.23k | } | 515 | | | 516 | 12.5k | SkAAClipBlitterWrapper wrap; | 517 | 12.5k | const SkRegion* clip = nullptr; | 518 | 12.5k | SkRect insetStorage, outsetStorage; | 519 | 12.5k | const SkRect* insetClip = nullptr; | 520 | 12.5k | const SkRect* outsetClip = nullptr; | 521 | | | 522 | 12.5k | { | 523 | 12.5k | const int capOut = SkPaint::kButt_Cap == capStyle ? 1 : 2; | 524 | 12.5k | const SkIRect ibounds = path.getBounds().roundOut().makeOutset(capOut, capOut); | 525 | 12.5k | if (rclip.quickReject(ibounds)) { | 526 | 970 | return; | 527 | 970 | } | 528 | 11.5k | if (!rclip.quickContains(ibounds)) { | 529 | 10.8k | if (rclip.isBW()) { | 530 | 10.7k | clip = &rclip.bwRgn(); | 531 | 154 | } else { | 532 | 154 | wrap.init(rclip, blitter); | 533 | 154 | blitter = wrap.getBlitter(); | 534 | 154 | clip = &wrap.getRgn(); | 535 | 154 | } | 536 | | | 537 | | /* | 538 | | * We now cache two scalar rects, to use for culling per-segment (e.g. cubic). | 539 | | * Since we're hairlining, the "bounds" of the control points isn't necessairly the | 540 | | * limit of where a segment can draw (it might draw up to 1 pixel beyond in aa-hairs). | 541 | | * | 542 | | * Compute the pt-bounds per segment is easy, so we do that, and then inversely adjust | 543 | | * the culling bounds so we can just do a straight compare per segment. | 544 | | * | 545 | | * insetClip is use for quick-accept (i.e. the segment is not clipped), so we inset | 546 | | * it from the clip-bounds (since segment bounds can be off by 1). | 547 | | * | 548 | | * outsetClip is used for quick-reject (i.e. the segment is entirely outside), so we | 549 | | * outset it from the clip-bounds. | 550 | | */ | 551 | 10.8k | insetStorage.set(clip->getBounds()); | 552 | 10.8k | outsetStorage = insetStorage.makeOutset(1, 1); | 553 | 10.8k | insetStorage.inset(1, 1); | 554 | 10.8k | if (is_inverted(insetStorage)) { | 555 | | /* | 556 | | * our bounds checks assume the rects are never inverted. If insetting has | 557 | | * created that, we assume that the area is too small to safely perform a | 558 | | * quick-accept, so we just mark the rect as empty (so the quick-accept check | 559 | | * will always fail. | 560 | | */ | 561 | 90 | insetStorage.setEmpty(); // just so we don't pass an inverted rect | 562 | 90 | } | 563 | 10.8k | if (rclip.isRect()) { | 564 | 10.5k | insetClip = &insetStorage; | 565 | 10.5k | } | 566 | 10.8k | outsetClip = &outsetStorage; | 567 | 10.8k | } | 568 | 11.5k | } | 569 | | | 570 | 11.5k | SkPathPriv::RangeIter iter = SkPathPriv::Iterate(path).begin(); | 571 | 11.5k | SkPathPriv::RangeIter end = SkPathPriv::Iterate(path).end(); | 572 | 11.5k | SkPoint pts[4], firstPt, lastPt; | 573 | 11.5k | SkPath::Verb prevVerb; | 574 | 11.5k | SkAutoConicToQuads converter; | 575 | | | 576 | 11.5k | if (SkPaint::kButt_Cap != capStyle) { | 577 | 0 | prevVerb = SkPath::kDone_Verb; | 578 | 0 | } | 579 | 57.1M | while (iter != end) { | 580 | 57.0M | auto [pathVerb, pathPts, w] = *iter++; | 581 | 57.0M | SkPath::Verb verb = (SkPath::Verb)pathVerb; | 582 | 57.0M | SkPath::Verb nextVerb = (iter != end) ? (SkPath::Verb)iter.peekVerb() : SkPath::kDone_Verb; | 583 | 57.0M | memcpy(pts, pathPts, SkPathPriv::PtsInIter(verb) * sizeof(SkPoint)); | 584 | 57.0M | switch (verb) { | 585 | 7.32M | case SkPath::kMove_Verb: | 586 | 7.32M | firstPt = lastPt = pts[0]; | 587 | 7.32M | break; | 588 | 45.7M | case SkPath::kLine_Verb: | 589 | 45.7M | if (SkPaint::kButt_Cap != capStyle) { | 590 | 0 | extend_pts<capStyle>(prevVerb, nextVerb, pts, 2); | 591 | 0 | } | 592 | 45.7M | lineproc(pts, 2, clip, blitter); | 593 | 45.7M | lastPt = pts[1]; | 594 | 45.7M | break; | 595 | 3.03M | case SkPath::kQuad_Verb: | 596 | 3.03M | if (SkPaint::kButt_Cap != capStyle) { | 597 | 0 | extend_pts<capStyle>(prevVerb, nextVerb, pts, 3); | 598 | 0 | } | 599 | 3.03M | hairquad(pts, clip, insetClip, outsetClip, blitter, compute_quad_level(pts), lineproc); | 600 | 3.03M | lastPt = pts[2]; | 601 | 3.03M | break; | 602 | 206k | case SkPath::kConic_Verb: { | 603 | 206k | if (SkPaint::kButt_Cap != capStyle) { | 604 | 0 | extend_pts<capStyle>(prevVerb, nextVerb, pts, 3); | 605 | 0 | } | 606 | | // how close should the quads be to the original conic? | 607 | 206k | const SkScalar tol = SK_Scalar1 / 4; | 608 | 206k | const SkPoint* quadPts = converter.computeQuads(pts, *w, tol); | 609 | 528k | for (int i = 0; i < converter.countQuads(); ++i) { | 610 | 322k | int level = compute_quad_level(quadPts); | 611 | 322k | hairquad(quadPts, clip, insetClip, outsetClip, blitter, level, lineproc); | 612 | 322k | quadPts += 2; | 613 | 322k | } | 614 | 206k | lastPt = pts[2]; | 615 | 206k | break; | 616 | 0 | } | 617 | 724k | case SkPath::kCubic_Verb: { | 618 | 724k | if (SkPaint::kButt_Cap != capStyle) { | 619 | 0 | extend_pts<capStyle>(prevVerb, nextVerb, pts, 4); | 620 | 0 | } | 621 | 724k | haircubic(pts, clip, insetClip, outsetClip, blitter, kMaxCubicSubdivideLevel, lineproc); | 622 | 724k | lastPt = pts[3]; | 623 | 724k | } break; | 624 | 4.41k | case SkPath::kClose_Verb: | 625 | 4.41k | pts[0] = lastPt; | 626 | 4.41k | pts[1] = firstPt; | 627 | 4.41k | if (SkPaint::kButt_Cap != capStyle && prevVerb == SkPath::kMove_Verb) { | 628 | | // cap moveTo/close to match svg expectations for degenerate segments | 629 | 0 | extend_pts<capStyle>(prevVerb, nextVerb, pts, 2); | 630 | 0 | } | 631 | 4.41k | lineproc(pts, 2, clip, blitter); | 632 | 4.41k | break; | 633 | 0 | case SkPath::kDone_Verb: | 634 | 0 | break; | 635 | 57.0M | } | 636 | 57.0M | if (SkPaint::kButt_Cap != capStyle) { | 637 | 0 | if (prevVerb == SkPath::kMove_Verb && | 638 | 0 | verb >= SkPath::kLine_Verb && verb <= SkPath::kCubic_Verb) { | 639 | 0 | firstPt = pts[0]; // the curve moved the initial point, so close to it instead | 640 | 0 | } | 641 | 0 | prevVerb = verb; | 642 | 0 | } | 643 | 57.0M | } | 644 | 11.5k | } |
void hair_path<(SkPaint::Cap)2>(SkPath const&, SkRasterClip const&, SkBlitter*, void (*)(SkPoint const*, int, SkRegion const*, SkBlitter*)) Line | Count | Source | 511 | 7.94k | SkScan::HairRgnProc lineproc) { | 512 | 7.94k | if (path.isEmpty()) { | 513 | 3.18k | return; | 514 | 3.18k | } | 515 | | | 516 | 4.75k | SkAAClipBlitterWrapper wrap; | 517 | 4.75k | const SkRegion* clip = nullptr; | 518 | 4.75k | SkRect insetStorage, outsetStorage; | 519 | 4.75k | const SkRect* insetClip = nullptr; | 520 | 4.75k | const SkRect* outsetClip = nullptr; | 521 | | | 522 | 4.75k | { | 523 | 4.75k | const int capOut = SkPaint::kButt_Cap == capStyle ? 1 : 2; | 524 | 4.75k | const SkIRect ibounds = path.getBounds().roundOut().makeOutset(capOut, capOut); | 525 | 4.75k | if (rclip.quickReject(ibounds)) { | 526 | 1.16k | return; | 527 | 1.16k | } | 528 | 3.59k | if (!rclip.quickContains(ibounds)) { | 529 | 3.50k | if (rclip.isBW()) { | 530 | 3.39k | clip = &rclip.bwRgn(); | 531 | 105 | } else { | 532 | 105 | wrap.init(rclip, blitter); | 533 | 105 | blitter = wrap.getBlitter(); | 534 | 105 | clip = &wrap.getRgn(); | 535 | 105 | } | 536 | | | 537 | | /* | 538 | | * We now cache two scalar rects, to use for culling per-segment (e.g. cubic). | 539 | | * Since we're hairlining, the "bounds" of the control points isn't necessairly the | 540 | | * limit of where a segment can draw (it might draw up to 1 pixel beyond in aa-hairs). | 541 | | * | 542 | | * Compute the pt-bounds per segment is easy, so we do that, and then inversely adjust | 543 | | * the culling bounds so we can just do a straight compare per segment. | 544 | | * | 545 | | * insetClip is use for quick-accept (i.e. the segment is not clipped), so we inset | 546 | | * it from the clip-bounds (since segment bounds can be off by 1). | 547 | | * | 548 | | * outsetClip is used for quick-reject (i.e. the segment is entirely outside), so we | 549 | | * outset it from the clip-bounds. | 550 | | */ | 551 | 3.50k | insetStorage.set(clip->getBounds()); | 552 | 3.50k | outsetStorage = insetStorage.makeOutset(1, 1); | 553 | 3.50k | insetStorage.inset(1, 1); | 554 | 3.50k | if (is_inverted(insetStorage)) { | 555 | | /* | 556 | | * our bounds checks assume the rects are never inverted. If insetting has | 557 | | * created that, we assume that the area is too small to safely perform a | 558 | | * quick-accept, so we just mark the rect as empty (so the quick-accept check | 559 | | * will always fail. | 560 | | */ | 561 | 324 | insetStorage.setEmpty(); // just so we don't pass an inverted rect | 562 | 324 | } | 563 | 3.50k | if (rclip.isRect()) { | 564 | 3.21k | insetClip = &insetStorage; | 565 | 3.21k | } | 566 | 3.50k | outsetClip = &outsetStorage; | 567 | 3.50k | } | 568 | 3.59k | } | 569 | | | 570 | 3.59k | SkPathPriv::RangeIter iter = SkPathPriv::Iterate(path).begin(); | 571 | 3.59k | SkPathPriv::RangeIter end = SkPathPriv::Iterate(path).end(); | 572 | 3.59k | SkPoint pts[4], firstPt, lastPt; | 573 | 3.59k | SkPath::Verb prevVerb; | 574 | 3.59k | SkAutoConicToQuads converter; | 575 | | | 576 | 3.59k | if (SkPaint::kButt_Cap != capStyle) { | 577 | 3.59k | prevVerb = SkPath::kDone_Verb; | 578 | 3.59k | } | 579 | 10.6M | while (iter != end) { | 580 | 10.6M | auto [pathVerb, pathPts, w] = *iter++; | 581 | 10.6M | SkPath::Verb verb = (SkPath::Verb)pathVerb; | 582 | 10.6M | SkPath::Verb nextVerb = (iter != end) ? (SkPath::Verb)iter.peekVerb() : SkPath::kDone_Verb; | 583 | 10.6M | memcpy(pts, pathPts, SkPathPriv::PtsInIter(verb) * sizeof(SkPoint)); | 584 | 10.6M | switch (verb) { | 585 | 643k | case SkPath::kMove_Verb: | 586 | 643k | firstPt = lastPt = pts[0]; | 587 | 643k | break; | 588 | 8.29M | case SkPath::kLine_Verb: | 589 | 8.29M | if (SkPaint::kButt_Cap != capStyle) { | 590 | 8.29M | extend_pts<capStyle>(prevVerb, nextVerb, pts, 2); | 591 | 8.29M | } | 592 | 8.29M | lineproc(pts, 2, clip, blitter); | 593 | 8.29M | lastPt = pts[1]; | 594 | 8.29M | break; | 595 | 1.51M | case SkPath::kQuad_Verb: | 596 | 1.51M | if (SkPaint::kButt_Cap != capStyle) { | 597 | 1.51M | extend_pts<capStyle>(prevVerb, nextVerb, pts, 3); | 598 | 1.51M | } | 599 | 1.51M | hairquad(pts, clip, insetClip, outsetClip, blitter, compute_quad_level(pts), lineproc); | 600 | 1.51M | lastPt = pts[2]; | 601 | 1.51M | break; | 602 | 97.9k | case SkPath::kConic_Verb: { | 603 | 97.9k | if (SkPaint::kButt_Cap != capStyle) { | 604 | 97.9k | extend_pts<capStyle>(prevVerb, nextVerb, pts, 3); | 605 | 97.9k | } | 606 | | // how close should the quads be to the original conic? | 607 | 97.9k | const SkScalar tol = SK_Scalar1 / 4; | 608 | 97.9k | const SkPoint* quadPts = converter.computeQuads(pts, *w, tol); | 609 | 468k | for (int i = 0; i < converter.countQuads(); ++i) { | 610 | 370k | int level = compute_quad_level(quadPts); | 611 | 370k | hairquad(quadPts, clip, insetClip, outsetClip, blitter, level, lineproc); | 612 | 370k | quadPts += 2; | 613 | 370k | } | 614 | 97.9k | lastPt = pts[2]; | 615 | 97.9k | break; | 616 | 0 | } | 617 | 27.5k | case SkPath::kCubic_Verb: { | 618 | 27.5k | if (SkPaint::kButt_Cap != capStyle) { | 619 | 27.5k | extend_pts<capStyle>(prevVerb, nextVerb, pts, 4); | 620 | 27.5k | } | 621 | 27.5k | haircubic(pts, clip, insetClip, outsetClip, blitter, kMaxCubicSubdivideLevel, lineproc); | 622 | 27.5k | lastPt = pts[3]; | 623 | 27.5k | } break; | 624 | 26.8k | case SkPath::kClose_Verb: | 625 | 26.8k | pts[0] = lastPt; | 626 | 26.8k | pts[1] = firstPt; | 627 | 26.8k | if (SkPaint::kButt_Cap != capStyle && prevVerb == SkPath::kMove_Verb) { | 628 | | // cap moveTo/close to match svg expectations for degenerate segments | 629 | 345 | extend_pts<capStyle>(prevVerb, nextVerb, pts, 2); | 630 | 345 | } | 631 | 26.8k | lineproc(pts, 2, clip, blitter); | 632 | 26.8k | break; | 633 | 0 | case SkPath::kDone_Verb: | 634 | 0 | break; | 635 | 10.6M | } | 636 | 10.6M | if (SkPaint::kButt_Cap != capStyle) { | 637 | 10.6M | if (prevVerb == SkPath::kMove_Verb && | 638 | 642k | verb >= SkPath::kLine_Verb && verb <= SkPath::kCubic_Verb) { | 639 | 475k | firstPt = pts[0]; // the curve moved the initial point, so close to it instead | 640 | 475k | } | 641 | 10.6M | prevVerb = verb; | 642 | 10.6M | } | 643 | 10.6M | } | 644 | 3.59k | } |
void hair_path<(SkPaint::Cap)1>(SkPath const&, SkRasterClip const&, SkBlitter*, void (*)(SkPoint const*, int, SkRegion const*, SkBlitter*)) Line | Count | Source | 511 | 1.78k | SkScan::HairRgnProc lineproc) { | 512 | 1.78k | if (path.isEmpty()) { | 513 | 667 | return; | 514 | 667 | } | 515 | | | 516 | 1.11k | SkAAClipBlitterWrapper wrap; | 517 | 1.11k | const SkRegion* clip = nullptr; | 518 | 1.11k | SkRect insetStorage, outsetStorage; | 519 | 1.11k | const SkRect* insetClip = nullptr; | 520 | 1.11k | const SkRect* outsetClip = nullptr; | 521 | | | 522 | 1.11k | { | 523 | 1.11k | const int capOut = SkPaint::kButt_Cap == capStyle ? 1 : 2; | 524 | 1.11k | const SkIRect ibounds = path.getBounds().roundOut().makeOutset(capOut, capOut); | 525 | 1.11k | if (rclip.quickReject(ibounds)) { | 526 | 24 | return; | 527 | 24 | } | 528 | 1.09k | if (!rclip.quickContains(ibounds)) { | 529 | 959 | if (rclip.isBW()) { | 530 | 959 | clip = &rclip.bwRgn(); | 531 | 0 | } else { | 532 | 0 | wrap.init(rclip, blitter); | 533 | 0 | blitter = wrap.getBlitter(); | 534 | 0 | clip = &wrap.getRgn(); | 535 | 0 | } | 536 | | | 537 | | /* | 538 | | * We now cache two scalar rects, to use for culling per-segment (e.g. cubic). | 539 | | * Since we're hairlining, the "bounds" of the control points isn't necessairly the | 540 | | * limit of where a segment can draw (it might draw up to 1 pixel beyond in aa-hairs). | 541 | | * | 542 | | * Compute the pt-bounds per segment is easy, so we do that, and then inversely adjust | 543 | | * the culling bounds so we can just do a straight compare per segment. | 544 | | * | 545 | | * insetClip is use for quick-accept (i.e. the segment is not clipped), so we inset | 546 | | * it from the clip-bounds (since segment bounds can be off by 1). | 547 | | * | 548 | | * outsetClip is used for quick-reject (i.e. the segment is entirely outside), so we | 549 | | * outset it from the clip-bounds. | 550 | | */ | 551 | 959 | insetStorage.set(clip->getBounds()); | 552 | 959 | outsetStorage = insetStorage.makeOutset(1, 1); | 553 | 959 | insetStorage.inset(1, 1); | 554 | 959 | if (is_inverted(insetStorage)) { | 555 | | /* | 556 | | * our bounds checks assume the rects are never inverted. If insetting has | 557 | | * created that, we assume that the area is too small to safely perform a | 558 | | * quick-accept, so we just mark the rect as empty (so the quick-accept check | 559 | | * will always fail. | 560 | | */ | 561 | 62 | insetStorage.setEmpty(); // just so we don't pass an inverted rect | 562 | 62 | } | 563 | 959 | if (rclip.isRect()) { | 564 | 959 | insetClip = &insetStorage; | 565 | 959 | } | 566 | 959 | outsetClip = &outsetStorage; | 567 | 959 | } | 568 | 1.09k | } | 569 | | | 570 | 1.09k | SkPathPriv::RangeIter iter = SkPathPriv::Iterate(path).begin(); | 571 | 1.09k | SkPathPriv::RangeIter end = SkPathPriv::Iterate(path).end(); | 572 | 1.09k | SkPoint pts[4], firstPt, lastPt; | 573 | 1.09k | SkPath::Verb prevVerb; | 574 | 1.09k | SkAutoConicToQuads converter; | 575 | | | 576 | 1.09k | if (SkPaint::kButt_Cap != capStyle) { | 577 | 1.09k | prevVerb = SkPath::kDone_Verb; | 578 | 1.09k | } | 579 | 35.2M | while (iter != end) { | 580 | 35.2M | auto [pathVerb, pathPts, w] = *iter++; | 581 | 35.2M | SkPath::Verb verb = (SkPath::Verb)pathVerb; | 582 | 35.2M | SkPath::Verb nextVerb = (iter != end) ? (SkPath::Verb)iter.peekVerb() : SkPath::kDone_Verb; | 583 | 35.2M | memcpy(pts, pathPts, SkPathPriv::PtsInIter(verb) * sizeof(SkPoint)); | 584 | 35.2M | switch (verb) { | 585 | 16.3M | case SkPath::kMove_Verb: | 586 | 16.3M | firstPt = lastPt = pts[0]; | 587 | 16.3M | break; | 588 | 2.56M | case SkPath::kLine_Verb: | 589 | 2.56M | if (SkPaint::kButt_Cap != capStyle) { | 590 | 2.56M | extend_pts<capStyle>(prevVerb, nextVerb, pts, 2); | 591 | 2.56M | } | 592 | 2.56M | lineproc(pts, 2, clip, blitter); | 593 | 2.56M | lastPt = pts[1]; | 594 | 2.56M | break; | 595 | 10.1M | case SkPath::kQuad_Verb: | 596 | 10.1M | if (SkPaint::kButt_Cap != capStyle) { | 597 | 10.1M | extend_pts<capStyle>(prevVerb, nextVerb, pts, 3); | 598 | 10.1M | } | 599 | 10.1M | hairquad(pts, clip, insetClip, outsetClip, blitter, compute_quad_level(pts), lineproc); | 600 | 10.1M | lastPt = pts[2]; | 601 | 10.1M | break; | 602 | 6.18M | case SkPath::kConic_Verb: { | 603 | 6.18M | if (SkPaint::kButt_Cap != capStyle) { | 604 | 6.18M | extend_pts<capStyle>(prevVerb, nextVerb, pts, 3); | 605 | 6.18M | } | 606 | | // how close should the quads be to the original conic? | 607 | 6.18M | const SkScalar tol = SK_Scalar1 / 4; | 608 | 6.18M | const SkPoint* quadPts = converter.computeQuads(pts, *w, tol); | 609 | 12.3M | for (int i = 0; i < converter.countQuads(); ++i) { | 610 | 6.19M | int level = compute_quad_level(quadPts); | 611 | 6.19M | hairquad(quadPts, clip, insetClip, outsetClip, blitter, level, lineproc); | 612 | 6.19M | quadPts += 2; | 613 | 6.19M | } | 614 | 6.18M | lastPt = pts[2]; | 615 | 6.18M | break; | 616 | 0 | } | 617 | 2.15k | case SkPath::kCubic_Verb: { | 618 | 2.15k | if (SkPaint::kButt_Cap != capStyle) { | 619 | 2.15k | extend_pts<capStyle>(prevVerb, nextVerb, pts, 4); | 620 | 2.15k | } | 621 | 2.15k | haircubic(pts, clip, insetClip, outsetClip, blitter, kMaxCubicSubdivideLevel, lineproc); | 622 | 2.15k | lastPt = pts[3]; | 623 | 2.15k | } break; | 624 | 1.39k | case SkPath::kClose_Verb: | 625 | 1.39k | pts[0] = lastPt; | 626 | 1.39k | pts[1] = firstPt; | 627 | 1.39k | if (SkPaint::kButt_Cap != capStyle && prevVerb == SkPath::kMove_Verb) { | 628 | | // cap moveTo/close to match svg expectations for degenerate segments | 629 | 263 | extend_pts<capStyle>(prevVerb, nextVerb, pts, 2); | 630 | 263 | } | 631 | 1.39k | lineproc(pts, 2, clip, blitter); | 632 | 1.39k | break; | 633 | 0 | case SkPath::kDone_Verb: | 634 | 0 | break; | 635 | 35.2M | } | 636 | 35.2M | if (SkPaint::kButt_Cap != capStyle) { | 637 | 35.2M | if (prevVerb == SkPath::kMove_Verb && | 638 | 16.3M | verb >= SkPath::kLine_Verb && verb <= SkPath::kCubic_Verb) { | 639 | 16.3M | firstPt = pts[0]; // the curve moved the initial point, so close to it instead | 640 | 16.3M | } | 641 | 35.2M | prevVerb = verb; | 642 | 35.2M | } | 643 | 35.2M | } | 644 | 1.09k | } |
|
645 | | |
646 | 3.35k | void SkScan::HairPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) { |
647 | 3.35k | hair_path<SkPaint::kButt_Cap>(path, clip, blitter, SkScan::HairLineRgn); |
648 | 3.35k | } |
649 | | |
650 | 16.4k | void SkScan::AntiHairPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) { |
651 | 16.4k | hair_path<SkPaint::kButt_Cap>(path, clip, blitter, SkScan::AntiHairLineRgn); |
652 | 16.4k | } |
653 | | |
654 | 2.81k | void SkScan::HairSquarePath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) { |
655 | 2.81k | hair_path<SkPaint::kSquare_Cap>(path, clip, blitter, SkScan::HairLineRgn); |
656 | 2.81k | } |
657 | | |
658 | 5.12k | void SkScan::AntiHairSquarePath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) { |
659 | 5.12k | hair_path<SkPaint::kSquare_Cap>(path, clip, blitter, SkScan::AntiHairLineRgn); |
660 | 5.12k | } |
661 | | |
662 | 136 | void SkScan::HairRoundPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) { |
663 | 136 | hair_path<SkPaint::kRound_Cap>(path, clip, blitter, SkScan::HairLineRgn); |
664 | 136 | } |
665 | | |
666 | 1.64k | void SkScan::AntiHairRoundPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) { |
667 | 1.64k | hair_path<SkPaint::kRound_Cap>(path, clip, blitter, SkScan::AntiHairLineRgn); |
668 | 1.64k | } |
669 | | |
670 | | /////////////////////////////////////////////////////////////////////////////// |
671 | | |
672 | | void SkScan::FrameRect(const SkRect& r, const SkPoint& strokeSize, |
673 | 16 | const SkRasterClip& clip, SkBlitter* blitter) { |
674 | 16 | SkASSERT(strokeSize.fX >= 0 && strokeSize.fY >= 0); |
675 | | |
676 | 16 | if (strokeSize.fX < 0 || strokeSize.fY < 0) { |
677 | 0 | return; |
678 | 0 | } |
679 | | |
680 | 16 | const SkScalar dx = strokeSize.fX; |
681 | 16 | const SkScalar dy = strokeSize.fY; |
682 | 16 | SkScalar rx = SkScalarHalf(dx); |
683 | 16 | SkScalar ry = SkScalarHalf(dy); |
684 | 16 | SkRect outer, tmp; |
685 | | |
686 | 16 | outer.setLTRB(r.fLeft - rx, r.fTop - ry, r.fRight + rx, r.fBottom + ry); |
687 | | |
688 | 16 | if (r.width() <= dx || r.height() <= dy) { |
689 | 2 | SkScan::FillRect(outer, clip, blitter); |
690 | 2 | return; |
691 | 2 | } |
692 | | |
693 | 14 | tmp.setLTRB(outer.fLeft, outer.fTop, outer.fRight, outer.fTop + dy); |
694 | 14 | SkScan::FillRect(tmp, clip, blitter); |
695 | 14 | tmp.fTop = outer.fBottom - dy; |
696 | 14 | tmp.fBottom = outer.fBottom; |
697 | 14 | SkScan::FillRect(tmp, clip, blitter); |
698 | | |
699 | 14 | tmp.setLTRB(outer.fLeft, outer.fTop + dy, outer.fLeft + dx, outer.fBottom - dy); |
700 | 14 | SkScan::FillRect(tmp, clip, blitter); |
701 | 14 | tmp.fLeft = outer.fRight - dx; |
702 | 14 | tmp.fRight = outer.fRight; |
703 | 14 | SkScan::FillRect(tmp, clip, blitter); |
704 | 14 | } |
705 | | |
706 | | void SkScan::HairLine(const SkPoint pts[], int count, const SkRasterClip& clip, |
707 | 79 | SkBlitter* blitter) { |
708 | 79 | if (clip.isBW()) { |
709 | 79 | HairLineRgn(pts, count, &clip.bwRgn(), blitter); |
710 | 0 | } else { |
711 | 0 | const SkRegion* clipRgn = nullptr; |
712 | |
|
713 | 0 | SkRect r; |
714 | 0 | r.setBounds(pts, count); |
715 | 0 | r.outset(SK_ScalarHalf, SK_ScalarHalf); |
716 | |
|
717 | 0 | SkAAClipBlitterWrapper wrap; |
718 | 0 | if (!clip.quickContains(r.roundOut())) { |
719 | 0 | wrap.init(clip, blitter); |
720 | 0 | blitter = wrap.getBlitter(); |
721 | 0 | clipRgn = &wrap.getRgn(); |
722 | 0 | } |
723 | 0 | HairLineRgn(pts, count, clipRgn, blitter); |
724 | 0 | } |
725 | 79 | } |
726 | | |
727 | | void SkScan::AntiHairLine(const SkPoint pts[], int count, const SkRasterClip& clip, |
728 | 3.10k | SkBlitter* blitter) { |
729 | 3.10k | if (clip.isBW()) { |
730 | 3.09k | AntiHairLineRgn(pts, count, &clip.bwRgn(), blitter); |
731 | 5 | } else { |
732 | 5 | const SkRegion* clipRgn = nullptr; |
733 | | |
734 | 5 | SkRect r; |
735 | 5 | r.setBounds(pts, count); |
736 | | |
737 | 5 | SkAAClipBlitterWrapper wrap; |
738 | 5 | if (!clip.quickContains(r.roundOut().makeOutset(1, 1))) { |
739 | 5 | wrap.init(clip, blitter); |
740 | 5 | blitter = wrap.getBlitter(); |
741 | 5 | clipRgn = &wrap.getRgn(); |
742 | 5 | } |
743 | 5 | AntiHairLineRgn(pts, count, clipRgn, blitter); |
744 | 5 | } |
745 | 3.10k | } |