michael@0: michael@0: /* michael@0: * Copyright 2006 The Android Open Source Project michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: michael@0: #include "SkScan.h" michael@0: #include "SkBlitter.h" michael@0: #include "SkRasterClip.h" michael@0: #include "SkFDot6.h" michael@0: #include "SkLineClipper.h" michael@0: michael@0: static void horiline(int x, int stopx, SkFixed fy, SkFixed dy, michael@0: SkBlitter* blitter) { michael@0: SkASSERT(x < stopx); michael@0: michael@0: do { michael@0: blitter->blitH(x, fy >> 16, 1); michael@0: fy += dy; michael@0: } while (++x < stopx); michael@0: } michael@0: michael@0: static void vertline(int y, int stopy, SkFixed fx, SkFixed dx, michael@0: SkBlitter* blitter) { michael@0: SkASSERT(y < stopy); michael@0: michael@0: do { michael@0: blitter->blitH(fx >> 16, y, 1); michael@0: fx += dx; michael@0: } while (++y < stopy); michael@0: } michael@0: michael@0: #ifdef SK_DEBUG michael@0: static bool canConvertFDot6ToFixed(SkFDot6 x) { michael@0: const int maxDot6 = SK_MaxS32 >> (16 - 6); michael@0: return SkAbs32(x) <= maxDot6; michael@0: } michael@0: #endif michael@0: michael@0: void SkScan::HairLineRgn(const SkPoint& pt0, const SkPoint& pt1, michael@0: const SkRegion* clip, SkBlitter* blitter) { michael@0: SkBlitterClipper clipper; michael@0: SkRect r; michael@0: SkIRect clipR, ptsR; michael@0: SkPoint pts[2] = { pt0, pt1 }; michael@0: michael@0: // We have to pre-clip the line to fit in a SkFixed, so we just chop michael@0: // the line. TODO find a way to actually draw beyond that range. michael@0: { michael@0: SkRect fixedBounds; michael@0: const SkScalar max = SkIntToScalar(32767); michael@0: fixedBounds.set(-max, -max, max, max); michael@0: if (!SkLineClipper::IntersectLine(pts, fixedBounds, pts)) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: if (clip) { michael@0: // Perform a clip in scalar space, so we catch huge values which might michael@0: // be missed after we convert to SkFDot6 (overflow) michael@0: r.set(clip->getBounds()); michael@0: if (!SkLineClipper::IntersectLine(pts, r, pts)) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: SkFDot6 x0 = SkScalarToFDot6(pts[0].fX); michael@0: SkFDot6 y0 = SkScalarToFDot6(pts[0].fY); michael@0: SkFDot6 x1 = SkScalarToFDot6(pts[1].fX); michael@0: SkFDot6 y1 = SkScalarToFDot6(pts[1].fY); michael@0: michael@0: SkASSERT(canConvertFDot6ToFixed(x0)); michael@0: SkASSERT(canConvertFDot6ToFixed(y0)); michael@0: SkASSERT(canConvertFDot6ToFixed(x1)); michael@0: SkASSERT(canConvertFDot6ToFixed(y1)); michael@0: michael@0: if (clip) { michael@0: // now perform clipping again, as the rounding to dot6 can wiggle us michael@0: // our rects are really dot6 rects, but since we've already used michael@0: // lineclipper, we know they will fit in 32bits (26.6) michael@0: const SkIRect& bounds = clip->getBounds(); michael@0: michael@0: clipR.set(SkIntToFDot6(bounds.fLeft), SkIntToFDot6(bounds.fTop), michael@0: SkIntToFDot6(bounds.fRight), SkIntToFDot6(bounds.fBottom)); michael@0: ptsR.set(x0, y0, x1, y1); michael@0: ptsR.sort(); michael@0: michael@0: // outset the right and bottom, to account for how hairlines are michael@0: // actually drawn, which may hit the pixel to the right or below of michael@0: // the coordinate michael@0: ptsR.fRight += SK_FDot6One; michael@0: ptsR.fBottom += SK_FDot6One; michael@0: michael@0: if (!SkIRect::Intersects(ptsR, clipR)) { michael@0: return; michael@0: } michael@0: if (clip->isRect() && clipR.contains(ptsR)) { michael@0: clip = NULL; michael@0: } else { michael@0: blitter = clipper.apply(blitter, clip); michael@0: } michael@0: } michael@0: michael@0: SkFDot6 dx = x1 - x0; michael@0: SkFDot6 dy = y1 - y0; michael@0: michael@0: if (SkAbs32(dx) > SkAbs32(dy)) { // mostly horizontal michael@0: if (x0 > x1) { // we want to go left-to-right michael@0: SkTSwap(x0, x1); michael@0: SkTSwap(y0, y1); michael@0: } michael@0: int ix0 = SkFDot6Round(x0); michael@0: int ix1 = SkFDot6Round(x1); michael@0: if (ix0 == ix1) {// too short to draw michael@0: return; michael@0: } michael@0: michael@0: SkFixed slope = SkFixedDiv(dy, dx); michael@0: SkFixed startY = SkFDot6ToFixed(y0) + (slope * ((32 - x0) & 63) >> 6); michael@0: michael@0: horiline(ix0, ix1, startY, slope, blitter); michael@0: } else { // mostly vertical michael@0: if (y0 > y1) { // we want to go top-to-bottom michael@0: SkTSwap(x0, x1); michael@0: SkTSwap(y0, y1); michael@0: } michael@0: int iy0 = SkFDot6Round(y0); michael@0: int iy1 = SkFDot6Round(y1); michael@0: if (iy0 == iy1) { // too short to draw michael@0: return; michael@0: } michael@0: michael@0: SkFixed slope = SkFixedDiv(dx, dy); michael@0: SkFixed startX = SkFDot6ToFixed(x0) + (slope * ((32 - y0) & 63) >> 6); michael@0: michael@0: vertline(iy0, iy1, startX, slope, blitter); michael@0: } michael@0: } michael@0: michael@0: // we don't just draw 4 lines, 'cause that can leave a gap in the bottom-right michael@0: // and double-hit the top-left. michael@0: // TODO: handle huge coordinates on rect (before calling SkScalarToFixed) michael@0: void SkScan::HairRect(const SkRect& rect, const SkRasterClip& clip, michael@0: SkBlitter* blitter) { michael@0: SkAAClipBlitterWrapper wrapper; michael@0: SkBlitterClipper clipper; michael@0: SkIRect r; michael@0: michael@0: r.set(SkScalarToFixed(rect.fLeft) >> 16, michael@0: SkScalarToFixed(rect.fTop) >> 16, michael@0: (SkScalarToFixed(rect.fRight) >> 16) + 1, michael@0: (SkScalarToFixed(rect.fBottom) >> 16) + 1); michael@0: michael@0: if (clip.quickReject(r)) { michael@0: return; michael@0: } michael@0: if (!clip.quickContains(r)) { michael@0: const SkRegion* clipRgn; michael@0: if (clip.isBW()) { michael@0: clipRgn = &clip.bwRgn(); michael@0: } else { michael@0: wrapper.init(clip, blitter); michael@0: clipRgn = &wrapper.getRgn(); michael@0: blitter = wrapper.getBlitter(); michael@0: } michael@0: blitter = clipper.apply(blitter, clipRgn); michael@0: } michael@0: michael@0: int width = r.width(); michael@0: int height = r.height(); michael@0: michael@0: if ((width | height) == 0) { michael@0: return; michael@0: } michael@0: if (width <= 2 || height <= 2) { michael@0: blitter->blitRect(r.fLeft, r.fTop, width, height); michael@0: return; michael@0: } michael@0: // if we get here, we know we have 4 segments to draw michael@0: blitter->blitH(r.fLeft, r.fTop, width); // top michael@0: blitter->blitRect(r.fLeft, r.fTop + 1, 1, height - 2); // left michael@0: blitter->blitRect(r.fRight - 1, r.fTop + 1, 1, height - 2); // right michael@0: blitter->blitH(r.fLeft, r.fBottom - 1, width); // bottom michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: #include "SkPath.h" michael@0: #include "SkGeometry.h" michael@0: michael@0: static int compute_int_quad_dist(const SkPoint pts[3]) { michael@0: // compute the vector between the control point ([1]) and the middle of the michael@0: // line connecting the start and end ([0] and [2]) michael@0: SkScalar dx = SkScalarHalf(pts[0].fX + pts[2].fX) - pts[1].fX; michael@0: SkScalar dy = SkScalarHalf(pts[0].fY + pts[2].fY) - pts[1].fY; michael@0: // we want everyone to be positive michael@0: dx = SkScalarAbs(dx); michael@0: dy = SkScalarAbs(dy); michael@0: // convert to whole pixel values (use ceiling to be conservative) michael@0: int idx = SkScalarCeilToInt(dx); michael@0: int idy = SkScalarCeilToInt(dy); michael@0: // use the cheap approx for distance michael@0: if (idx > idy) { michael@0: return idx + (idy >> 1); michael@0: } else { michael@0: return idy + (idx >> 1); michael@0: } michael@0: } michael@0: michael@0: typedef void (*LineProc)(const SkPoint&, const SkPoint&, const SkRegion*, michael@0: SkBlitter*); michael@0: michael@0: static void hairquad(const SkPoint pts[3], const SkRegion* clip, michael@0: SkBlitter* blitter, int level, LineProc lineproc) { michael@0: if (level > 0) { michael@0: SkPoint tmp[5]; michael@0: michael@0: SkChopQuadAtHalf(pts, tmp); michael@0: hairquad(tmp, clip, blitter, level - 1, lineproc); michael@0: hairquad(&tmp[2], clip, blitter, level - 1, lineproc); michael@0: } else { michael@0: lineproc(pts[0], pts[2], clip, blitter); michael@0: } michael@0: } michael@0: michael@0: static void haircubic(const SkPoint pts[4], const SkRegion* clip, michael@0: SkBlitter* blitter, int level, LineProc lineproc) { michael@0: if (level > 0) { michael@0: SkPoint tmp[7]; michael@0: michael@0: SkChopCubicAt(pts, tmp, SK_Scalar1/2); michael@0: haircubic(tmp, clip, blitter, level - 1, lineproc); michael@0: haircubic(&tmp[3], clip, blitter, level - 1, lineproc); michael@0: } else { michael@0: lineproc(pts[0], pts[3], clip, blitter); michael@0: } michael@0: } michael@0: michael@0: #define kMaxCubicSubdivideLevel 6 michael@0: #define kMaxQuadSubdivideLevel 5 michael@0: michael@0: static int compute_quad_level(const SkPoint pts[3]) { michael@0: int d = compute_int_quad_dist(pts); michael@0: /* quadratics approach the line connecting their start and end points michael@0: 4x closer with each subdivision, so we compute the number of michael@0: subdivisions to be the minimum need to get that distance to be less michael@0: than a pixel. michael@0: */ michael@0: int level = (33 - SkCLZ(d)) >> 1; michael@0: // sanity check on level (from the previous version) michael@0: if (level > kMaxQuadSubdivideLevel) { michael@0: level = kMaxQuadSubdivideLevel; michael@0: } michael@0: return level; michael@0: } michael@0: michael@0: static void hair_path(const SkPath& path, const SkRasterClip& rclip, michael@0: SkBlitter* blitter, LineProc lineproc) { michael@0: if (path.isEmpty()) { michael@0: return; michael@0: } michael@0: michael@0: SkAAClipBlitterWrapper wrap; michael@0: const SkRegion* clip = NULL; michael@0: michael@0: { michael@0: SkIRect ibounds; michael@0: path.getBounds().roundOut(&ibounds); michael@0: ibounds.inset(-1, -1); michael@0: michael@0: if (rclip.quickReject(ibounds)) { michael@0: return; michael@0: } michael@0: if (!rclip.quickContains(ibounds)) { michael@0: if (rclip.isBW()) { michael@0: clip = &rclip.bwRgn(); michael@0: } else { michael@0: wrap.init(rclip, blitter); michael@0: blitter = wrap.getBlitter(); michael@0: clip = &wrap.getRgn(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: SkPath::Iter iter(path, false); michael@0: SkPoint pts[4]; michael@0: SkPath::Verb verb; michael@0: SkAutoConicToQuads converter; michael@0: michael@0: while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) { michael@0: switch (verb) { michael@0: case SkPath::kMove_Verb: michael@0: break; michael@0: case SkPath::kLine_Verb: michael@0: lineproc(pts[0], pts[1], clip, blitter); michael@0: break; michael@0: case SkPath::kQuad_Verb: michael@0: hairquad(pts, clip, blitter, compute_quad_level(pts), lineproc); michael@0: break; michael@0: case SkPath::kConic_Verb: { michael@0: // how close should the quads be to the original conic? michael@0: const SkScalar tol = SK_Scalar1 / 4; michael@0: const SkPoint* quadPts = converter.computeQuads(pts, michael@0: iter.conicWeight(), tol); michael@0: for (int i = 0; i < converter.countQuads(); ++i) { michael@0: int level = compute_quad_level(quadPts); michael@0: hairquad(quadPts, clip, blitter, level, lineproc); michael@0: quadPts += 2; michael@0: } michael@0: break; michael@0: } michael@0: case SkPath::kCubic_Verb: michael@0: haircubic(pts, clip, blitter, kMaxCubicSubdivideLevel, lineproc); michael@0: break; michael@0: case SkPath::kClose_Verb: michael@0: break; michael@0: case SkPath::kDone_Verb: michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void SkScan::HairPath(const SkPath& path, const SkRasterClip& clip, michael@0: SkBlitter* blitter) { michael@0: hair_path(path, clip, blitter, SkScan::HairLineRgn); michael@0: } michael@0: michael@0: void SkScan::AntiHairPath(const SkPath& path, const SkRasterClip& clip, michael@0: SkBlitter* blitter) { michael@0: hair_path(path, clip, blitter, SkScan::AntiHairLineRgn); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: void SkScan::FrameRect(const SkRect& r, const SkPoint& strokeSize, michael@0: const SkRasterClip& clip, SkBlitter* blitter) { michael@0: SkASSERT(strokeSize.fX >= 0 && strokeSize.fY >= 0); michael@0: michael@0: if (strokeSize.fX < 0 || strokeSize.fY < 0) { michael@0: return; michael@0: } michael@0: michael@0: const SkScalar dx = strokeSize.fX; michael@0: const SkScalar dy = strokeSize.fY; michael@0: SkScalar rx = SkScalarHalf(dx); michael@0: SkScalar ry = SkScalarHalf(dy); michael@0: SkRect outer, tmp; michael@0: michael@0: outer.set(r.fLeft - rx, r.fTop - ry, michael@0: r.fRight + rx, r.fBottom + ry); michael@0: michael@0: if (r.width() <= dx || r.height() <= dx) { michael@0: SkScan::FillRect(outer, clip, blitter); michael@0: return; michael@0: } michael@0: michael@0: tmp.set(outer.fLeft, outer.fTop, outer.fRight, outer.fTop + dy); michael@0: SkScan::FillRect(tmp, clip, blitter); michael@0: tmp.fTop = outer.fBottom - dy; michael@0: tmp.fBottom = outer.fBottom; michael@0: SkScan::FillRect(tmp, clip, blitter); michael@0: michael@0: tmp.set(outer.fLeft, outer.fTop + dy, outer.fLeft + dx, outer.fBottom - dy); michael@0: SkScan::FillRect(tmp, clip, blitter); michael@0: tmp.fLeft = outer.fRight - dx; michael@0: tmp.fRight = outer.fRight; michael@0: SkScan::FillRect(tmp, clip, blitter); michael@0: } michael@0: michael@0: void SkScan::HairLine(const SkPoint& p0, const SkPoint& p1, michael@0: const SkRasterClip& clip, SkBlitter* blitter) { michael@0: if (clip.isBW()) { michael@0: HairLineRgn(p0, p1, &clip.bwRgn(), blitter); michael@0: } else { michael@0: const SkRegion* clipRgn = NULL; michael@0: SkRect r; michael@0: SkIRect ir; michael@0: r.set(p0.fX, p0.fY, p1.fX, p1.fY); michael@0: r.sort(); michael@0: r.inset(-SK_ScalarHalf, -SK_ScalarHalf); michael@0: r.roundOut(&ir); michael@0: michael@0: SkAAClipBlitterWrapper wrap; michael@0: if (!clip.quickContains(ir)) { michael@0: wrap.init(clip, blitter); michael@0: blitter = wrap.getBlitter(); michael@0: clipRgn = &wrap.getRgn(); michael@0: } michael@0: HairLineRgn(p0, p1, clipRgn, blitter); michael@0: } michael@0: } michael@0: michael@0: void SkScan::AntiHairLine(const SkPoint& p0, const SkPoint& p1, michael@0: const SkRasterClip& clip, SkBlitter* blitter) { michael@0: if (clip.isBW()) { michael@0: AntiHairLineRgn(p0, p1, &clip.bwRgn(), blitter); michael@0: } else { michael@0: const SkRegion* clipRgn = NULL; michael@0: SkRect r; michael@0: SkIRect ir; michael@0: r.set(p0.fX, p0.fY, p1.fX, p1.fY); michael@0: r.sort(); michael@0: r.roundOut(&ir); michael@0: ir.inset(-1, -1); michael@0: michael@0: SkAAClipBlitterWrapper wrap; michael@0: if (!clip.quickContains(ir)) { michael@0: wrap.init(clip, blitter); michael@0: blitter = wrap.getBlitter(); michael@0: clipRgn = &wrap.getRgn(); michael@0: } michael@0: AntiHairLineRgn(p0, p1, clipRgn, blitter); michael@0: } michael@0: }