handle large conic strokes better

A stroked conic computes the outset quad's control point by
computing the intersection of the quad's endpoints. If the
the denominator used to compute the scale factor for the
control point is small, check to see if the numerator is also
small so that the division stays bounded.

Also clean up error returns and internal function calls to
simplify the code.

Additionally, remove comic max curvature (unimplemented) and call
extrema functions instead to handle cases where the conic is degenerate
or is a line.

R=reed@google.com, fmalita@chromium.org
BUG=skia:3843

Review URL: https://codereview.chromium.org/1144883003
This commit is contained in:
caryclark 2015-05-19 11:05:37 -07:00 committed by Commit bot
parent ee2a8eede9
commit 612f70d5fa
6 changed files with 104 additions and 54 deletions

View File

@ -241,12 +241,42 @@ private:
typedef skiagm::GM INHERITED;
};
class Strokes4GM : public skiagm::GM {
public:
Strokes4GM() {}
protected:
SkString onShortName() override {
return SkString("strokes_zoomed");
}
SkISize onISize() override {
return SkISize::Make(W, H*2);
}
void onDraw(SkCanvas* canvas) override {
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(0.055f);
canvas->scale(1000, 1000);
canvas->drawCircle(0, 2, 1.97f, paint);
}
private:
typedef skiagm::GM INHERITED;
};
//////////////////////////////////////////////////////////////////////////////
static skiagm::GM* F0(void*) { return new StrokesGM; }
static skiagm::GM* F1(void*) { return new Strokes2GM; }
static skiagm::GM* F2(void*) { return new Strokes3GM; }
static skiagm::GM* F3(void*) { return new Strokes4GM; }
static skiagm::GMRegistry R0(F0);
static skiagm::GMRegistry R1(F1);
static skiagm::GMRegistry R2(F2);
static skiagm::GMRegistry R3(F3);

View File

