Fix GrAAConvexTessellator colinear point removal.

We weren't checking whether the new point in lineTo was backtracking or not.

Also:
Replace distance-to-line check with triangle area check. The previous check
was asymmetric. Given point sequence (a, b, c) it might make a different
decision than when given (c, b, a).

Compute normals late since we don't use them to detect colinear edges
anymore.

Rename SkPointPriv::SetOrhog -> SkPointPriv::MakeOrthog and return
computed value rather than take SkPoint* dst.

Bug: chromium:869172


Change-Id: I8da53edf1a2e6098f4199da57368ebb644866e4c
Reviewed-on: https://skia-review.googlesource.com/150682
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
This commit is contained in:
Brian Salomon 2018-08-31 12:04:18 -04:00 committed by Skia Commit-Bot
parent d709a85751
commit 0235c648b7
7 changed files with 90 additions and 93 deletions

View File

@ -317,9 +317,8 @@ protected:
this->drawPath(canvas, i, &offset);
}
// Repro for crbug.com/472723 (Missing AA on portions of graphic with GPU rasterization)
{
canvas->translate(356.0f, 50.0f);
// Repro for crbug.com/472723 (Missing AA on portions of graphic with GPU rasterization)
SkPaint p;
p.setAntiAlias(true);
@ -334,7 +333,31 @@ protected:
p1.lineTo(59.4380493f, 364.671021f);
p1.lineTo(385.414276f, 690.647217f);
p1.lineTo(386.121399f, 689.940125f);
canvas->save();
canvas->translate(356.0f, 50.0f);
canvas->drawPath(p1, p);
canvas->restore();
// Repro for crbug.com/869172 (SVG path incorrectly simplified when using GPU
// Rasterization). This will only draw anything in the stroke-and-fill version.
SkPath p2;
p2.moveTo(10.f, 0.f);
p2.lineTo(38.f, 0.f);
p2.lineTo(66.f, 0.f);
p2.lineTo(94.f, 0.f);
p2.lineTo(122.f, 0.f);
p2.lineTo(150.f, 0.f);
p2.lineTo(150.f, 0.f);
p2.lineTo(122.f, 0.f);
p2.lineTo(94.f, 0.f);
p2.lineTo(66.f, 0.f);
p2.lineTo(38.f, 0.f);
p2.lineTo(10.f, 0.f);
p2.close();
canvas->save();
canvas->translate(0.0f, 500.0f);
canvas->drawPath(p2, p);
canvas->restore();
}
}

View File

