SkRect clipBounds; if (clip) {
clipBounds.set(clip->getBounds());
}
for (size_t i = 0; i < src.size() - 1; ++i) {
SkBlitter* blitter = origBlitter;
SkPoint pts[2];
// We have to pre-clip the line to fit in a SkFixed, so we just chop // the line. TODO find a way to actually draw beyond that range. if (!SkLineClipper::IntersectLine(&src[i], fixedBounds, pts)) { continue;
}
// Perform a clip in scalar space, so we catch huge values which might // be missed after we convert to SkFDot6 (overflow) if (clip && !SkLineClipper::IntersectLine(pts, clipBounds, pts)) { continue;
}
if (clip) { // now perform clipping again, as the rounding to dot6 can wiggle us // our rects are really dot6 rects, but since we've already used // lineclipper, we know they will fit in 32bits (26.6) const SkIRect& bounds = clip->getBounds();
// outset the right and bottom, to account for how hairlines are // actually drawn, which may hit the pixel to the right or below of // the coordinate
ptsR.fRight += SK_FDot6One;
ptsR.fBottom += SK_FDot6One;
if (!SkIRect::Intersects(ptsR, clipR)) { continue;
} if (!clip->isRect() || !clipR.contains(ptsR)) {
blitter = clipper.apply(origBlitter, clip);
}
}
SkFDot6 dx = x1 - x0;
SkFDot6 dy = y1 - y0;
if (SkAbs32(dx) > SkAbs32(dy)) { // mostly horizontal if (x0 > x1) { // we want to go left-to-right
using std::swap;
swap(x0, x1);
swap(y0, y1);
} int ix0 = SkFDot6Round(x0); int ix1 = SkFDot6Round(x1); if (ix0 == ix1) {// too short to draw continue;
} #ifdefined(SK_BUILD_FOR_FUZZER) if ((ix1 - ix0) > 100000 || (ix1 - ix0) < 0) { continue; // too big to draw
} #endif
SkFixed slope = SkFixedDiv(dy, dx);
SkFixed startY = SkFDot6ToFixed(y0) + (slope * ((32 - x0) & 63) >> 6);
horiline(ix0, ix1, startY, slope, blitter);
} else { // mostly vertical if (y0 > y1) { // we want to go top-to-bottom
using std::swap;
swap(x0, x1);
swap(y0, y1);
} int iy0 = SkFDot6Round(y0); int iy1 = SkFDot6Round(y1); if (iy0 == iy1) { // too short to draw continue;
} #ifdefined(SK_BUILD_FOR_FUZZER) if ((iy1 - iy0) > 100000 || (iy1 - iy0) < 0) { continue; // too big to draw
} #endif
SkFixed slope = SkFixedDiv(dx, dy);
SkFixed startX = SkFDot6ToFixed(x0) + (slope * ((32 - y0) & 63) >> 6);
// we don't just draw 4 lines, 'cause that can leave a gap in the bottom-right // and double-hit the top-left. void SkScan::HairRect(const SkRect& rect, const SkRasterClip& clip, SkBlitter* blitter) {
SkAAClipBlitterWrapper wrapper;
SkBlitterClipper clipper; // Create the enclosing bounds of the hairrect. i.e. we will stroke the interior of r.
SkIRect r = SkIRect::MakeLTRB(SkScalarFloorToInt(rect.fLeft),
SkScalarFloorToInt(rect.fTop),
SkScalarFloorToInt(rect.fRight + 1),
SkScalarFloorToInt(rect.fBottom + 1));
// Note: r might be crazy big, if rect was huge, possibly getting pinned to max/min s32. // We need to trim it back to something reasonable before we can query its width etc. // since r.fRight - r.fLeft might wrap around to negative even if fRight > fLeft. // // We outset the clip bounds by 1 before intersecting, since r is being stroked and not filled // so we don't want to pin an edge of it to the clip. The intersect's job is mostly to just // get the actual edge values into a reasonable range (e.g. so width() can't overflow). if (!r.intersect(clip.getBounds().makeOutset(1, 1))) { return;
}
static uint32_t compute_int_quad_dist(const SkPoint pts[3]) { // compute the vector between the control point ([1]) and the middle of the // line connecting the start and end ([0] and [2])
SkScalar dx = SkScalarHalf(pts[0].fX + pts[2].fX) - pts[1].fX;
SkScalar dy = SkScalarHalf(pts[0].fY + pts[2].fY) - pts[1].fY; // we want everyone to be positive
dx = SkScalarAbs(dx);
dy = SkScalarAbs(dy); // convert to whole pixel values (use ceiling to be conservative). // assign to unsigned so we can safely add 1/2 of the smaller and still fit in // uint32_t, since SkScalarCeilToInt() returns 31 bits at most.
uint32_t idx = SkScalarCeilToInt(dx);
uint32_t idy = SkScalarCeilToInt(dy); // use the cheap approx for distance if (idx > idy) { return idx + (idy >> 1);
} else { return idy + (idx >> 1);
}
}
// Draw a quadratic by subdividing it into a series of line segments. // Assuming none of those points are infinite/nan, then draw the line. staticvoid hair_quad(const SkPoint pts[3], const SkRegion* clip,
SkBlitter* blitter, int level,
SkScan::HairRgnProc lineproc) {
SkASSERT(level <= kMaxQuadSubdivideLevel);
// Convert the quadratic points into coefficients for the form: p(t) = At^2 + Bt + C
SkQuadCoeff coeff(pts);
tmp[0] = pts[0];
float2 A = coeff.fA;
float2 B = coeff.fB;
float2 C = coeff.fC;
mask2 is_finite(~0); // start out as true for (unsigned i = 1; i < lines; ++i) {
t = t + dt;
float2 p = (A * t + B) * t + C;
is_finite &= float2_is_finite(p);
p.store(&tmp[i]);
} if (all(is_finite)) {
tmp[lines] = pts[2];
lineproc({tmp, lines + 1}, clip, blitter);
}
}
// Can't call SkRect::intersects, since it cares about empty, and we don't (since we tracking // something to be stroked, so empty can still draw something (e.g. horizontal line) staticbool geometric_overlap(const SkRect& a, const SkRect& b) {
SkASSERT(!is_inverted(a) && !is_inverted(b)); return a.fLeft < b.fRight && b.fLeft < a.fRight &&
a.fTop < b.fBottom && b.fTop < a.fBottom;
}
// Can't call SkRect::contains, since it cares about empty, and we don't (since we tracking // something to be stroked, so empty can still draw something (e.g. horizontal line) staticbool geometric_contains(const SkRect& outer, const SkRect& inner) {
SkASSERT(!is_inverted(outer) && !is_inverted(inner)); return inner.fRight <= outer.fRight && inner.fLeft >= outer.fLeft &&
inner.fBottom <= outer.fBottom && inner.fTop >= outer.fTop;
}
// The off-curve points are "inside" the limits of the on-curve pts staticbool quick_cubic_niceness_check(const SkPoint pts[4]) { return lt_90(pts[1], pts[0], pts[3]) &&
lt_90(pts[2], pts[0], pts[3]) &&
lt_90(pts[1], pts[3], pts[0]) &&
lt_90(pts[2], pts[3], pts[0]);
}
// Draw a cubic by subdividing it into a series of line segments. // Assuming none of those points are infinite/nan, then draw the line. staticvoid hair_cubic(const SkPoint pts[4], const SkRegion* clip,
SkBlitter* blitter,
SkScan::HairRgnProc lineproc) { const size_t lines = compute_cubic_segs(pts);
SkASSERT(lines > 0); if (1 == lines) {
lineproc({{pts[0], pts[3]}}, clip, blitter); return;
}
// Convert the cubic points into coefficients for the form: p(t) = At^3 + Bt^2 + Ct + D
SkCubicCoeff coeff(pts);
tmp[0] = pts[0];
float2 A = coeff.fA;
float2 B = coeff.fB;
float2 C = coeff.fC;
float2 D = coeff.fD;
mask2 is_finite(~0); // start out as true for (unsigned i = 1; i < lines; ++i) {
t = t + dt;
float2 p = ((A * t + B) * t + C) * t + D;
is_finite &= float2_is_finite(p);
p.store(&tmp[i]);
} if (all(is_finite)) {
tmp[lines] = pts[3];
lineproc({tmp, lines + 1}, clip, blitter);
} // else some point(s) are non-finite, so don't draw
}
/* Extend the points in the direction of the starting or ending tangent by 1/2 unit to accountforaroundorsquarecap.Ifthere'snodistancebetweentheendpointand thecontrolpoint,usethenextcontrolpointtocreateatangent.Ifthecurve
is degenerate, move the cap out 1/2 unit horizontally. */ template <SkPaint::Cap capStyle> void extend_pts(std::optional<SkPathVerb> prevVerb, std::optional<SkPathVerb> nextVerb,
SkSpan<SkPoint> pts) {
SkASSERT(SkPaint::kSquare_Cap == capStyle || SkPaint::kRound_Cap == capStyle); // The area of a circle is PI*R*R. For a unit circle, R=1/2, and the cap covers half of that. const SkScalar capOutset = SkPaint::kSquare_Cap == capStyle ? 0.5f : SK_ScalarPI / 8; if (optional_eq(prevVerb, SkPathVerb::kMove)) {
SkPoint* first = pts.data();
SkPoint* ctrl = first;
size_t controls = pts.size() - 1;
SkVector tangent; do {
tangent = *first - *++ctrl;
} while (tangent.isZero() && --controls > 0); if (tangent.isZero()) {
tangent.set(1, 0);
controls = pts.size() - 1; // If all points are equal, move all but one
} else {
tangent.normalize();
} do { // If the end point and control points are equal, loop to move them in tandem.
first->fX += tangent.fX * capOutset;
first->fY += tangent.fY * capOutset;
++first;
} while (++controls < pts.size());
} if (!nextVerb.has_value() ||
nextVerb.value() == SkPathVerb::kMove ||
nextVerb.value() == SkPathVerb::kClose)
{
SkPoint* last = &pts.back();
SkPoint* ctrl = last;
size_t controls = pts.size() - 1;
SkVector tangent; do {
tangent = *last - *--ctrl;
} while (tangent.isZero() && --controls > 0); if (tangent.isZero()) {
tangent.set(-1, 0);
controls = pts.size() - 1;
} else {
tangent.normalize();
} do {
last->fX += tangent.fX * capOutset;
last->fY += tangent.fY * capOutset;
--last;
} while (++controls < pts.size());
}
}
staticinlinevoid hairconic(const SkPoint* p, DrawingParameters d, float conicWeight) {
SkAutoConicToQuads converter; // how close should the quads be to the original conic? const SkScalar tol = SK_Scalar1 / 4; const SkPoint* quadPts = converter.computeQuads(p, conicWeight, tol); for (int i = 0; i < converter.countQuads(); ++i) { int level = compute_quad_level(quadPts);
hairquad(quadPts, d, level);
quadPts += 2;
}
}
// This function assumes that iter is currently ON a SkPathVerb::kMove verb staticinlinebool is_next_contour_closed(SkPathIter scanner) { // we assume the first verb is already a move, so do scanner.next() to proceed to the next verb. // This will ideally be a contour verb or a close auto rec = scanner.next(); if (rec->fVerb == SkPathVerb::kClose) return true; if (rec->fVerb == SkPathVerb::kMove) returnfalse;
while (scanner.peekNextVerb().has_value()) {
SkPathVerb next_verb = scanner.peekNextVerb().value(); // The current contour ends here, before this kMove. if (next_verb == SkPathVerb::kMove) returnfalse; // This kClose ends the current contour. if (next_verb == SkPathVerb::kClose) return true;
scanner.next(); // Consume the record
} returnfalse;
}
if (r.width() <= dx || r.height() <= dy) { // If we're empty on either axis, we remove the outset amount, to be sure // we stroke the same way a polygon would (i.e. it would just see a "line" // and not extend it for the miter join). if (r.width() == 0) {
outer.fTop = r.fTop;
outer.fBottom = r.fBottom;
} if (r.height() == 0) {
outer.fLeft = r.fLeft;
outer.fRight = r.fRight;
}
SkScan::FillRect(outer, clip, blitter); return;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.