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