SkScan_Hairline.cpp revision fbfcd5602128ec010c82cb733c9cdc0a3254f9f3
1 2/* 3 * Copyright 2006 The Android Open Source Project 4 * 5 * Use of this source code is governed by a BSD-style license that can be 6 * found in the LICENSE file. 7 */ 8 9 10#include "SkScan.h" 11#include "SkBlitter.h" 12#include "SkRasterClip.h" 13#include "SkFDot6.h" 14#include "SkLineClipper.h" 15 16static 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 26static 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 36static bool canConvertFDot6ToFixed(SkFDot6 x) { 37 const int maxDot6 = SK_MaxS32 >> (16 - 6); 38 return SkAbs32(x) <= maxDot6; 39} 40 41void SkScan::HairLineRgn(const SkPoint& pt0, const SkPoint& pt1, 42 const SkRegion* clip, SkBlitter* blitter) { 43 SkBlitterClipper clipper; 44 SkRect r; 45 SkIRect clipR, ptsR; 46 SkPoint pts[2] = { pt0, pt1 }; 47 48#ifdef SK_SCALAR_IS_FLOAT 49 // We have to pre-clip the line to fit in a SkFixed, so we just chop 50 // the line. TODO find a way to actually draw beyond that range. 51 { 52 SkRect fixedBounds; 53 const SkScalar max = SkIntToScalar(32767); 54 fixedBounds.set(-max, -max, max, max); 55 if (!SkLineClipper::IntersectLine(pts, fixedBounds, pts)) { 56 return; 57 } 58 } 59#endif 60 61 if (clip) { 62 // Perform a clip in scalar space, so we catch huge values which might 63 // be missed after we convert to SkFDot6 (overflow) 64 r.set(clip->getBounds()); 65 if (!SkLineClipper::IntersectLine(pts, r, pts)) { 66 return; 67 } 68 } 69 70 SkFDot6 x0 = SkScalarToFDot6(pts[0].fX); 71 SkFDot6 y0 = SkScalarToFDot6(pts[0].fY); 72 SkFDot6 x1 = SkScalarToFDot6(pts[1].fX); 73 SkFDot6 y1 = SkScalarToFDot6(pts[1].fY); 74 75 SkASSERT(canConvertFDot6ToFixed(x0)); 76 SkASSERT(canConvertFDot6ToFixed(y0)); 77 SkASSERT(canConvertFDot6ToFixed(x1)); 78 SkASSERT(canConvertFDot6ToFixed(y1)); 79 80 if (clip) { 81 // now perform clipping again, as the rounding to dot6 can wiggle us 82 // our rects are really dot6 rects, but since we've already used 83 // lineclipper, we know they will fit in 32bits (26.6) 84 const SkIRect& bounds = clip->getBounds(); 85 86 clipR.set(SkIntToFDot6(bounds.fLeft), SkIntToFDot6(bounds.fTop), 87 SkIntToFDot6(bounds.fRight), SkIntToFDot6(bounds.fBottom)); 88 ptsR.set(x0, y0, x1, y1); 89 ptsR.sort(); 90 91 // outset the right and bottom, to account for how hairlines are 92 // actually drawn, which may hit the pixel to the right or below of 93 // the coordinate 94 ptsR.fRight += SK_FDot6One; 95 ptsR.fBottom += SK_FDot6One; 96 97 if (!SkIRect::Intersects(ptsR, clipR)) { 98 return; 99 } 100 if (clip->isRect() && clipR.contains(ptsR)) { 101 clip = NULL; 102 } else { 103 blitter = clipper.apply(blitter, 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 return; 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 return; 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// 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) 146void 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 194static bool quad_too_curvy(const SkPoint pts[3]) { 195 return true; 196} 197 198static 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 = SkScalarCeil(dx); 208 int idy = SkScalarCeil(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 217static void hairquad(const SkPoint pts[3], const SkRegion* clip, SkBlitter* blitter, int level, 218 void (*lineproc)(const SkPoint&, const SkPoint&, const SkRegion* clip, SkBlitter*)) 219{ 220#if 1 221 if (level > 0 && quad_too_curvy(pts)) 222 { 223 SkPoint tmp[5]; 224 225 SkChopQuadAtHalf(pts, tmp); 226 hairquad(tmp, clip, blitter, level - 1, lineproc); 227 hairquad(&tmp[2], clip, blitter, level - 1, lineproc); 228 } 229 else 230 lineproc(pts[0], pts[2], clip, blitter); 231#else 232 int count = 1 << level; 233 const SkScalar dt = SkFixedToScalar(SK_Fixed1 >> level); 234 SkScalar t = dt; 235 SkPoint prevPt = pts[0]; 236 for (int i = 1; i < count; i++) { 237 SkPoint nextPt; 238 SkEvalQuadAt(pts, t, &nextPt); 239 lineproc(prevPt, nextPt, clip, blitter); 240 t += dt; 241 prevPt = nextPt; 242 } 243 // draw the last line explicitly to 1.0, in case t didn't match that exactly 244 lineproc(prevPt, pts[2], clip, blitter); 245#endif 246} 247 248static bool cubic_too_curvy(const SkPoint pts[4]) 249{ 250 return true; 251} 252 253static void haircubic(const SkPoint pts[4], const SkRegion* clip, SkBlitter* blitter, int level, 254 void (*lineproc)(const SkPoint&, const SkPoint&, const SkRegion*, SkBlitter*)) 255{ 256 if (level > 0 && cubic_too_curvy(pts)) 257 { 258 SkPoint tmp[7]; 259 260 SkChopCubicAt(pts, tmp, SK_Scalar1/2); 261 haircubic(tmp, clip, blitter, level - 1, lineproc); 262 haircubic(&tmp[3], clip, blitter, level - 1, lineproc); 263 } 264 else 265 lineproc(pts[0], pts[3], clip, blitter); 266} 267 268#define kMaxCubicSubdivideLevel 6 269#define kMaxQuadSubdivideLevel 5 270 271static void hair_path(const SkPath& path, const SkRasterClip& rclip, SkBlitter* blitter, 272 void (*lineproc)(const SkPoint&, const SkPoint&, const SkRegion*, SkBlitter*)) 273{ 274 if (path.isEmpty()) { 275 return; 276 } 277 278 SkAAClipBlitterWrapper wrap; 279 const SkIRect* clipR = NULL; 280 const SkRegion* clip = NULL; 281 282 { 283 SkIRect ibounds; 284 path.getBounds().roundOut(&ibounds); 285 ibounds.inset(-1, -1); 286 287 if (rclip.quickReject(ibounds)) { 288 return; 289 } 290 if (!rclip.quickContains(ibounds)) { 291 clipR = &rclip.getBounds(); 292 if (rclip.isBW()) { 293 clip = &rclip.bwRgn(); 294 } else { 295 wrap.init(rclip, blitter); 296 blitter = wrap.getBlitter(); 297 clip = &wrap.getRgn(); 298 } 299 } 300 } 301 302 SkPath::Iter iter(path, false); 303 SkPoint pts[4]; 304 SkPath::Verb verb; 305 306 while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) { 307 switch (verb) { 308 case SkPath::kLine_Verb: 309 lineproc(pts[0], pts[1], clip, blitter); 310 break; 311 case SkPath::kQuad_Verb: { 312 int d = compute_int_quad_dist(pts); 313 /* quadratics approach the line connecting their start and end points 314 4x closer with each subdivision, so we compute the number of 315 subdivisions to be the minimum need to get that distance to be less 316 than a pixel. 317 */ 318 int level = (33 - SkCLZ(d)) >> 1; 319 // SkDebugf("----- distance %d computedLevel %d\n", d, computedLevel); 320 // sanity check on level (from the previous version) 321 if (level > kMaxQuadSubdivideLevel) { 322 level = kMaxQuadSubdivideLevel; 323 } 324 hairquad(pts, clip, blitter, level, lineproc); 325 break; 326 } 327 case SkPath::kCubic_Verb: 328 haircubic(pts, clip, blitter, kMaxCubicSubdivideLevel, lineproc); 329 break; 330 default: 331 break; 332 } 333 } 334} 335 336void SkScan::HairPath(const SkPath& path, const SkRasterClip& clip, 337 SkBlitter* blitter) { 338 hair_path(path, clip, blitter, SkScan::HairLineRgn); 339} 340 341void SkScan::AntiHairPath(const SkPath& path, const SkRasterClip& clip, 342 SkBlitter* blitter) { 343 hair_path(path, clip, blitter, SkScan::AntiHairLineRgn); 344} 345 346/////////////////////////////////////////////////////////////////////////////// 347 348void SkScan::FrameRect(const SkRect& r, const SkPoint& strokeSize, 349 const SkRasterClip& clip, SkBlitter* blitter) { 350 SkASSERT(strokeSize.fX >= 0 && strokeSize.fY >= 0); 351 352 if (strokeSize.fX < 0 || strokeSize.fY < 0) { 353 return; 354 } 355 356 const SkScalar dx = strokeSize.fX; 357 const SkScalar dy = strokeSize.fY; 358 SkScalar rx = SkScalarHalf(dx); 359 SkScalar ry = SkScalarHalf(dy); 360 SkRect outer, tmp; 361 362 outer.set(r.fLeft - rx, r.fTop - ry, 363 r.fRight + rx, r.fBottom + ry); 364 365 if (r.width() <= dx || r.height() <= dx) { 366 SkScan::FillRect(outer, clip, blitter); 367 return; 368 } 369 370 tmp.set(outer.fLeft, outer.fTop, outer.fRight, outer.fTop + dy); 371 SkScan::FillRect(tmp, clip, blitter); 372 tmp.fTop = outer.fBottom - dy; 373 tmp.fBottom = outer.fBottom; 374 SkScan::FillRect(tmp, clip, blitter); 375 376 tmp.set(outer.fLeft, outer.fTop + dy, outer.fLeft + dx, outer.fBottom - dy); 377 SkScan::FillRect(tmp, clip, blitter); 378 tmp.fLeft = outer.fRight - dx; 379 tmp.fRight = outer.fRight; 380 SkScan::FillRect(tmp, clip, blitter); 381} 382 383void SkScan::HairLine(const SkPoint& p0, const SkPoint& p1, 384 const SkRasterClip& clip, SkBlitter* blitter) { 385 if (clip.isBW()) { 386 HairLineRgn(p0, p1, &clip.bwRgn(), blitter); 387 } else { 388 const SkRegion* clipRgn = NULL; 389 SkRect r; 390 SkIRect ir; 391 r.set(p0.fX, p0.fY, p1.fX, p1.fY); 392 r.sort(); 393 r.inset(-SK_ScalarHalf, -SK_ScalarHalf); 394 r.roundOut(&ir); 395 396 SkAAClipBlitterWrapper wrap; 397 if (!clip.quickContains(ir)) { 398 wrap.init(clip, blitter); 399 blitter = wrap.getBlitter(); 400 clipRgn = &wrap.getRgn(); 401 } 402 HairLineRgn(p0, p1, clipRgn, blitter); 403 } 404} 405 406void SkScan::AntiHairLine(const SkPoint& p0, const SkPoint& p1, 407 const SkRasterClip& clip, SkBlitter* blitter) { 408 if (clip.isBW()) { 409 AntiHairLineRgn(p0, p1, &clip.bwRgn(), blitter); 410 } else { 411 const SkRegion* clipRgn = NULL; 412 SkRect r; 413 SkIRect ir; 414 r.set(p0.fX, p0.fY, p1.fX, p1.fY); 415 r.sort(); 416 r.roundOut(&ir); 417 ir.inset(-1, -1); 418 419 SkAAClipBlitterWrapper wrap; 420 if (!clip.quickContains(ir)) { 421 wrap.init(clip, blitter); 422 blitter = wrap.getBlitter(); 423 clipRgn = &wrap.getRgn(); 424 } 425 AntiHairLineRgn(p0, p1, clipRgn, blitter); 426 } 427} 428