@ -304,10 +304,10 @@ protected:
void draw_stroke(SkCanvas* canvas, const SkPath& path, SkScalar width, SkScalar scale,
bool drawText) {
SkRect bounds = path.getBounds();
if (bounds.isEmpty()) {
if (path.isEmpty()) {
return;
}
SkRect bounds = path.getBounds();
this->setWHZ(SkScalarCeilToInt(bounds.right()), drawText
? SkScalarRoundToInt(scale * 3 / 2) : SkScalarRoundToInt(scale),
SkScalarRoundToInt(950.0f / scale));

View File

@ -1474,10 +1474,12 @@ void SkConic::computeFastBounds(SkRect* bounds) const {
bounds->set(fPts, 3);
}
#if 0 // unimplemented
bool SkConic::findMaxCurvature(SkScalar* t) const {
// TODO: Implement me
return false;
}
#endif
SkScalar SkConic::TransformW(const SkPoint pts[], SkScalar w,
const SkMatrix& matrix) {

View File

@ -276,7 +276,7 @@ struct SkConic {
*
* @return true if max curvature found inside 0..1 range, false otherwise
*/
bool findMaxCurvature(SkScalar* t) const;
// bool findMaxCurvature(SkScalar* t) const; // unimplemented
static SkScalar TransformW(const SkPoint[3], SkScalar w, const SkMatrix&);

View File

@ -231,16 +231,16 @@ private:
bool fFoundTangents; // do less work until tangents meet (cubic)
void addDegenerateLine(const SkQuadConstruct* );
ReductionType CheckConicLinear(const SkConic& , SkPoint* reduction);
ReductionType CheckCubicLinear(const SkPoint cubic[4], SkPoint reduction[3],
static ReductionType CheckConicLinear(const SkConic& , SkPoint* reduction);
static ReductionType CheckCubicLinear(const SkPoint cubic[4], SkPoint reduction[3],
const SkPoint** tanPtPtr);
ReductionType CheckQuadLinear(const SkPoint quad[3], SkPoint* reduction);
ResultType compareQuadConic(const SkConic& , SkQuadConstruct* );
static ReductionType CheckQuadLinear(const SkPoint quad[3], SkPoint* reduction);
ResultType compareQuadConic(const SkConic& , SkQuadConstruct* ) const;
ResultType compareQuadCubic(const SkPoint cubic[4], SkQuadConstruct* );
ResultType compareQuadQuad(const SkPoint quad[3], SkQuadConstruct* );
bool conicPerpRay(const SkConic& , SkScalar t, SkPoint* tPt, SkPoint* onPt,
void conicPerpRay(const SkConic& , SkScalar t, SkPoint* tPt, SkPoint* onPt,
SkPoint* tangent) const;
bool conicQuadEnds(const SkConic& , SkQuadConstruct* );
void conicQuadEnds(const SkConic& , SkQuadConstruct* ) const;
bool conicStroke(const SkConic& , SkQuadConstruct* );
bool cubicMidOnLine(const SkPoint cubic[4], const SkQuadConstruct* ) const;
bool cubicPerpRay(const SkPoint cubic[4], SkScalar t, SkPoint* tPt, SkPoint* onPt,
@ -661,10 +661,20 @@ SkPathStroker::ReductionType SkPathStroker::CheckConicLinear(const SkConic& coni
if (!conic_in_line(conic)) {
return kQuad_ReductionType;
}
#if 0 // once findMaxCurvature is implemented, this will be a better solution
SkScalar t;
if (!conic.findMaxCurvature(&t) || 0 == t) {
return kLine_ReductionType;
}
#else // but for now, use extrema instead
SkScalar xT = 0, yT = 0;
(void) conic.findXExtrema(&xT);
(void) conic.findYExtrema(&yT);
SkScalar t = SkTMax(xT, yT);
if (0 == t) {
return kLine_ReductionType;
}
#endif
conic.evalAt(t, reduction, NULL);
return kDegenerate_ReductionType;
}
@ -923,36 +933,30 @@ void SkPathStroker::setRayPts(const SkPoint& tPt, SkVector* dxy, SkPoint* onPt,
// Given a conic and t, return the point on curve, its perpendicular, and the perpendicular tangent.
// Returns false if the perpendicular could not be computed (because the derivative collapsed to 0)
bool SkPathStroker::conicPerpRay(const SkConic& conic, SkScalar t, SkPoint* tPt, SkPoint* onPt,
void SkPathStroker::conicPerpRay(const SkConic& conic, SkScalar t, SkPoint* tPt, SkPoint* onPt,
SkPoint* tangent) const {
SkVector dxy;
conic.evalAt(t, tPt, &dxy);
if (dxy.fX == 0 && dxy.fY == 0) {
dxy = conic.fPts[2] - conic.fPts[0];
}
setRayPts(*tPt, &dxy, onPt, tangent);
return true;
this->setRayPts(*tPt, &dxy, onPt, tangent);
}
// Given a conic and a t range, find the start and end if they haven't been found already.
bool SkPathStroker::conicQuadEnds(const SkConic& conic, SkQuadConstruct* quadPts) {
void SkPathStroker::conicQuadEnds(const SkConic& conic, SkQuadConstruct* quadPts) const {
if (!quadPts->fStartSet) {
SkPoint conicStartPt;
if (!this->conicPerpRay(conic, quadPts->fStartT, &conicStartPt, &quadPts->fQuad[0],
&quadPts->fTangentStart)) {
return false;
}
this->conicPerpRay(conic, quadPts->fStartT, &conicStartPt, &quadPts->fQuad[0],
&quadPts->fTangentStart);
quadPts->fStartSet = true;
}
if (!quadPts->fEndSet) {
SkPoint conicEndPt;
if (!this->conicPerpRay(conic, quadPts->fEndT, &conicEndPt, &quadPts->fQuad[2],
&quadPts->fTangentEnd)) {
return false;
}
this->conicPerpRay(conic, quadPts->fEndT, &conicEndPt, &quadPts->fQuad[2],
&quadPts->fTangentEnd);
quadPts->fEndSet = true;
}
return true;
}
@ -1025,35 +1029,49 @@ SkPathStroker::ResultType SkPathStroker::intersectRay(SkQuadConstruct* quadPts,
const SkPoint& end = quadPts->fQuad[2];
SkVector aLen = quadPts->fTangentStart - start;
SkVector bLen = quadPts->fTangentEnd - end;
/* Slopes match when denom goes to zero:
axLen / ayLen == bxLen / byLen
(ayLen * byLen) * axLen / ayLen == (ayLen * byLen) * bxLen / byLen
byLen * axLen == ayLen * bxLen
byLen * axLen - ayLen * bxLen ( == denom )
*/
SkScalar denom = aLen.cross(bLen);
if (denom == 0 || !SkScalarIsFinite(denom)) {
return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts, "denom == 0");
}
SkVector ab0 = start - end;
SkScalar numerA = bLen.cross(ab0);
SkScalar numerB = aLen.cross(ab0);
if (!SkScalarNearlyZero(denom)) {
if ((numerA >= 0) == (numerB >= 0)) { // if the control point is outside the quad ends
// if the perpendicular distances from the quad points to the opposite tangent line
// are small, a straight line is good enough
SkScalar dist1 = pt_to_line(start, end, quadPts->fTangentEnd);
SkScalar dist2 = pt_to_line(end, start, quadPts->fTangentStart);
if ((numerA >= 0) != (numerB >= 0)) {
if (kCtrlPt_RayType == intersectRayType) {
numerA /= denom;
SkPoint* ctrlPt = &quadPts->fQuad[1];
ctrlPt->fX = start.fX * (1 - numerA) + quadPts->fTangentStart.fX * numerA;
ctrlPt->fY = start.fY * (1 - numerA) + quadPts->fTangentStart.fY * numerA;
}
return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
"(numerA=%g >= 0) != (numerB=%g >= 0)", numerA, numerB);
}
if (SkTMax(dist1, dist2) <= fInvResScaleSquared) {
return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts,
"SkTMax(dist1=%g, dist2=%g) <= fInvResScaleSquared", dist1, dist2);
}
return STROKER_RESULT(kSplit_ResultType, depth, quadPts,
"(numerA=%g >= 0) == (numerB=%g >= 0)", numerA, numerB);
} else { // if the lines are parallel, straight line is good enough
}
// check to see if the denomerator is teeny relative to the numerator
bool validDivide = SkScalarAbs(numerA) * SK_ScalarNearlyZero < SkScalarAbs(denom);
SkASSERT(!SkScalarNearlyZero(denom / numerA) == validDivide);
if (validDivide) {
if (kCtrlPt_RayType == intersectRayType) {
numerA /= denom;
SkPoint* ctrlPt = &quadPts->fQuad[1];
// the intersection of the tangents need not be on the tangent segment
// so 0 <= numerA <= 1 is not necessarily true
ctrlPt->fX = start.fX * (1 - numerA) + quadPts->fTangentStart.fX * numerA;
ctrlPt->fY = start.fY * (1 - numerA) + quadPts->fTangentStart.fY * numerA;
}
return STROKER_RESULT(kQuad_ResultType, depth, quadPts,
"(numerA=%g >= 0) != (numerB=%g >= 0)", numerA, numerB);
}
// if the lines are parallel, straight line is good enough
return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts,
"SkScalarNearlyZero(denom=%g)", denom);
}
}
// Given a cubic and a t-range, determine if the stroke can be described by a quadratic.
@ -1062,7 +1080,7 @@ SkPathStroker::ResultType SkPathStroker::tangentsMeet(const SkPoint cubic[4],
if (!this->cubicQuadEnds(cubic, quadPts)) {
return kNormalError_ResultType;
}
return intersectRay(quadPts, kResultType_RayType STROKER_DEBUG_PARAMS(fRecursionDepth));
return this->intersectRay(quadPts, kResultType_RayType STROKER_DEBUG_PARAMS(fRecursionDepth));
}
// Intersect the line with the quad and return the t values on the quad where the line crosses.
@ -1178,7 +1196,7 @@ SkPathStroker::ResultType SkPathStroker::compareQuadCubic(const SkPoint cubic[4]
if (!this->cubicQuadEnds(cubic, quadPts)) {
return kNormalError_ResultType;
}
ResultType resultType = intersectRay(quadPts, kCtrlPt_RayType
ResultType resultType = this->intersectRay(quadPts, kCtrlPt_RayType
STROKER_DEBUG_PARAMS(fRecursionDepth) );
if (resultType != kQuad_ResultType) {
return resultType;
@ -1188,26 +1206,24 @@ SkPathStroker::ResultType SkPathStroker::compareQuadCubic(const SkPoint cubic[4]
if (!this->cubicPerpRay(cubic, quadPts->fMidT, &ray[1], &ray[0], NULL)) {
return kNormalError_ResultType;
}
return strokeCloseEnough(quadPts->fQuad, ray, quadPts STROKER_DEBUG_PARAMS(fRecursionDepth));
return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts
STROKER_DEBUG_PARAMS(fRecursionDepth));
}
SkPathStroker::ResultType SkPathStroker::compareQuadConic(const SkConic& conic,
SkQuadConstruct* quadPts) {
SkQuadConstruct* quadPts) const {
// get the quadratic approximation of the stroke
if (!this->conicQuadEnds(conic, quadPts)) {
return kNormalError_ResultType;
}
ResultType resultType = intersectRay(quadPts, kCtrlPt_RayType
this->conicQuadEnds(conic, quadPts);
ResultType resultType = this->intersectRay(quadPts, kCtrlPt_RayType
STROKER_DEBUG_PARAMS(fRecursionDepth) );
if (resultType != kQuad_ResultType) {
return resultType;
}
// project a ray from the curve to the stroke
SkPoint ray[2]; // points near midpoint on quad, midpoint on conic
if (!this->conicPerpRay(conic, quadPts->fMidT, &ray[1], &ray[0], NULL)) {
return kNormalError_ResultType;
}
return strokeCloseEnough(quadPts->fQuad, ray, quadPts STROKER_DEBUG_PARAMS(fRecursionDepth));
this->conicPerpRay(conic, quadPts->fMidT, &ray[1], &ray[0], NULL);
return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts
STROKER_DEBUG_PARAMS(fRecursionDepth));
}
SkPathStroker::ResultType SkPathStroker::compareQuadQuad(const SkPoint quad[3],
@ -1225,7 +1241,7 @@ SkPathStroker::ResultType SkPathStroker::compareQuadQuad(const SkPoint quad[3],
&quadPts->fTangentEnd);
quadPts->fEndSet = true;
}
ResultType resultType = intersectRay(quadPts, kCtrlPt_RayType
ResultType resultType = this->intersectRay(quadPts, kCtrlPt_RayType
STROKER_DEBUG_PARAMS(fRecursionDepth));
if (resultType != kQuad_ResultType) {
return resultType;
@ -1233,7 +1249,8 @@ SkPathStroker::ResultType SkPathStroker::compareQuadQuad(const SkPoint quad[3],
// project a ray from the curve to the stroke
SkPoint ray[2];
this->quadPerpRay(quad, quadPts->fMidT, &ray[1], &ray[0], NULL);
return strokeCloseEnough(quadPts->fQuad, ray, quadPts STROKER_DEBUG_PARAMS(fRecursionDepth));
return this->strokeCloseEnough(quadPts->fQuad, ray, quadPts
STROKER_DEBUG_PARAMS(fRecursionDepth));
}
void SkPathStroker::addDegenerateLine(const SkQuadConstruct* quadPts) {
@ -1577,8 +1594,8 @@ void SkStroke::strokePath(const SkPath& src, SkPath* dst) const {
}
}
SkAutoConicToQuads converter;
#ifdef SK_LEGACY_STROKE_CURVES
SkAutoConicToQuads converter;
const SkScalar conicTol = SK_Scalar1 / 4 / fResScale;
#endif
SkPathStroker stroker(src, radius, fMiterLimit, this->getCap(), this->getJoin(), fResScale);

View File

@ -7,9 +7,10 @@
<div style="height:0">
<div id="sect1">
{{{6, -3}, {0, 1}}} id=3
{{{1.6714313f, -1.08141601f}, {2.24979973f, -1.14467525f}, {2.27122664f, -0.514151096f}, {0, 1}}} id=5
{{{0.001119050197303295135, 0.9992539882659912109}, {4.001119136810302734, 6.999254226684570312}}},
{{{fX=1.97000003 fY=2.00000000 }, {fX=1.97000003 fY=3.97000003 }, {fX=0.000000000 fY=3.97000003 }} fW: 0.707106769
{{{fX=1.99750006 fY=2.00000000 } {fX=1.99750006 fY=2.02749991 }
{{{fX=1.96415257 fY=2.36347127 } {fX=1.95914865 fY=2.39051223 }
</div>
</div>