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