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