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 "SkRasterClip.h" 12 #include "SkFDot6.h" 13 #include "SkLineClipper.h" 14 15 static void horiline(int x, int stopx, SkFixed fy, SkFixed dy, 16 SkBlitter* blitter) { 17 SkASSERT(x < stopx); 18 19 do { 20 blitter->blitH(x, fy >> 16, 1); 21 fy += dy; 22 } while (++x < stopx); 23 } 24 25 static void vertline(int y, int stopy, SkFixed fx, SkFixed dx, 26 SkBlitter* blitter) { 27 SkASSERT(y < stopy); 28 29 do { 30 blitter->blitH(fx >> 16, y, 1); 31 fx += dx; 32 } while (++y < stopy); 33 } 34 35 #ifdef SK_DEBUG 36 static bool canConvertFDot6ToFixed(SkFDot6 x) { 37 const int maxDot6 = SK_MaxS32 >> (16 - 6); 38 return SkAbs32(x) <= maxDot6; 39 } 40 #endif 41 42 void SkScan::HairLineRgn(const SkPoint array[], int arrayCount, const SkRegion* clip, 43 SkBlitter* origBlitter) { 44 SkBlitterClipper clipper; 45 SkIRect clipR, ptsR; 46 47 const SkScalar max = SkIntToScalar(32767); 48 const SkRect fixedBounds = SkRect::MakeLTRB(-max, -max, max, max); 49 50 SkRect clipBounds; 51 if (clip) { 52 clipBounds.set(clip->getBounds()); 53 } 54 55 for (int i = 0; i < arrayCount - 1; ++i) { 56 SkBlitter* blitter = origBlitter; 57 58 SkPoint pts[2]; 59 60 // We have to pre-clip the line to fit in a SkFixed, so we just chop 61 // the line. TODO find a way to actually draw beyond that range. 62 if (!SkLineClipper::IntersectLine(&array[i], fixedBounds, pts)) { 63 continue; 64 } 65 66 // Perform a clip in scalar space, so we catch huge values which might 67 // be missed after we convert to SkFDot6 (overflow) 68 if (clip && !SkLineClipper::IntersectLine(pts, clipBounds, pts)) { 69 continue; 70 } 71 72 SkFDot6 x0 = SkScalarToFDot6(pts[0].fX); 73 SkFDot6 y0 = SkScalarToFDot6(pts[0].fY); 74 SkFDot6 x1 = SkScalarToFDot6(pts[1].fX); 75 SkFDot6 y1 = SkScalarToFDot6(pts[1].fY); 76 77 SkASSERT(canConvertFDot6ToFixed(x0)); 78 SkASSERT(canConvertFDot6ToFixed(y0)); 79 SkASSERT(canConvertFDot6ToFixed(x1)); 80 SkASSERT(canConvertFDot6ToFixed(y1)); 81 82 if (clip) { 83 // now perform clipping again, as the rounding to dot6 can wiggle us 84 // our rects are really dot6 rects, but since we've already used 85 // lineclipper, we know they will fit in 32bits (26.6) 86 const SkIRect& bounds = clip->getBounds(); 87 88 clipR.set(SkIntToFDot6(bounds.fLeft), SkIntToFDot6(bounds.fTop), 89 SkIntToFDot6(bounds.fRight), SkIntToFDot6(bounds.fBottom)); 90 ptsR.set(x0, y0, x1, y1); 91 ptsR.sort(); 92 93 // outset the right and bottom, to account for how hairlines are 94 // actually drawn, which may hit the pixel to the right or below of 95 // the coordinate 96 ptsR.fRight += SK_FDot6One; 97 ptsR.fBottom += SK_FDot6One; 98 99 if (!SkIRect::Intersects(ptsR, clipR)) { 100 continue; 101 } 102 if (!clip->isRect() || !clipR.contains(ptsR)) { 103 blitter = clipper.apply(origBlitter, clip); 104 } 105 } 106 107 SkFDot6 dx = x1 - x0; 108 SkFDot6 dy = y1 - y0; 109 110 if (SkAbs32(dx) > SkAbs32(dy)) { // mostly horizontal 111 if (x0 > x1) { // we want to go left-to-right 112 SkTSwap<SkFDot6>(x0, x1); 113 SkTSwap<SkFDot6>(y0, y1); 114 } 115 int ix0 = SkFDot6Round(x0); 116 int ix1 = SkFDot6Round(x1); 117 if (ix0 == ix1) {// too short to draw 118 continue; 119 } 120 121 SkFixed slope = SkFixedDiv(dy, dx); 122 SkFixed startY = SkFDot6ToFixed(y0) + (slope * ((32 - x0) & 63) >> 6); 123 124 horiline(ix0, ix1, startY, slope, blitter); 125 } else { // mostly vertical 126 if (y0 > y1) { // we want to go top-to-bottom 127 SkTSwap<SkFDot6>(x0, x1); 128 SkTSwap<SkFDot6>(y0, y1); 129 } 130 int iy0 = SkFDot6Round(y0); 131 int iy1 = SkFDot6Round(y1); 132 if (iy0 == iy1) { // too short to draw 133 continue; 134 } 135 136 SkFixed slope = SkFixedDiv(dx, dy); 137 SkFixed startX = SkFDot6ToFixed(x0) + (slope * ((32 - y0) & 63) >> 6); 138 139 vertline(iy0, iy1, startX, slope, blitter); 140 } 141 } 142 } 143 144 // we don't just draw 4 lines, 'cause that can leave a gap in the bottom-right 145 // and double-hit the top-left. 146 // TODO: handle huge coordinates on rect (before calling SkScalarToFixed) 147 void SkScan::HairRect(const SkRect& rect, const SkRasterClip& clip, 148 SkBlitter* blitter) { 149 SkAAClipBlitterWrapper wrapper; 150 SkBlitterClipper clipper; 151 SkIRect r; 152 153 r.set(SkScalarToFixed(rect.fLeft) >> 16, 154 SkScalarToFixed(rect.fTop) >> 16, 155 (SkScalarToFixed(rect.fRight) >> 16) + 1, 156 (SkScalarToFixed(rect.fBottom) >> 16) + 1); 157 158 if (clip.quickReject(r)) { 159 return; 160 } 161 if (!clip.quickContains(r)) { 162 const SkRegion* clipRgn; 163 if (clip.isBW()) { 164 clipRgn = &clip.bwRgn(); 165 } else { 166 wrapper.init(clip, blitter); 167 clipRgn = &wrapper.getRgn(); 168 blitter = wrapper.getBlitter(); 169 } 170 blitter = clipper.apply(blitter, clipRgn); 171 } 172 173 int width = r.width(); 174 int height = r.height(); 175 176 if ((width | height) == 0) { 177 return; 178 } 179 if (width <= 2 || height <= 2) { 180 blitter->blitRect(r.fLeft, r.fTop, width, height); 181 return; 182 } 183 // if we get here, we know we have 4 segments to draw 184 blitter->blitH(r.fLeft, r.fTop, width); // top 185 blitter->blitRect(r.fLeft, r.fTop + 1, 1, height - 2); // left 186 blitter->blitRect(r.fRight - 1, r.fTop + 1, 1, height - 2); // right 187 blitter->blitH(r.fLeft, r.fBottom - 1, width); // bottom 188 } 189 190 /////////////////////////////////////////////////////////////////////////////// 191 192 #include "SkPath.h" 193 #include "SkGeometry.h" 194 #include "SkNx.h" 195 196 #define kMaxCubicSubdivideLevel 9 197 #define kMaxQuadSubdivideLevel 5 198 199 static int compute_int_quad_dist(const SkPoint pts[3]) { 200 // compute the vector between the control point ([1]) and the middle of the 201 // line connecting the start and end ([0] and [2]) 202 SkScalar dx = SkScalarHalf(pts[0].fX + pts[2].fX) - pts[1].fX; 203 SkScalar dy = SkScalarHalf(pts[0].fY + pts[2].fY) - pts[1].fY; 204 // we want everyone to be positive 205 dx = SkScalarAbs(dx); 206 dy = SkScalarAbs(dy); 207 // convert to whole pixel values (use ceiling to be conservative) 208 int idx = SkScalarCeilToInt(dx); 209 int 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 int 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 SkPoint* last = &pts[ptCount - 1]; 452 SkPoint* ctrl = last; 453 int controls = ptCount - 1; 454 SkVector tangent; 455 do { 456 tangent = *last - *--ctrl; 457 } while (tangent.isZero() && --controls > 0); 458 if (tangent.isZero()) { 459 tangent.set(-1, 0); 460 controls = ptCount - 1; 461 } else { 462 tangent.normalize(); 463 } 464 do { 465 last->fX += tangent.fX * capOutset; 466 last->fY += tangent.fY * capOutset; 467 --last; 468 } while (++controls < ptCount); 469 } 470 } 471 472 template <SkPaint::Cap capStyle> 473 void hair_path(const SkPath& path, const SkRasterClip& rclip, SkBlitter* blitter, 474 SkScan::HairRgnProc lineproc) { 475 if (path.isEmpty()) { 476 return; 477 } 478 479 SkAAClipBlitterWrapper wrap; 480 const SkRegion* clip = nullptr; 481 SkRect insetStorage, outsetStorage; 482 const SkRect* insetClip = nullptr; 483 const SkRect* outsetClip = nullptr; 484 485 { 486 const int capOut = SkPaint::kButt_Cap == capStyle ? 1 : 2; 487 const SkIRect ibounds = path.getBounds().roundOut().makeOutset(capOut, capOut); 488 if (rclip.quickReject(ibounds)) { 489 return; 490 } 491 if (!rclip.quickContains(ibounds)) { 492 if (rclip.isBW()) { 493 clip = &rclip.bwRgn(); 494 } else { 495 wrap.init(rclip, blitter); 496 blitter = wrap.getBlitter(); 497 clip = &wrap.getRgn(); 498 } 499 500 /* 501 * We now cache two scalar rects, to use for culling per-segment (e.g. cubic). 502 * Since we're hairlining, the "bounds" of the control points isn't necessairly the 503 * limit of where a segment can draw (it might draw up to 1 pixel beyond in aa-hairs). 504 * 505 * Compute the pt-bounds per segment is easy, so we do that, and then inversely adjust 506 * the culling bounds so we can just do a straight compare per segment. 507 * 508 * insetClip is use for quick-accept (i.e. the segment is not clipped), so we inset 509 * it from the clip-bounds (since segment bounds can be off by 1). 510 * 511 * outsetClip is used for quick-reject (i.e. the segment is entirely outside), so we 512 * outset it from the clip-bounds. 513 */ 514 insetStorage.set(clip->getBounds()); 515 outsetStorage = insetStorage.makeOutset(1, 1); 516 insetStorage.inset(1, 1); 517 if (is_inverted(insetStorage)) { 518 /* 519 * our bounds checks assume the rects are never inverted. If insetting has 520 * created that, we assume that the area is too small to safely perform a 521 * quick-accept, so we just mark the rect as empty (so the quick-accept check 522 * will always fail. 523 */ 524 insetStorage.setEmpty(); // just so we don't pass an inverted rect 525 } 526 if (rclip.isRect()) { 527 insetClip = &insetStorage; 528 } 529 outsetClip = &outsetStorage; 530 } 531 } 532 533 SkPath::RawIter iter(path); 534 SkPoint pts[4], firstPt, lastPt; 535 SkPath::Verb verb, prevVerb; 536 SkAutoConicToQuads converter; 537 538 if (SkPaint::kButt_Cap != capStyle) { 539 prevVerb = SkPath::kDone_Verb; 540 } 541 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { 542 switch (verb) { 543 case SkPath::kMove_Verb: 544 firstPt = lastPt = pts[0]; 545 break; 546 case SkPath::kLine_Verb: 547 if (SkPaint::kButt_Cap != capStyle) { 548 extend_pts<capStyle>(prevVerb, iter.peek(), pts, 2); 549 } 550 lineproc(pts, 2, clip, blitter); 551 lastPt = pts[1]; 552 break; 553 case SkPath::kQuad_Verb: 554 if (SkPaint::kButt_Cap != capStyle) { 555 extend_pts<capStyle>(prevVerb, iter.peek(), pts, 3); 556 } 557 hairquad(pts, clip, insetClip, outsetClip, blitter, compute_quad_level(pts), lineproc); 558 lastPt = pts[2]; 559 break; 560 case SkPath::kConic_Verb: { 561 if (SkPaint::kButt_Cap != capStyle) { 562 extend_pts<capStyle>(prevVerb, iter.peek(), pts, 3); 563 } 564 // how close should the quads be to the original conic? 565 const SkScalar tol = SK_Scalar1 / 4; 566 const SkPoint* quadPts = converter.computeQuads(pts, 567 iter.conicWeight(), tol); 568 for (int i = 0; i < converter.countQuads(); ++i) { 569 int level = compute_quad_level(quadPts); 570 hairquad(quadPts, clip, insetClip, outsetClip, blitter, level, lineproc); 571 quadPts += 2; 572 } 573 lastPt = pts[2]; 574 break; 575 } 576 case SkPath::kCubic_Verb: { 577 if (SkPaint::kButt_Cap != capStyle) { 578 extend_pts<capStyle>(prevVerb, iter.peek(), pts, 4); 579 } 580 haircubic(pts, clip, insetClip, outsetClip, blitter, kMaxCubicSubdivideLevel, lineproc); 581 lastPt = pts[3]; 582 } break; 583 case SkPath::kClose_Verb: 584 pts[0] = lastPt; 585 pts[1] = firstPt; 586 if (SkPaint::kButt_Cap != capStyle && prevVerb == SkPath::kMove_Verb) { 587 // cap moveTo/close to match svg expectations for degenerate segments 588 extend_pts<capStyle>(prevVerb, iter.peek(), pts, 2); 589 } 590 lineproc(pts, 2, clip, blitter); 591 break; 592 case SkPath::kDone_Verb: 593 break; 594 } 595 if (SkPaint::kButt_Cap != capStyle) { 596 if (prevVerb == SkPath::kMove_Verb && 597 verb >= SkPath::kLine_Verb && verb <= SkPath::kCubic_Verb) { 598 firstPt = pts[0]; // the curve moved the initial point, so close to it instead 599 } 600 prevVerb = verb; 601 } 602 } 603 } 604 605 void SkScan::HairPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) { 606 hair_path<SkPaint::kButt_Cap>(path, clip, blitter, SkScan::HairLineRgn); 607 } 608 609 void SkScan::AntiHairPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) { 610 hair_path<SkPaint::kButt_Cap>(path, clip, blitter, SkScan::AntiHairLineRgn); 611 } 612 613 void SkScan::HairSquarePath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) { 614 hair_path<SkPaint::kSquare_Cap>(path, clip, blitter, SkScan::HairLineRgn); 615 } 616 617 void SkScan::AntiHairSquarePath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) { 618 hair_path<SkPaint::kSquare_Cap>(path, clip, blitter, SkScan::AntiHairLineRgn); 619 } 620 621 void SkScan::HairRoundPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) { 622 hair_path<SkPaint::kRound_Cap>(path, clip, blitter, SkScan::HairLineRgn); 623 } 624 625 void SkScan::AntiHairRoundPath(const SkPath& path, const SkRasterClip& clip, SkBlitter* blitter) { 626 hair_path<SkPaint::kRound_Cap>(path, clip, blitter, SkScan::AntiHairLineRgn); 627 } 628 629 /////////////////////////////////////////////////////////////////////////////// 630 631 void SkScan::FrameRect(const SkRect& r, const SkPoint& strokeSize, 632 const SkRasterClip& clip, SkBlitter* blitter) { 633 SkASSERT(strokeSize.fX >= 0 && strokeSize.fY >= 0); 634 635 if (strokeSize.fX < 0 || strokeSize.fY < 0) { 636 return; 637 } 638 639 const SkScalar dx = strokeSize.fX; 640 const SkScalar dy = strokeSize.fY; 641 SkScalar rx = SkScalarHalf(dx); 642 SkScalar ry = SkScalarHalf(dy); 643 SkRect outer, tmp; 644 645 outer.set(r.fLeft - rx, r.fTop - ry, 646 r.fRight + rx, r.fBottom + ry); 647 648 if (r.width() <= dx || r.height() <= dy) { 649 SkScan::FillRect(outer, clip, blitter); 650 return; 651 } 652 653 tmp.set(outer.fLeft, outer.fTop, outer.fRight, outer.fTop + dy); 654 SkScan::FillRect(tmp, clip, blitter); 655 tmp.fTop = outer.fBottom - dy; 656 tmp.fBottom = outer.fBottom; 657 SkScan::FillRect(tmp, clip, blitter); 658 659 tmp.set(outer.fLeft, outer.fTop + dy, outer.fLeft + dx, outer.fBottom - dy); 660 SkScan::FillRect(tmp, clip, blitter); 661 tmp.fLeft = outer.fRight - dx; 662 tmp.fRight = outer.fRight; 663 SkScan::FillRect(tmp, clip, blitter); 664 } 665 666 void SkScan::HairLine(const SkPoint pts[], int count, const SkRasterClip& clip, 667 SkBlitter* blitter) { 668 if (clip.isBW()) { 669 HairLineRgn(pts, count, &clip.bwRgn(), blitter); 670 } else { 671 const SkRegion* clipRgn = nullptr; 672 673 SkRect r; 674 r.set(pts, count); 675 r.outset(SK_ScalarHalf, SK_ScalarHalf); 676 677 SkAAClipBlitterWrapper wrap; 678 if (!clip.quickContains(r.roundOut())) { 679 wrap.init(clip, blitter); 680 blitter = wrap.getBlitter(); 681 clipRgn = &wrap.getRgn(); 682 } 683 HairLineRgn(pts, count, clipRgn, blitter); 684 } 685 } 686 687 void SkScan::AntiHairLine(const SkPoint pts[], int count, const SkRasterClip& clip, 688 SkBlitter* blitter) { 689 if (clip.isBW()) { 690 AntiHairLineRgn(pts, count, &clip.bwRgn(), blitter); 691 } else { 692 const SkRegion* clipRgn = nullptr; 693 694 SkRect r; 695 r.set(pts, count); 696 697 SkAAClipBlitterWrapper wrap; 698 if (!clip.quickContains(r.roundOut().makeOutset(1, 1))) { 699 wrap.init(clip, blitter); 700 blitter = wrap.getBlitter(); 701 clipRgn = &wrap.getRgn(); 702 } 703 AntiHairLineRgn(pts, count, clipRgn, blitter); 704 } 705 } 706