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:
parent
ee2a8eede9
commit
612f70d5fa
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -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) {
|
||||
|
@ -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&);
|
||||
|
||||
|
@ -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
|
||||
return STROKER_RESULT(kDegenerate_ResultType, depth, quadPts,
|
||||
"SkScalarNearlyZero(denom=%g)", denom);
|
||||
}
|
||||
// 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);
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user