@ -97,17 +97,9 @@ public:
static bool SetLengthFast(SkPoint* pt, float length);
static void SetOrthog(SkPoint* pt, const SkPoint& vec, Side side = kLeft_Side) {
// vec could be this
SkScalar tmp = vec.fX;
if (kRight_Side == side) {
pt->fX = -vec.fY;
pt->fY = tmp;
} else {
SkASSERT(kLeft_Side == side);
pt->fX = vec.fY;
pt->fY = -tmp;
}
static SkPoint MakeOrthog(const SkPoint& vec, Side side = kLeft_Side) {
SkASSERT(side == kRight_Side || side == kLeft_Side);
return (side == kRight_Side) ? SkPoint{-vec.fY, vec.fX} : SkPoint{vec.fY, -vec.fX};
}
// counter-clockwise fan

View File

@ -253,7 +253,7 @@ void GrPathUtils::QuadUVMatrix::set(const SkPoint qPts[3]) {
// when looking from the point 0 down the line we want positive
// distances to be to the left. This matches the non-degenerate
// case.
SkPointPriv::SetOrthog(&lineVec, lineVec, SkPointPriv::kLeft_Side);
lineVec = SkPointPriv::MakeOrthog(lineVec, SkPointPriv::kLeft_Side);
// first row
fM[0] = 0;
fM[1] = 0;
@ -498,9 +498,9 @@ void convert_noninflect_cubic_to_quads(const SkPoint p[4],
if (constrainWithinTangents &&
!is_point_within_cubic_tangents(p[0], ab, dc, p[3], dir, cAvg)) {
// choose a new cAvg that is the intersection of the two tangent lines.
SkPointPriv::SetOrthog(&ab, ab);
ab = SkPointPriv::MakeOrthog(ab);
SkScalar z0 = -ab.dot(p[0]);
SkPointPriv::SetOrthog(&dc, dc);
dc = SkPointPriv::MakeOrthog(dc);
SkScalar z1 = -dc.dot(p[3]);
cAvg.fX = ab.fY * z1 - z0 * dc.fY;
cAvg.fY = z0 * dc.fX - ab.fX * z1;

View File

@ -147,7 +147,7 @@ static bool compute_vectors(SegmentArray* segments,
for (int p = 0; p < n; ++p) {
segb.fNorms[p] = segb.fPts[p] - *prevPt;
segb.fNorms[p].normalize();
SkPointPriv::SetOrthog(&segb.fNorms[p], segb.fNorms[p], normSide);
segb.fNorms[p] = SkPointPriv::MakeOrthog(segb.fNorms[p], normSide);
prevPt = &segb.fPts[p];
}
if (Segment::kLine == segb.fType) {
@ -206,7 +206,7 @@ static void update_degenerate_test(DegenerateTestData* data, const SkPoint& pt)
if (SkPointPriv::DistanceToSqd(pt, data->fFirstPoint) > kCloseSqd) {
data->fLineNormal = pt - data->fFirstPoint;
data->fLineNormal.normalize();
SkPointPriv::SetOrthog(&data->fLineNormal, data->fLineNormal);
data->fLineNormal = SkPointPriv::MakeOrthog(data->fLineNormal);
data->fLineC = -data->fLineNormal.dot(data->fFirstPoint);
data->fStage = DegenerateTestData::kLine;
}

View File

@ -59,10 +59,14 @@ static bool duplicate_pt(const SkPoint& p0, const SkPoint& p1) {
return distSq < kCloseSqd;
}
static SkScalar abs_dist_from_line(const SkPoint& p0, const SkVector& v, const SkPoint& test) {
SkPoint testV = test - p0;
SkScalar dist = testV.fX * v.fY - testV.fY * v.fX;
return SkScalarAbs(dist);
static bool points_are_colinear_and_b_is_middle(const SkPoint& a, const SkPoint& b,
const SkPoint& c) {
// 'area' is twice the area of the triangle with corners a, b, and c.
SkScalar area = a.fX * (b.fY - c.fY) + b.fX * (c.fY - a.fY) + c.fX * (a.fY - b.fY);
if (SkScalarAbs(area) >= 2 * kCloseSqd) {
return false;
}
return (a - b).dot(b - c) >= 0;
}
int GrAAConvexTessellator::addPt(const SkPoint& pt,
@ -142,6 +146,27 @@ void GrAAConvexTessellator::rewind() {
#endif
}
void GrAAConvexTessellator::computeNormals() {
auto normalToVector = [this](SkVector v) {
SkVector n = SkPointPriv::MakeOrthog(v, fSide);
SkAssertResult(n.normalize());
SkASSERT(SkScalarNearlyEqual(1.0f, n.length()));
return n;
};
// Check the cross product of the final trio
fNorms.append(fPts.count());
fNorms[0] = fPts[1] - fPts[0];
fNorms.top() = fPts[0] - fPts.top();
SkScalar cross = SkPoint::CrossProduct(fNorms[0], fNorms.top());
fSide = (cross > 0.0f) ? SkPointPriv::kRight_Side : SkPointPriv::kLeft_Side;
fNorms[0] = normalToVector(fNorms[0]);
for (int cur = 1; cur < fNorms.count() - 1; ++cur) {
fNorms[cur] = normalToVector(fPts[cur + 1] - fPts[cur]);
}
fNorms.top() = normalToVector(fNorms.top());
}
void GrAAConvexTessellator::computeBisectors() {
fBisectors.setCount(fNorms.count());
@ -149,11 +174,8 @@ void GrAAConvexTessellator::computeBisectors() {
for (int cur = 0; cur < fBisectors.count(); prev = cur, ++cur) {
fBisectors[cur] = fNorms[cur] + fNorms[prev];
if (!fBisectors[cur].normalize()) {
SkASSERT(SkPointPriv::kLeft_Side == fSide || SkPointPriv::kRight_Side == fSide);
SkPointPriv::SetOrthog(&fBisectors[cur], fNorms[cur], (SkPointPriv::Side)-fSide);
SkVector other;
SkPointPriv::SetOrthog(&other, fNorms[prev], fSide);
fBisectors[cur] += other;
fBisectors[cur] = SkPointPriv::MakeOrthog(fNorms[cur], (SkPointPriv::Side)-fSide) +
SkPointPriv::MakeOrthog(fNorms[prev], fSide);
SkAssertResult(fBisectors[cur].normalize());
} else {
fBisectors[cur].negate(); // make the bisector face in
@ -357,8 +379,6 @@ bool GrAAConvexTessellator::extractFromPath(const SkMatrix& m, const SkPath& pat
// Presumptive inner ring: 6*numPts + 6
fIndices.setReserve(18*path.countPoints() + 6);
fNorms.setReserve(path.countPoints());
// TODO: is there a faster way to extract the points from the path? Perhaps
// get all the points via a new entry point, transform them all in bulk
// and then walk them to find duplicates?
@ -393,48 +413,24 @@ bool GrAAConvexTessellator::extractFromPath(const SkMatrix& m, const SkPath& pat
// check if last point is a duplicate of the first point. If so, remove it.
if (duplicate_pt(fPts[this->numPts()-1], fPts[0])) {
this->popLastPt();
fNorms.pop();
}
SkASSERT(fPts.count() == fNorms.count()+1);
if (this->numPts() >= 3) {
if (abs_dist_from_line(fPts.top(), fNorms.top(), fPts[0]) < kClose) {
// The last point is on the line from the second to last to the first point.
// Remove any lingering colinear points where the path wraps around
bool noRemovalsToDo = false;
while (!noRemovalsToDo && this->numPts() >= 3) {
if (points_are_colinear_and_b_is_middle(fPts[fPts.count() - 2], fPts.top(), fPts[0])) {
this->popLastPt();
fNorms.pop();
}
*fNorms.push() = fPts[0] - fPts.top();
SkDEBUGCODE(SkScalar len =) SkPoint::Normalize(&fNorms.top());
SkASSERT(len > 0.0f);
SkASSERT(fPts.count() == fNorms.count());
}
if (this->numPts() >= 3 && abs_dist_from_line(fPts[0], fNorms.top(), fPts[1]) < kClose) {
// The first point is on the line from the last to the second.
this->popFirstPtShuffle();
fNorms.removeShuffle(0);
fNorms[0] = fPts[1] - fPts[0];
SkDEBUGCODE(SkScalar len =) SkPoint::Normalize(&fNorms[0]);
SkASSERT(len > 0.0f);
SkASSERT(SkScalarNearlyEqual(1.0f, fNorms[0].length()));
}
if (this->numPts() >= 3) {
// Check the cross product of the final trio
SkScalar cross = SkPoint::CrossProduct(fNorms[0], fNorms.top());
if (cross > 0.0f) {
fSide = SkPointPriv::kRight_Side;
} else if (points_are_colinear_and_b_is_middle(fPts.top(), fPts[0], fPts[1])) {
this->popFirstPtShuffle();
} else {
fSide = SkPointPriv::kLeft_Side;
}
// Make all the normals face outwards rather than along the edge
for (int cur = 0; cur < fNorms.count(); ++cur) {
SkPointPriv::SetOrthog(&fNorms[cur], fNorms[cur], fSide);
SkASSERT(SkScalarNearlyEqual(1.0f, fNorms[cur].length()));
noRemovalsToDo = true;
}
}
// Compute the normals and bisectors.
SkASSERT(fNorms.empty());
if (this->numPts() >= 3) {
this->computeNormals();
this->computeBisectors();
} else if (this->numPts() == 2) {
// We've got two points, so we're degenerate.
@ -445,13 +441,11 @@ bool GrAAConvexTessellator::extractFromPath(const SkMatrix& m, const SkPath& pat
// For stroking, we still need to process the degenerate path, so fix it up
fSide = SkPointPriv::kLeft_Side;
// Make all the normals face outwards rather than along the edge
for (int cur = 0; cur < fNorms.count(); ++cur) {
SkPointPriv::SetOrthog(&fNorms[cur], fNorms[cur], fSide);
SkASSERT(SkScalarNearlyEqual(1.0f, fNorms[cur].length()));
}
fNorms.push_back(SkPoint::Make(-fNorms[0].fX, -fNorms[0].fY));
fNorms.append(2);
fNorms[0] = SkPointPriv::MakeOrthog(fPts[1] - fPts[0], fSide);
fNorms[0].normalize();
fNorms[1] = -fNorms[0];
SkASSERT(SkScalarNearlyEqual(1.0f, fNorms[0].length()));
// we won't actually use the bisectors, so just push zeroes
fBisectors.push_back(SkPoint::Make(0.0, 0.0));
fBisectors.push_back(SkPoint::Make(0.0, 0.0));
@ -839,7 +833,7 @@ void GrAAConvexTessellator::Ring::computeNormals(const GrAAConvexTessellator& te
fPts[cur].fNorm = tess.point(fPts[next].fIndex) - tess.point(fPts[cur].fIndex);
SkPoint::Normalize(&fPts[cur].fNorm);
SkPointPriv::SetOrthog(&fPts[cur].fNorm, fPts[cur].fNorm, tess.side());
fPts[cur].fNorm = SkPointPriv::MakeOrthog(fPts[cur].fNorm, tess.side());
}
}
@ -848,13 +842,9 @@ void GrAAConvexTessellator::Ring::computeBisectors(const GrAAConvexTessellator&
for (int cur = 0; cur < fPts.count(); prev = cur, ++cur) {
fPts[cur].fBisector = fPts[cur].fNorm + fPts[prev].fNorm;
if (!fPts[cur].fBisector.normalize()) {
SkASSERT(SkPointPriv::kLeft_Side == tess.side() ||
SkPointPriv::kRight_Side == tess.side());
SkPointPriv::SetOrthog(&fPts[cur].fBisector, fPts[cur].fNorm,
(SkPointPriv::Side)-tess.side());
SkVector other;
SkPointPriv::SetOrthog(&other, fPts[prev].fNorm, tess.side());
fPts[cur].fBisector += other;
fPts[cur].fBisector =
SkPointPriv::MakeOrthog(fPts[cur].fNorm, (SkPointPriv::Side)-tess.side()) +
SkPointPriv::MakeOrthog(fPts[prev].fNorm, tess.side());
SkAssertResult(fPts[cur].fBisector.normalize());
} else {
fPts[cur].fBisector.negate(); // make the bisector face in
@ -904,11 +894,10 @@ void GrAAConvexTessellator::lineTo(const SkPoint& p, CurveState curve) {
return;
}
SkASSERT(fPts.count() <= 1 || fPts.count() == fNorms.count()+1);
if (this->numPts() >= 2 && abs_dist_from_line(fPts.top(), fNorms.top(), p) < kClose) {
if (this->numPts() >= 2 &&
points_are_colinear_and_b_is_middle(fPts[fPts.count() - 2], fPts.top(), p)) {
// The old last point is on the line from the second to last to the new point
this->popLastPt();
fNorms.pop();
// double-check that the new last point is not a duplicate of the new point. In an ideal
// world this wouldn't be necessary (since it's only possible for non-convex paths), but
// floating point precision issues mean it can actually happen on paths that were
@ -919,12 +908,6 @@ void GrAAConvexTessellator::lineTo(const SkPoint& p, CurveState curve) {
}
SkScalar initialRingCoverage = (SkStrokeRec::kFill_Style == fStyle) ? 0.5f : 1.0f;
this->addPt(p, 0.0f, initialRingCoverage, false, curve);
if (this->numPts() > 1) {
*fNorms.push() = fPts.top() - fPts[fPts.count()-2];
SkDEBUGCODE(SkScalar len =) SkPoint::Normalize(&fNorms.top());
SkASSERT(len > 0.0f);
SkASSERT(SkScalarNearlyEqual(1.0f, fNorms.top().length()));
}
}
void GrAAConvexTessellator::lineTo(const SkMatrix& m, SkPoint p, CurveState curve) {

View File

@ -230,6 +230,7 @@ private:
// return false on failure/degenerate path
bool extractFromPath(const SkMatrix& m, const SkPath& path);
void computeBisectors();
void computeNormals();
void fanRing(const Ring& ring);

View File

@ -554,15 +554,13 @@ static void bloat_quad(const SkPoint qpts[3], const SkMatrix* toDevice,
SkASSERT(ab.length() > 0 && cb.length() > 0);
ab.normalize();
SkVector abN;
SkPointPriv::SetOrthog(&abN, ab, SkPointPriv::kLeft_Side);
SkVector abN = SkPointPriv::MakeOrthog(ab, SkPointPriv::kLeft_Side);
if (abN.dot(ac) > 0) {
abN.negate();
}
cb.normalize();
SkVector cbN;
SkPointPriv::SetOrthog(&cbN, cb, SkPointPriv::kLeft_Side);
SkVector cbN = SkPointPriv::MakeOrthog(cb, SkPointPriv::kLeft_Side);
if (cbN.dot(ac) < 0) {
cbN.negate();
}