fix circular dashing
Path measure cannot use the same code approach for quadratics and cubics. Subdividing cubics repeatedly does not result in subdivided t values, e.g. a quarter circle cubic divided in half twice does not have a t value equivalent to 1/4. Instead, always compute the cubic segment from a pair of t values. When finding the length of the cubic through recursive measures, it is enough to carry the point at a given t to the next subdivision. (Chrome suppression has landed already.) R=reed@google.com GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1602153002 Review URL: https://codereview.chromium.org/1602153002
This commit is contained in:
parent
a913275bda
commit
b6474dd1a5
78
gm/dashcircle.cpp
Normal file
78
gm/dashcircle.cpp
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Google Inc.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by a BSD-style license that can be
|
||||||
|
* found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "gm.h"
|
||||||
|
#include "SkPath.h"
|
||||||
|
#include "SkDashPathEffect.h"
|
||||||
|
|
||||||
|
int dash1[] = { 1, 1 };
|
||||||
|
int dash2[] = { 1, 3 };
|
||||||
|
int dash3[] = { 1, 1, 3, 3 };
|
||||||
|
int dash4[] = { 1, 3, 2, 4 };
|
||||||
|
|
||||||
|
struct DashExample {
|
||||||
|
int* pattern;
|
||||||
|
int length;
|
||||||
|
} dashExamples[] = {
|
||||||
|
{ dash1, SK_ARRAY_COUNT(dash1) },
|
||||||
|
{ dash2, SK_ARRAY_COUNT(dash2) },
|
||||||
|
{ dash3, SK_ARRAY_COUNT(dash3) },
|
||||||
|
{ dash4, SK_ARRAY_COUNT(dash4) }
|
||||||
|
};
|
||||||
|
|
||||||
|
DEF_SIMPLE_GM(dashcircle, canvas, 900, 1200) {
|
||||||
|
SkPaint refPaint;
|
||||||
|
refPaint.setAntiAlias(true);
|
||||||
|
refPaint.setColor(0xFFbf3f7f);
|
||||||
|
refPaint.setStyle(SkPaint::kStroke_Style);
|
||||||
|
refPaint.setStrokeWidth(1);
|
||||||
|
const SkScalar radius = 125;
|
||||||
|
SkRect oval = SkRect::MakeLTRB(-radius - 20, -radius - 20, radius + 20, radius + 20);
|
||||||
|
SkPath circle;
|
||||||
|
circle.addCircle(0, 0, radius);
|
||||||
|
SkScalar circumference = radius * SK_ScalarPI * 2;
|
||||||
|
int wedges[] = { 6, 12, 36 };
|
||||||
|
canvas->translate(radius + 20, radius + 20);
|
||||||
|
for (int wedge : wedges) {
|
||||||
|
SkScalar arcLength = 360.f / wedge;
|
||||||
|
canvas->save();
|
||||||
|
for (const DashExample& dashExample : dashExamples) {
|
||||||
|
SkPath refPath;
|
||||||
|
int dashUnits = 0;
|
||||||
|
for (int index = 0; index < dashExample.length; ++index) {
|
||||||
|
dashUnits += dashExample.pattern[index];
|
||||||
|
}
|
||||||
|
SkScalar unitLength = arcLength / dashUnits;
|
||||||
|
SkScalar angle = 0;
|
||||||
|
for (int index = 0; index < wedge; ++index) {
|
||||||
|
for (int i2 = 0; i2 < dashExample.length; i2 += 2) {
|
||||||
|
SkScalar span = dashExample.pattern[i2] * unitLength;
|
||||||
|
refPath.moveTo(0, 0);
|
||||||
|
refPath.arcTo(oval, angle, span, false);
|
||||||
|
refPath.close();
|
||||||
|
angle += span + (dashExample.pattern[i2 + 1]) * unitLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
canvas->drawPath(refPath, refPaint);
|
||||||
|
SkPaint p;
|
||||||
|
p.setAntiAlias(true);
|
||||||
|
p.setStyle(SkPaint::kStroke_Style);
|
||||||
|
p.setStrokeWidth(10);
|
||||||
|
SkScalar intervals[4];
|
||||||
|
int intervalCount = dashExample.length;
|
||||||
|
SkScalar dashLength = circumference / wedge / dashUnits;
|
||||||
|
for (int index = 0; index < dashExample.length; ++index) {
|
||||||
|
intervals[index] = dashExample.pattern[index] * dashLength;
|
||||||
|
}
|
||||||
|
p.setPathEffect(SkDashPathEffect::Create(intervals, intervalCount, 0))->unref();
|
||||||
|
canvas->drawPath(circle, p);
|
||||||
|
canvas->translate(0, radius * 2 + 50);
|
||||||
|
}
|
||||||
|
canvas->restore();
|
||||||
|
canvas->translate(radius * 2 + 50, 0);
|
||||||
|
}
|
||||||
|
}
|
@ -107,7 +107,13 @@ private:
|
|||||||
void buildSegments();
|
void buildSegments();
|
||||||
SkScalar compute_quad_segs(const SkPoint pts[3], SkScalar distance,
|
SkScalar compute_quad_segs(const SkPoint pts[3], SkScalar distance,
|
||||||
int mint, int maxt, int ptIndex);
|
int mint, int maxt, int ptIndex);
|
||||||
|
#ifdef SK_SUPPORT_LEGACY_CONIC_MEASURE
|
||||||
SkScalar compute_conic_segs(const SkConic&, SkScalar distance, int mint, int maxt, int ptIndex);
|
SkScalar compute_conic_segs(const SkConic&, SkScalar distance, int mint, int maxt, int ptIndex);
|
||||||
|
#else
|
||||||
|
SkScalar compute_conic_segs(const SkConic&, SkScalar distance,
|
||||||
|
int mint, const SkPoint& minPt,
|
||||||
|
int maxt, const SkPoint& maxPt, int ptIndex);
|
||||||
|
#endif
|
||||||
SkScalar compute_cubic_segs(const SkPoint pts[3], SkScalar distance,
|
SkScalar compute_cubic_segs(const SkPoint pts[3], SkScalar distance,
|
||||||
int mint, int maxt, int ptIndex);
|
int mint, int maxt, int ptIndex);
|
||||||
const Segment* distanceToSegment(SkScalar distance, SkScalar* t);
|
const Segment* distanceToSegment(SkScalar distance, SkScalar* t);
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include "SampleCode.h"
|
#include "SampleCode.h"
|
||||||
#include "SkView.h"
|
#include "SkView.h"
|
||||||
#include "SkCanvas.h"
|
#include "SkCanvas.h"
|
||||||
|
#include "SkGeometry.h"
|
||||||
#include "SkPathMeasure.h"
|
#include "SkPathMeasure.h"
|
||||||
#include "SkRandom.h"
|
#include "SkRandom.h"
|
||||||
#include "SkRRect.h"
|
#include "SkRRect.h"
|
||||||
@ -122,6 +123,7 @@ class QuadStrokerView : public SampleView {
|
|||||||
bool fAnimate;
|
bool fAnimate;
|
||||||
bool fDrawRibs;
|
bool fDrawRibs;
|
||||||
bool fDrawTangents;
|
bool fDrawTangents;
|
||||||
|
bool fDrawTDivs;
|
||||||
#ifdef SK_DEBUG
|
#ifdef SK_DEBUG
|
||||||
#define kStrokerErrorMin 0.001f
|
#define kStrokerErrorMin 0.001f
|
||||||
#define kStrokerErrorMax 5
|
#define kStrokerErrorMax 5
|
||||||
@ -288,16 +290,84 @@ protected:
|
|||||||
SkScalar total = meas.getLength();
|
SkScalar total = meas.getLength();
|
||||||
|
|
||||||
SkScalar delta = 8;
|
SkScalar delta = 8;
|
||||||
SkPaint paint;
|
SkPaint paint, labelP;
|
||||||
paint.setColor(color);
|
paint.setColor(color);
|
||||||
|
labelP.setColor(color & 0xff5f9f5f);
|
||||||
SkPoint pos, tan;
|
SkPoint pos, tan;
|
||||||
|
int index = 0;
|
||||||
for (SkScalar dist = 0; dist <= total; dist += delta) {
|
for (SkScalar dist = 0; dist <= total; dist += delta) {
|
||||||
if (meas.getPosTan(dist, &pos, &tan)) {
|
if (meas.getPosTan(dist, &pos, &tan)) {
|
||||||
tan.scale(radius);
|
tan.scale(radius);
|
||||||
tan.rotateCCW();
|
tan.rotateCCW();
|
||||||
canvas->drawLine(pos.x() + tan.x(), pos.y() + tan.y(),
|
canvas->drawLine(pos.x() + tan.x(), pos.y() + tan.y(),
|
||||||
pos.x() - tan.x(), pos.y() - tan.y(), paint);
|
pos.x() - tan.x(), pos.y() - tan.y(), paint);
|
||||||
|
if (0 == index % 10) {
|
||||||
|
SkString label;
|
||||||
|
label.appendS32(index);
|
||||||
|
SkRect dot = SkRect::MakeXYWH(pos.x() - 2, pos.y() - 2, 4, 4);
|
||||||
|
canvas->drawRect(dot, labelP);
|
||||||
|
canvas->drawText(label.c_str(), label.size(),
|
||||||
|
pos.x() - tan.x() * 1.25f, pos.y() - tan.y() * 1.25f, labelP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw_t_divs(SkCanvas* canvas, const SkPath& path, SkScalar width, SkColor color) {
|
||||||
|
const SkScalar radius = width / 2;
|
||||||
|
SkPaint paint;
|
||||||
|
paint.setColor(color);
|
||||||
|
SkPathMeasure meas(path, false);
|
||||||
|
SkScalar total = meas.getLength();
|
||||||
|
SkScalar delta = 8;
|
||||||
|
int ribs = 0;
|
||||||
|
for (SkScalar dist = 0; dist <= total; dist += delta) {
|
||||||
|
++ribs;
|
||||||
|
}
|
||||||
|
SkPath::RawIter iter(path);
|
||||||
|
SkPoint pts[4];
|
||||||
|
if (SkPath::kMove_Verb != iter.next(pts)) {
|
||||||
|
SkASSERT(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SkPath::Verb verb = iter.next(pts);
|
||||||
|
SkASSERT(SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb);
|
||||||
|
SkPoint pos, tan;
|
||||||
|
for (int index = 0; index < ribs; ++index) {
|
||||||
|
SkScalar t = (SkScalar) index / ribs;
|
||||||
|
switch (verb) {
|
||||||
|
case SkPath::kLine_Verb:
|
||||||
|
tan = pts[1] - pts[0];
|
||||||
|
pos = pts[0];
|
||||||
|
pos.fX += tan.fX * t;
|
||||||
|
pos.fY += tan.fY * t;
|
||||||
|
break;
|
||||||
|
case SkPath::kQuad_Verb:
|
||||||
|
pos = SkEvalQuadAt(pts, t);
|
||||||
|
tan = SkEvalQuadTangentAt(pts, t);
|
||||||
|
break;
|
||||||
|
case SkPath::kConic_Verb: {
|
||||||
|
SkConic conic(pts, iter.conicWeight());
|
||||||
|
pos = conic.evalAt(t);
|
||||||
|
tan = conic.evalTangentAt(t);
|
||||||
|
} break;
|
||||||
|
case SkPath::kCubic_Verb:
|
||||||
|
SkEvalCubicAt(pts, t, &pos, &tan, nullptr);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
SkASSERT(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tan.setLength(radius);
|
||||||
|
tan.rotateCCW();
|
||||||
|
canvas->drawLine(pos.x() + tan.x(), pos.y() + tan.y(),
|
||||||
|
pos.x() - tan.x(), pos.y() - tan.y(), paint);
|
||||||
|
if (0 == index % 10) {
|
||||||
|
SkString label;
|
||||||
|
label.appendS32(index);
|
||||||
|
canvas->drawText(label.c_str(), label.size(),
|
||||||
|
pos.x() + tan.x() * 1.25f, pos.y() + tan.y() * 1.25f, paint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -343,6 +413,10 @@ protected:
|
|||||||
draw_ribs(canvas, scaled, width, 0xFF00FF00);
|
draw_ribs(canvas, scaled, width, 0xFF00FF00);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fDrawTDivs) {
|
||||||
|
draw_t_divs(canvas, scaled, width, 0xFF3F3F00);
|
||||||
|
}
|
||||||
|
|
||||||
SkPath fill;
|
SkPath fill;
|
||||||
|
|
||||||
SkPaint p;
|
SkPaint p;
|
||||||
@ -428,17 +502,24 @@ protected:
|
|||||||
void setForGeometry() {
|
void setForGeometry() {
|
||||||
fDrawRibs = true;
|
fDrawRibs = true;
|
||||||
fDrawTangents = true;
|
fDrawTangents = true;
|
||||||
|
fDrawTDivs = false;
|
||||||
fWidthScale = 1;
|
fWidthScale = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setForText() {
|
void setForText() {
|
||||||
fDrawRibs = fDrawTangents = false;
|
fDrawRibs = fDrawTangents = fDrawTDivs = false;
|
||||||
fWidthScale = 0.002f;
|
fWidthScale = 0.002f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setForSingles() {
|
||||||
|
setForGeometry();
|
||||||
|
fDrawTDivs = true;
|
||||||
|
}
|
||||||
|
|
||||||
void setAsNeeded() {
|
void setAsNeeded() {
|
||||||
if (fConicButton.fEnabled || fCubicButton.fEnabled || fQuadButton.fEnabled
|
if (fConicButton.fEnabled || fCubicButton.fEnabled || fQuadButton.fEnabled) {
|
||||||
|| fRRectButton.fEnabled || fCircleButton.fEnabled) {
|
setForSingles();
|
||||||
|
} else if (fRRectButton.fEnabled || fCircleButton.fEnabled) {
|
||||||
setForGeometry();
|
setForGeometry();
|
||||||
} else {
|
} else {
|
||||||
setForText();
|
setForText();
|
||||||
@ -452,14 +533,15 @@ protected:
|
|||||||
if (fCubicButton.fEnabled) {
|
if (fCubicButton.fEnabled) {
|
||||||
path.moveTo(fPts[0]);
|
path.moveTo(fPts[0]);
|
||||||
path.cubicTo(fPts[1], fPts[2], fPts[3]);
|
path.cubicTo(fPts[1], fPts[2], fPts[3]);
|
||||||
setForGeometry();
|
setForSingles();
|
||||||
draw_stroke(canvas, path, width, 950, false);
|
draw_stroke(canvas, path, width, 950, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fConicButton.fEnabled) {
|
if (fConicButton.fEnabled) {
|
||||||
|
path.reset();
|
||||||
path.moveTo(fPts[4]);
|
path.moveTo(fPts[4]);
|
||||||
path.conicTo(fPts[5], fPts[6], fWeight);
|
path.conicTo(fPts[5], fPts[6], fWeight);
|
||||||
setForGeometry();
|
setForSingles();
|
||||||
draw_stroke(canvas, path, width, 950, false);
|
draw_stroke(canvas, path, width, 950, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -467,7 +549,7 @@ protected:
|
|||||||
path.reset();
|
path.reset();
|
||||||
path.moveTo(fPts[7]);
|
path.moveTo(fPts[7]);
|
||||||
path.quadTo(fPts[8], fPts[9]);
|
path.quadTo(fPts[8], fPts[9]);
|
||||||
setForGeometry();
|
setForSingles();
|
||||||
draw_stroke(canvas, path, width, 950, false);
|
draw_stroke(canvas, path, width, 950, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,18 +9,6 @@
|
|||||||
#include "SkMatrix.h"
|
#include "SkMatrix.h"
|
||||||
#include "SkNx.h"
|
#include "SkNx.h"
|
||||||
|
|
||||||
#if 0
|
|
||||||
static Sk2s from_point(const SkPoint& point) {
|
|
||||||
return Sk2s::Load(&point.fX);
|
|
||||||
}
|
|
||||||
|
|
||||||
static SkPoint to_point(const Sk2s& x) {
|
|
||||||
SkPoint point;
|
|
||||||
x.store(&point.fX);
|
|
||||||
return point;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static SkVector to_vector(const Sk2s& x) {
|
static SkVector to_vector(const Sk2s& x) {
|
||||||
SkVector vector;
|
SkVector vector;
|
||||||
x.store(&vector.fX);
|
x.store(&vector.fX);
|
||||||
@ -220,7 +208,7 @@ void SkChopQuadAt(const SkPoint src[3], SkPoint dst[5], SkScalar t) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SkChopQuadAtHalf(const SkPoint src[3], SkPoint dst[5]) {
|
void SkChopQuadAtHalf(const SkPoint src[3], SkPoint dst[5]) {
|
||||||
SkChopQuadAt(src, dst, 0.5f); return;
|
SkChopQuadAt(src, dst, 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Quad'(t) = At + B, where
|
/** Quad'(t) = At + B, where
|
||||||
@ -1246,8 +1234,34 @@ void SkConic::chopAt(SkScalar t, SkConic dst[2]) const {
|
|||||||
dst[1].fW = tmp2[2].fZ / root;
|
dst[1].fW = tmp2[2].fZ / root;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Sk2s times_2(const Sk2s& value) {
|
void SkConic::chopAt(SkScalar t1, SkScalar t2, SkConic* dst) const {
|
||||||
return value + value;
|
if (0 == t1 || 1 == t2) {
|
||||||
|
if (0 == t1 && 1 == t2) {
|
||||||
|
*dst = *this;
|
||||||
|
} else {
|
||||||
|
SkConic pair[2];
|
||||||
|
this->chopAt(t1 ? t1 : t2, pair);
|
||||||
|
*dst = pair[SkToBool(t1)];
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SkConicCoeff coeff(*this);
|
||||||
|
Sk2s tt1(t1);
|
||||||
|
Sk2s aXY = coeff.fNumer.eval(tt1);
|
||||||
|
Sk2s aZZ = coeff.fDenom.eval(tt1);
|
||||||
|
Sk2s midTT((t1 + t2) / 2);
|
||||||
|
Sk2s dXY = coeff.fNumer.eval(midTT);
|
||||||
|
Sk2s dZZ = coeff.fDenom.eval(midTT);
|
||||||
|
Sk2s tt2(t2);
|
||||||
|
Sk2s cXY = coeff.fNumer.eval(tt2);
|
||||||
|
Sk2s cZZ = coeff.fDenom.eval(tt2);
|
||||||
|
Sk2s bXY = times_2(dXY) - (aXY + cXY) * Sk2s(0.5f);
|
||||||
|
Sk2s bZZ = times_2(dZZ) - (aZZ + cZZ) * Sk2s(0.5f);
|
||||||
|
dst->fPts[0] = to_point(aXY / aZZ);
|
||||||
|
dst->fPts[1] = to_point(bXY / bZZ);
|
||||||
|
dst->fPts[2] = to_point(cXY / cZZ);
|
||||||
|
Sk2s ww = bZZ / (aZZ * cZZ).sqrt();
|
||||||
|
dst->fW = ww.kth<0>();
|
||||||
}
|
}
|
||||||
|
|
||||||
SkPoint SkConic::evalAt(SkScalar t) const {
|
SkPoint SkConic::evalAt(SkScalar t) const {
|
||||||
|
@ -23,7 +23,11 @@ static inline SkPoint to_point(const Sk2s& x) {
|
|||||||
|
|
||||||
static inline Sk2s sk2s_cubic_eval(const Sk2s& A, const Sk2s& B, const Sk2s& C, const Sk2s& D,
|
static inline Sk2s sk2s_cubic_eval(const Sk2s& A, const Sk2s& B, const Sk2s& C, const Sk2s& D,
|
||||||
const Sk2s& t) {
|
const Sk2s& t) {
|
||||||
return ((A * t + B) * t + C) * t + D;
|
return ((A * t + B) * t + C) * t + D;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Sk2s times_2(const Sk2s& value) {
|
||||||
|
return value + value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Given a quadratic equation Ax^2 + Bx + C = 0, return 0, 1, 2 roots for the
|
/** Given a quadratic equation Ax^2 + Bx + C = 0, return 0, 1, 2 roots for the
|
||||||
@ -42,10 +46,10 @@ SkPoint SkEvalQuadTangentAt(const SkPoint src[3], SkScalar t);
|
|||||||
void SkEvalQuadAt(const SkPoint src[3], SkScalar t, SkPoint* pt, SkVector* tangent = nullptr);
|
void SkEvalQuadAt(const SkPoint src[3], SkScalar t, SkPoint* pt, SkVector* tangent = nullptr);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* output is : eval(t) == coeff[0] * t^2 + coeff[1] * t + coeff[2]
|
* output is : eval(t) == coeff[0] * t^2 + coeff[1] * t + coeff[2]
|
||||||
*/
|
*/
|
||||||
void SkQuadToCoeff(const SkPoint pts[3], SkPoint coeff[3]);
|
void SkQuadToCoeff(const SkPoint pts[3], SkPoint coeff[3]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* output is : eval(t) == coeff[0] * t^3 + coeff[1] * t^2 + coeff[2] * t + coeff[3]
|
* output is : eval(t) == coeff[0] * t^3 + coeff[1] * t^2 + coeff[2] * t + coeff[3]
|
||||||
*/
|
*/
|
||||||
@ -241,6 +245,7 @@ struct SkConic {
|
|||||||
*/
|
*/
|
||||||
void evalAt(SkScalar t, SkPoint* pos, SkVector* tangent = nullptr) const;
|
void evalAt(SkScalar t, SkPoint* pos, SkVector* tangent = nullptr) const;
|
||||||
void chopAt(SkScalar t, SkConic dst[2]) const;
|
void chopAt(SkScalar t, SkConic dst[2]) const;
|
||||||
|
void chopAt(SkScalar t1, SkScalar t2, SkConic* dst) const;
|
||||||
void chop(SkConic dst[2]) const;
|
void chop(SkConic dst[2]) const;
|
||||||
|
|
||||||
SkPoint evalAt(SkScalar t) const;
|
SkPoint evalAt(SkScalar t) const;
|
||||||
@ -287,6 +292,102 @@ struct SkConic {
|
|||||||
const SkMatrix*, SkConic conics[kMaxConicsForArc]);
|
const SkMatrix*, SkConic conics[kMaxConicsForArc]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// inline helpers are contained in a namespace to avoid external leakage to fragile SkNx members
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* use for : eval(t) == A * t^2 + B * t + C
|
||||||
|
*/
|
||||||
|
struct SkQuadCoeff {
|
||||||
|
SkQuadCoeff() {}
|
||||||
|
|
||||||
|
SkQuadCoeff(const Sk2s& A, const Sk2s& B, const Sk2s& C)
|
||||||
|
: fA(A)
|
||||||
|
, fB(B)
|
||||||
|
, fC(C)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SkQuadCoeff(const SkPoint src[3]) {
|
||||||
|
fC = from_point(src[0]);
|
||||||
|
Sk2s P1 = from_point(src[1]);
|
||||||
|
Sk2s P2 = from_point(src[2]);
|
||||||
|
fB = times_2(P1 - fC);
|
||||||
|
fA = P2 - times_2(P1) + fC;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sk2s eval(SkScalar t) {
|
||||||
|
Sk2s tt(t);
|
||||||
|
return eval(tt);
|
||||||
|
}
|
||||||
|
|
||||||
|
Sk2s eval(const Sk2s& tt) {
|
||||||
|
return (fA * tt + fB) * tt + fC;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sk2s fA;
|
||||||
|
Sk2s fB;
|
||||||
|
Sk2s fC;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SkConicCoeff {
|
||||||
|
SkConicCoeff(const SkConic& conic) {
|
||||||
|
Sk2s p0 = from_point(conic.fPts[0]);
|
||||||
|
Sk2s p1 = from_point(conic.fPts[1]);
|
||||||
|
Sk2s p2 = from_point(conic.fPts[2]);
|
||||||
|
Sk2s ww(conic.fW);
|
||||||
|
|
||||||
|
Sk2s p1w = p1 * ww;
|
||||||
|
fNumer.fC = p0;
|
||||||
|
fNumer.fA = p2 - times_2(p1w) + p0;
|
||||||
|
fNumer.fB = times_2(p1w - p0);
|
||||||
|
|
||||||
|
fDenom.fC = Sk2s(1);
|
||||||
|
fDenom.fB = times_2(ww - fDenom.fC);
|
||||||
|
fDenom.fA = Sk2s(0) - fDenom.fB;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sk2s eval(SkScalar t) {
|
||||||
|
Sk2s tt(t);
|
||||||
|
Sk2s numer = fNumer.eval(tt);
|
||||||
|
Sk2s denom = fDenom.eval(tt);
|
||||||
|
return numer / denom;
|
||||||
|
}
|
||||||
|
|
||||||
|
SkQuadCoeff fNumer;
|
||||||
|
SkQuadCoeff fDenom;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SkCubicCoeff {
|
||||||
|
SkCubicCoeff(const SkPoint src[4]) {
|
||||||
|
Sk2s P0 = from_point(src[0]);
|
||||||
|
Sk2s P1 = from_point(src[1]);
|
||||||
|
Sk2s P2 = from_point(src[2]);
|
||||||
|
Sk2s P3 = from_point(src[3]);
|
||||||
|
Sk2s three(3);
|
||||||
|
fA = P3 + three * (P1 - P2) - P0;
|
||||||
|
fB = three * (P2 - times_2(P1) + P0);
|
||||||
|
fC = three * (P1 - P0);
|
||||||
|
fD = P0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sk2s eval(SkScalar t) {
|
||||||
|
Sk2s tt(t);
|
||||||
|
return eval(tt);
|
||||||
|
}
|
||||||
|
|
||||||
|
Sk2s eval(const Sk2s& t) {
|
||||||
|
return ((fA * t + fB) * t + fC) * t + fD;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sk2s fA;
|
||||||
|
Sk2s fB;
|
||||||
|
Sk2s fC;
|
||||||
|
Sk2s fD;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#include "SkTemplates.h"
|
#include "SkTemplates.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,6 +73,15 @@ static bool quad_too_curvy(const SkPoint pts[3]) {
|
|||||||
return dist > CHEAP_DIST_LIMIT;
|
return dist > CHEAP_DIST_LIMIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool conic_too_curvy(const SkPoint& firstPt, const SkPoint& midTPt,
|
||||||
|
const SkPoint& lastPt) {
|
||||||
|
SkPoint midEnds = firstPt + lastPt;
|
||||||
|
midEnds *= 0.5f;
|
||||||
|
SkVector dxy = midTPt - midEnds;
|
||||||
|
SkScalar dist = SkMaxScalar(SkScalarAbs(dxy.fX), SkScalarAbs(dxy.fY));
|
||||||
|
return dist > CHEAP_DIST_LIMIT;
|
||||||
|
}
|
||||||
|
|
||||||
static bool cheap_dist_exceeds_limit(const SkPoint& pt,
|
static bool cheap_dist_exceeds_limit(const SkPoint& pt,
|
||||||
SkScalar x, SkScalar y) {
|
SkScalar x, SkScalar y) {
|
||||||
SkScalar dist = SkMaxScalar(SkScalarAbs(x - pt.fX), SkScalarAbs(y - pt.fY));
|
SkScalar dist = SkMaxScalar(SkScalarAbs(x - pt.fX), SkScalarAbs(y - pt.fY));
|
||||||
@ -90,27 +99,57 @@ static bool cubic_too_curvy(const SkPoint pts[4]) {
|
|||||||
SkScalarInterp(pts[0].fY, pts[3].fY, SK_Scalar1*2/3));
|
SkScalarInterp(pts[0].fY, pts[3].fY, SK_Scalar1*2/3));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* from http://www.malczak.linuxpl.com/blog/quadratic-bezier-curve-length/ */
|
static SkScalar quad_folded_len(const SkPoint pts[3]) {
|
||||||
static SkScalar compute_quad_len(const SkPoint pts[3]) {
|
SkScalar t = SkFindQuadMaxCurvature(pts);
|
||||||
SkPoint a,b;
|
SkPoint pt = SkEvalQuadAt(pts, t);
|
||||||
a.fX = pts[0].fX - 2 * pts[1].fX + pts[2].fX;
|
SkVector a = pts[2] - pt;
|
||||||
a.fY = pts[0].fY - 2 * pts[1].fY + pts[2].fY;
|
SkScalar result = a.length();
|
||||||
b.fX = 2 * (pts[1].fX - pts[0].fX);
|
if (0 != t) {
|
||||||
b.fY = 2 * (pts[1].fY - pts[0].fY);
|
SkVector b = pts[0] - pt;
|
||||||
SkScalar A = 4 * (a.fX * a.fX + a.fY * a.fY);
|
result += b.length();
|
||||||
SkScalar B = 4 * (a.fX * b.fX + a.fY * b.fY);
|
}
|
||||||
SkScalar C = b.fX * b.fX + b.fY * b.fY;
|
SkASSERT(SkScalarIsFinite(result));
|
||||||
|
return result;
|
||||||
SkScalar Sabc = 2 * SkScalarSqrt(A + B + C);
|
|
||||||
SkScalar A_2 = SkScalarSqrt(A);
|
|
||||||
SkScalar A_32 = 2 * A * A_2;
|
|
||||||
SkScalar C_2 = 2 * SkScalarSqrt(C);
|
|
||||||
SkScalar BA = B / A_2;
|
|
||||||
|
|
||||||
return (A_32 * Sabc + A_2 * B * (Sabc - C_2) +
|
|
||||||
(4 * C * A - B * B) * SkScalarLog((2 * A_2 + BA + Sabc) / (BA + C_2))) / (4 * A_32);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* from http://www.malczak.linuxpl.com/blog/quadratic-bezier-curve-length/ */
|
||||||
|
/* This works -- more needs to be done to see if it is performant on all platforms.
|
||||||
|
To use this to measure parts of quads requires recomputing everything -- perhaps
|
||||||
|
a chop-like interface can start from a larger measurement and get two new measurements
|
||||||
|
with one call here.
|
||||||
|
*/
|
||||||
|
static SkScalar compute_quad_len(const SkPoint pts[3]) {
|
||||||
|
SkPoint a,b;
|
||||||
|
a.fX = pts[0].fX - 2 * pts[1].fX + pts[2].fX;
|
||||||
|
a.fY = pts[0].fY - 2 * pts[1].fY + pts[2].fY;
|
||||||
|
SkScalar A = 4 * (a.fX * a.fX + a.fY * a.fY);
|
||||||
|
if (0 == A) {
|
||||||
|
a = pts[2] - pts[0];
|
||||||
|
return a.length();
|
||||||
|
}
|
||||||
|
b.fX = 2 * (pts[1].fX - pts[0].fX);
|
||||||
|
b.fY = 2 * (pts[1].fY - pts[0].fY);
|
||||||
|
SkScalar B = 4 * (a.fX * b.fX + a.fY * b.fY);
|
||||||
|
SkScalar C = b.fX * b.fX + b.fY * b.fY;
|
||||||
|
SkScalar Sabc = 2 * SkScalarSqrt(A + B + C);
|
||||||
|
SkScalar A_2 = SkScalarSqrt(A);
|
||||||
|
SkScalar A_32 = 2 * A * A_2;
|
||||||
|
SkScalar C_2 = 2 * SkScalarSqrt(C);
|
||||||
|
SkScalar BA = B / A_2;
|
||||||
|
if (0 == BA + C_2) {
|
||||||
|
return quad_folded_len(pts);
|
||||||
|
}
|
||||||
|
SkScalar J = A_32 * Sabc + A_2 * B * (Sabc - C_2);
|
||||||
|
SkScalar K = 4 * C * A - B * B;
|
||||||
|
SkScalar L = (2 * A_2 + BA + Sabc) / (BA + C_2);
|
||||||
|
if (L <= 0) {
|
||||||
|
return quad_folded_len(pts);
|
||||||
|
}
|
||||||
|
SkScalar M = SkScalarLog(L);
|
||||||
|
SkScalar result = (J + K * M) / (4 * A_32);
|
||||||
|
SkASSERT(SkScalarIsFinite(result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
SkScalar SkPathMeasure::compute_quad_segs(const SkPoint pts[3],
|
SkScalar SkPathMeasure::compute_quad_segs(const SkPoint pts[3],
|
||||||
SkScalar distance, int mint, int maxt, int ptIndex) {
|
SkScalar distance, int mint, int maxt, int ptIndex) {
|
||||||
@ -136,6 +175,7 @@ SkScalar SkPathMeasure::compute_quad_segs(const SkPoint pts[3],
|
|||||||
return distance;
|
return distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef SK_SUPPORT_LEGACY_CONIC_MEASURE
|
||||||
SkScalar SkPathMeasure::compute_conic_segs(const SkConic& conic,
|
SkScalar SkPathMeasure::compute_conic_segs(const SkConic& conic,
|
||||||
SkScalar distance, int mint, int maxt, int ptIndex) {
|
SkScalar distance, int mint, int maxt, int ptIndex) {
|
||||||
if (tspan_big_enough(maxt - mint) && quad_too_curvy(conic.fPts)) {
|
if (tspan_big_enough(maxt - mint) && quad_too_curvy(conic.fPts)) {
|
||||||
@ -159,6 +199,30 @@ SkScalar SkPathMeasure::compute_conic_segs(const SkConic& conic,
|
|||||||
}
|
}
|
||||||
return distance;
|
return distance;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
SkScalar SkPathMeasure::compute_conic_segs(const SkConic& conic, SkScalar distance,
|
||||||
|
int mint, const SkPoint& minPt,
|
||||||
|
int maxt, const SkPoint& maxPt, int ptIndex) {
|
||||||
|
int halft = (mint + maxt) >> 1;
|
||||||
|
SkPoint halfPt = conic.evalAt(tValue2Scalar(halft));
|
||||||
|
if (tspan_big_enough(maxt - mint) && conic_too_curvy(minPt, halfPt, maxPt)) {
|
||||||
|
distance = this->compute_conic_segs(conic, distance, mint, minPt, halft, halfPt, ptIndex);
|
||||||
|
distance = this->compute_conic_segs(conic, distance, halft, halfPt, maxt, maxPt, ptIndex);
|
||||||
|
} else {
|
||||||
|
SkScalar d = SkPoint::Distance(minPt, maxPt);
|
||||||
|
SkScalar prevD = distance;
|
||||||
|
distance += d;
|
||||||
|
if (distance > prevD) {
|
||||||
|
Segment* seg = fSegments.append();
|
||||||
|
seg->fDistance = distance;
|
||||||
|
seg->fPtIndex = ptIndex;
|
||||||
|
seg->fType = kConic_SegType;
|
||||||
|
seg->fTValue = maxt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
SkScalar SkPathMeasure::compute_cubic_segs(const SkPoint pts[4],
|
SkScalar SkPathMeasure::compute_cubic_segs(const SkPoint pts[4],
|
||||||
SkScalar distance, int mint, int maxt, int ptIndex) {
|
SkScalar distance, int mint, int maxt, int ptIndex) {
|
||||||
@ -253,7 +317,12 @@ void SkPathMeasure::buildSegments() {
|
|||||||
case SkPath::kConic_Verb: {
|
case SkPath::kConic_Verb: {
|
||||||
const SkConic conic(pts, fIter.conicWeight());
|
const SkConic conic(pts, fIter.conicWeight());
|
||||||
SkScalar prevD = distance;
|
SkScalar prevD = distance;
|
||||||
|
#ifdef SK_SUPPORT_LEGACY_CONIC_MEASURE
|
||||||
distance = this->compute_conic_segs(conic, distance, 0, kMaxTValue, ptIndex);
|
distance = this->compute_conic_segs(conic, distance, 0, kMaxTValue, ptIndex);
|
||||||
|
#else
|
||||||
|
distance = this->compute_conic_segs(conic, distance, 0, conic.fPts[0],
|
||||||
|
kMaxTValue, conic.fPts[2], ptIndex);
|
||||||
|
#endif
|
||||||
if (distance > prevD) {
|
if (distance > prevD) {
|
||||||
// we store the conic weight in our next point, followed by the last 2 pts
|
// we store the conic weight in our next point, followed by the last 2 pts
|
||||||
// thus to reconstitue a conic, you'd need to say
|
// thus to reconstitue a conic, you'd need to say
|
||||||
@ -406,7 +475,8 @@ static void seg_to(const SkPoint pts[], int segType,
|
|||||||
dst->conicTo(tmp[0].fPts[1], tmp[0].fPts[2], tmp[0].fW);
|
dst->conicTo(tmp[0].fPts[1], tmp[0].fPts[2], tmp[0].fW);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
SkConic tmp1[2];
|
#ifdef SK_SUPPORT_LEGACY_CONIC_MEASURE
|
||||||
|
SkConic tmp1[2];
|
||||||
conic.chopAt(startT, tmp1);
|
conic.chopAt(startT, tmp1);
|
||||||
if (SK_Scalar1 == stopT) {
|
if (SK_Scalar1 == stopT) {
|
||||||
dst->conicTo(tmp1[1].fPts[1], tmp1[1].fPts[2], tmp1[1].fW);
|
dst->conicTo(tmp1[1].fPts[1], tmp1[1].fPts[2], tmp1[1].fW);
|
||||||
@ -415,6 +485,17 @@ static void seg_to(const SkPoint pts[], int segType,
|
|||||||
tmp1[1].chopAt((stopT - startT) / (SK_Scalar1 - startT), tmp2);
|
tmp1[1].chopAt((stopT - startT) / (SK_Scalar1 - startT), tmp2);
|
||||||
dst->conicTo(tmp2[0].fPts[1], tmp2[0].fPts[2], tmp2[0].fW);
|
dst->conicTo(tmp2[0].fPts[1], tmp2[0].fPts[2], tmp2[0].fW);
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
if (SK_Scalar1 == stopT) {
|
||||||
|
SkConic tmp1[2];
|
||||||
|
conic.chopAt(startT, tmp1);
|
||||||
|
dst->conicTo(tmp1[1].fPts[1], tmp1[1].fPts[2], tmp1[1].fW);
|
||||||
|
} else {
|
||||||
|
SkConic tmp;
|
||||||
|
conic.chopAt(startT, stopT, &tmp);
|
||||||
|
dst->conicTo(tmp.fPts[1], tmp.fPts[2], tmp.fW);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case kCubic_SegType:
|
case kCubic_SegType:
|
||||||
|
@ -201,3 +201,19 @@ DEF_TEST(PathMeasure, reporter) {
|
|||||||
test_small_segment2();
|
test_small_segment2();
|
||||||
test_small_segment3();
|
test_small_segment3();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEF_TEST(PathMeasureConic, reporter) {
|
||||||
|
SkPoint stdP, hiP, pts[] = {{0,0}, {100,0}, {100,0}};
|
||||||
|
SkPath p;
|
||||||
|
p.moveTo(0, 0);
|
||||||
|
p.conicTo(pts[1], pts[2], 1);
|
||||||
|
SkPathMeasure stdm(p, false);
|
||||||
|
REPORTER_ASSERT(reporter, stdm.getPosTan(20, &stdP, nullptr));
|
||||||
|
p.reset();
|
||||||
|
p.moveTo(0, 0);
|
||||||
|
p.conicTo(pts[1], pts[2], 10);
|
||||||
|
stdm.setPath(&p, false);
|
||||||
|
REPORTER_ASSERT(reporter, stdm.getPosTan(20, &hiP, nullptr));
|
||||||
|
REPORTER_ASSERT(reporter, 19.5f < stdP.fX && stdP.fX < 20.5f);
|
||||||
|
REPORTER_ASSERT(reporter, 19.5f < hiP.fX && hiP.fX < 20.5f);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user