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 || 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