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