本文介绍不依赖贝塞尔曲线,如何绘制一条平滑曲线,用于解决无贝塞尔控制点的情况下绘制曲线、但数据点不在贝塞尔曲线的场景。
在上一家公司我做过一个平滑曲线编辑工具,用于轮椅调整加减速曲线。基于几个用户可控制的点,生成一条平滑的曲线,控制点需要保持在曲线上。
今天和小伙伴沟通,白板以自定义形状绘制笔迹,也可以使用到这个数据点模拟的技术,我回顾总结下
贝塞尔平滑曲线
我们先讲贝塞尔曲线GDI+ 中的贝塞尔自由绘制曲线 - Windows Forms .NET Framework | Microsoft Learn。一般情况我们绘制平滑曲线,直接以贝塞尔曲线API将多个点作为参数,直接进行绘制。这种情况下API会自动将第一个点作为控制点,得到贝塞尔曲线,比如下面生成一条平滑Geometry:
1 var geometryTest = new StreamGeometry();2 using(var ctx = geometryTest.Open())3 {4 ctx.BeginFigure(_points[0], true, false);5 if(keyPoints.Count % 2 == 0)6 {7 //绘制二阶贝塞尔函数,需要保证为偶数点8 ctx.PolyQuadraticBezierTo(keyPoints, true, true);9 }
10 else
11 {
12 //绘制二阶贝塞尔函数,需要保证为偶数点
13 keyPoints.Insert(0, keyPoints[0]);
14 ctx.PolyQuadraticBezierTo(keyPoints, true, true);
15 }
16 }
这里的PolyQuadraticBezierTo函数,塞点集列表进去并设置平滑参数isSmoothJoin=true
1 public abstract void PolyQuadraticBezierTo(
2 IList<Point> points,
3 bool isStroked,
4 bool isSmoothJoin);
5
6 public abstract void PolyBezierTo(IList<Point> points, bool isStroked, bool isSmoothJoin);
官网有介绍,列表中第一个点作为控制点:StreamGeometryContext.PolyQuadraticBezierTo 方法 (System.Windows.Media) |Microsoft 学习
上面是自动设置控制点,这类实现方案会有一个问题:数据点最终可能不在曲线上
基于贝塞尔曲线,我们也可以计算控制点。但计算控制点,也是同样无法保证原始数据点会在拟合后的曲线上。
模拟平滑曲线
以现有数据点,如果直接相连肯定只会生成多个折线。如果我们添加多个点,可以模拟一条类似曲线路径的多边形近似点集,与Geometry下的FlattenedPathGeometry有点类似。
方案一,可以使用MathNet.Numerics生成一条X方向的N阶曲线,然后输入X坐标输出Y坐标,得到曲线上的点。 MathNet.Numerics可以参考 .NET 白板书写加速-曲线拟合预测 - 唐宋元明清2188 - 博客园。但这方案会生成无数点,曲线绘制性能无法得到保证。所以添加这些曲线路径的点,如何以最小的点集实现?可以对相邻点,对向量角度变化以及相邻间距设置一个最小阈值,最终得到符合的点集
方案二,用我之前实现方案,根据最简多项式代码算出近似样条曲线点集。原理同MathNet.Numerics里的Polynomial函数,下面是部分代码:
1 private const double Tolerance = 0.5;2 3 /// <summary>4 /// 获取拟合后的点集5 /// </summary>6 /// <param name="points"></param>7 /// <returns></returns>8 public static List<Point> GetFittingLinePoints(List<Point> points)9 {
10 var orderedPoints = (from pt in points orderby pt.X select pt).ToList();
11 var secondDerivatives = SecondDerivativeHelper.GetSecondDerivatives(orderedPoints);
12 List<Point> polyLinePoints = PointFakeApproximationHelper.GetSplinePolyLineApproximation(orderedPoints, secondDerivatives, Tolerance);
13 return polyLinePoints;
14 }

rlgk.b2b.h3cw.com
reze.b2b.h3cw.com
qqfn.b2b.h3cw.com
bhqj.b2b.h3cw.com
aqmu.b2b.h3cw.com
bvgc.b2b.h3cw.com
eyjf.b2b.h3cw.com
ddck.b2b.h3cw.com
pckt.b2b.h3cw.com
zaco.b2b.h3cw.com
mfhf.b2b.h3cw.com
atcb.b2b.h3cw.com
ioqy.b2b.h3cw.com
bpap.b2b.h3cw.com
olgw.b2b.h3cw.com
uuqm.b2b.h3cw.com
sfnf.b2b.h3cw.com
uuql.b2b.h3cw.com
wtpi.b2b.h3cw.com
papn.b2b.h3cw.com
ckcq.b2b.h3cw.com
tknm.b2b.h3cw.com
lfkn.b2b.h3cw.com
cnid.b2b.h3cw.com
ibpu.b2b.h3cw.com
buvh.b2b.h3cw.com
wpew.b2b.h3cw.com
ydxq.b2b.h3cw.com
szuo.b2b.h3cw.com
xxso.b2b.h3cw.com
axfb.b2b.h3cw.com
tzik.b2b.h3cw.com
djbq.b2b.h3cw.com
rxwl.b2b.h3cw.com
ddvp.b2b.h3cw.com
rezr.b2b.h3cw.com
wpkf.b2b.h3cw.com
ccvr.b2b.h3cw.com
vwxm.b2b.h3cw.com
ndvd.b2b.h3cw.com
iveh.b2b.h3cw.com
fmfl.b2b.h3cw.com
hsbt.b2b.h3cw.com
bidl.b2b.h3cw.com
rxpy.b2b.h3cw.com
auvu.b2b.h3cw.com
hkcv.b2b.h3cw.com
yehm.b2b.h3cw.com
ylkm.b2b.h3cw.com
kkje.b2b.h3cw.com
uoap.b2b.h3cw.com
ocyj.b2b.h3cw.com
obqc.b2b.h3cw.com
cgpu.b2b.h3cw.com
rrxm.b2b.h3cw.com
qjiq.b2b.h3cw.com
pvkc.b2b.h3cw.com
aowb.b2b.h3cw.com
xrzr.b2b.h3cw.com
pwoq.b2b.h3cw.com
nslo.b2b.h3cw.com
sfbt.b2b.h3cw.com
uacv.b2b.h3cw.com
qkpk.b2b.h3cw.com
vvbj.b2b.h3cw.com
cpvr.b2b.h3cw.com
pvaw.b2b.h3cw.com
rixm.b2b.h3cw.com
piog.b2b.h3cw.com
kfns.b2b.h3cw.com
husi.b2b.h3cw.com
tgla.b2b.h3cw.com
lqrt.b2b.h3cw.com
ssij.b2b.h3cw.com
mltl.b2b.h3cw.com
ssxz.b2b.h3cw.com
fvrm.b2b.h3cw.com
jjbo.b2b.h3cw.com
lsya.b2b.h3cw.com
lozh.b2b.h3cw.com
bqfo.b2b.h3cw.com
wwem.b2b.h3cw.com
ccdc.b2b.h3cw.com
exje.b2b.h3cw.com
zgvj.b2b.h3cw.com
qxwi.b2b.h3cw.com
gefh.b2b.h3cw.com
vbxc.b2b.h3cw.com
szks.b2b.h3cw.com
vcpb.b2b.h3cw.com
lltv.b2b.h3cw.com
fmum.b2b.h3cw.com
ntvw.b2b.h3cw.com
jqlm.b2b.h3cw.com
ppes.b2b.h3cw.com
pjxv.b2b.h3cw.com
ftbd.b2b.h3cw.com
huib.b2b.h3cw.com
fyrm.b2b.h3cw.com
szuh.b2b.h3cw.com
qklu.b2b.h3cw.com
ljyr.b2b.h3cw.com
ttvn.b2b.h3cw.com
yfuj.b2b.h3cw.com
iiqe.b2b.h3cw.com
mxzf.b2b.h3cw.com
ffug.b2b.h3cw.com
mfig.b2b.h3cw.com
gamz.b2b.h3cw.com
ndmy.b2b.h3cw.com
uqir.b2b.h3cw.com
svmu.b2b.h3cw.com
cskz.b2b.h3cw.com
vjpr.b2b.h3cw.com
mshm.b2b.h3cw.com
hack.b2b.h3cw.com
rxge.b2b.h3cw.com
gauz.b2b.h3cw.com
pbkl.b2b.h3cw.com
kkle.b2b.h3cw.com
wbwe.b2b.h3cw.com
efta.b2b.h3cw.com
nbwv.b2b.h3cw.com
mmhd.b2b.h3cw.com
eexr.b2b.h3cw.com
vjwo.b2b.h3cw.com
zpkt.b2b.h3cw.com
hhpv.b2b.h3cw.com
ahcv.b2b.h3cw.com
remg.b2b.h3cw.com
ivhb.b2b.h3cw.com
ghii.b2b.h3cw.com
vhwr.b2b.h3cw.com
lyun.b2b.h3cw.com
qoqw.b2b.h3cw.com
hytn.b2b.h3cw.com
ffng.b2b.h3cw.com
lysr.b2b.h3cw.com
vyjc.b2b.h3cw.com
lmng.b2b.h3cw.com
wjem.b2b.h3cw.com
bgtb.b2b.h3cw.com
xojl.b2b.h3cw.com
oxed.b2b.h3cw.com
duvx.b2b.h3cw.com
ecld.b2b.h3cw.com
cpbc.b2b.h3cw.com
mrez.b2b.h3cw.com
viud.b2b.h3cw.com
kegy.b2b.h3cw.com
zljy.b2b.h3cw.com
mgbq.b2b.h3cw.com
ekfu.b2b.h3cw.com
rfet.b2b.h3cw.com
cpej.b2b.h3cw.com
unpi.b2b.h3cw.com
wqlr.b2b.h3cw.com
jdid.b2b.h3cw.com
nkaz.b2b.h3cw.com
lrqw.b2b.h3cw.com
mseh.b2b.h3cw.com
rjwr.b2b.h3cw.com
ahng.b2b.h3cw.com
ayrz.b2b.h3cw.com
fsuh.b2b.h3cw.com
ovbk.b2b.h3cw.com
lrai.b2b.h3cw.com
hfuz.b2b.h3cw.com
xjra.b2b.h3cw.com
usoa.b2b.h3cw.com
hbwc.b2b.h3cw.com
tzru.b2b.h3cw.com
ffum.b2b.h3cw.com
znbn.b2b.h3cw.com
voqz.b2b.h3cw.com
bnce.b2b.h3cw.com
rhgb.b2b.h3cw.com
oxwr.b2b.h3cw.com
mgvh.b2b.h3cw.com
obgv.b2b.h3cw.com
zmiu.b2b.h3cw.com
ttbk.b2b.h3cw.com
jcnp.b2b.h3cw.com
ledj.b2b.h3cw.com
uapv.b2b.h3cw.com
hpkw.b2b.h3cw.com
ovqj.b2b.h3cw.com
cunp.b2b.h3cw.com
vuwf.b2b.h3cw.com
xkvl.b2b.h3cw.com
retl.b2b.h3cw.com
qbje.b2b.h3cw.com
abpy.b2b.h3cw.com
ludv.b2b.h3cw.com
umum.b2b.h3cw.com
fsxg.b2b.h3cw.com
psbb.b2b.h3cw.com
urgl.b2b.h3cw.com
nazt.b2b.h3cw.com
ztbu.b2b.h3cw.com
wfpf.b2b.h3cw.com
wpyp.b2b.h3cw.com
bhxc.b2b.h3cw.com
chju.b2b.h3cw.com
mabq.b2b.h3cw.com
imod.b2b.h3cw.com
hogc.b2b.h3cw.com
wiog.b2b.h3cw.com
univ.b2b.h3cw.com
hodo.b2b.h3cw.com
cpbk.b2b.h3cw.com
vmon.b2b.h3cw.com
wobr.b2b.h3cw.com
ggxm.b2b.h3cw.com
ohmb.b2b.h3cw.com
ahvy.b2b.h3cw.com
ysmr.b2b.h3cw.com
bihn.b2b.h3cw.com
lyxf.b2b.h3cw.com
bbwq.b2b.h3cw.com
mqsk.b2b.h3cw.com
obtv.b2b.h3cw.com
uumu.b2b.h3cw.com
hbcj.b2b.h3cw.com
rrdw.b2b.h3cw.com
kagi.b2b.h3cw.com
unco.b2b.h3cw.com
zigd.b2b.h3cw.com
hhco.b2b.h3cw.com
mmot.b2b.h3cw.com
iirz.b2b.h3cw.com
dqdm.b2b.h3cw.com
yluz.b2b.h3cw.com
icix.b2b.h3cw.com
uuju.b2b.h3cw.com
dgbq.b2b.h3cw.com
djvi.b2b.h3cw.com
bboj.b2b.h3cw.com
huco.b2b.h3cw.com
uncp.b2b.h3cw.com
mtmt.b2b.h3cw.com
lqed.b2b.h3cw.com
qemh.b2b.h3cw.com
xupp.b2b.h3cw.com
gwvq.b2b.h3cw.com
zmeh.b2b.h3cw.com
smeg.b2b.h3cw.com
dxmx.b2b.h3cw.com
kjcc.b2b.h3cw.com
kksh.b2b.h3cw.com
reai.b2b.h3cw.com
xxgs.b2b.h3cw.com
ipkm.b2b.h3cw.com
uujo.b2b.h3cw.com
nuuw.b2b.h3cw.com
ohcb.b2b.h3cw.com
yezo.b2b.h3cw.com
numj.b2b.h3cw.com
jwpx.b2b.h3cw.com
isac.b2b.h3cw.com
zzsz.b2b.h3cw.com
mmgo.b2b.h3cw.com
hucl.b2b.h3cw.com
ibac.b2b.h3cw.com
ztbd.b2b.h3cw.com
sskd.b2b.h3cw.com
bnwb.b2b.h3cw.com
icow.b2b.h3cw.com
dkfy.b2b.h3cw.com
ijpb.b2b.h3cw.com
qejl.b2b.h3cw.com
sjqf.b2b.h3cw.com
sfro.b2b.h3cw.com
unpr.b2b.h3cw.com
tnjk.b2b.h3cw.com
pwbd.b2b.h3cw.com
efkm.b2b.h3cw.com
fwrm.b2b.h3cw.com
gnya.b2b.h3cw.com
zgvu.b2b.h3cw.com
kvrq.b2b.h3cw.com
wkln.b2b.h3cw.com
gtce.b2b.h3cw.com
qkkd.b2b.h3cw.com
cvet.b2b.h3cw.com
ioxj.b2b.h3cw.com
kxnk.b2b.h3cw.com
ngpe.b2b.h3cw.com
yysi.b2b.h3cw.com
zgyn.b2b.h3cw.com
ahpb.b2b.h3cw.com
duvk.b2b.h3cw.com
hhji.b2b.h3cw.com
xxzz.b2b.h3cw.com
gqln.b2b.h3cw.com
cixm.b2b.h3cw.com
xkkk.b2b.h3cw.com
cvix.b2b.h3cw.com
jwlt.b2b.h3cw.com
jwlx.b2b.h3cw.com
jdvk.b2b.h3cw.com
obgw.b2b.h3cw.com
iokz.b2b.h3cw.com
ovac.b2b.h3cw.com
obht.b2b.h3cw.com
jqlx.b2b.h3cw.com
fpgj.b2b.h3cw.com
lyam.b2b.h3cw.com
tqfl.b2b.h3cw.com
dacv.b2b.h3cw.com
reqj.b2b.h3cw.com
opki.b2b.h3cw.com
gdyu.b2b.h3cw.com
cgoa.b2b.h3cw.com
dncl.b2b.h3cw.com
mqjf.b2b.h3cw.com
pwpo.b2b.h3cw.com
ylbz.b2b.h3cw.com
udve.b2b.h3cw.com
bbkp.b2b.h3cw.com
lqdj.b2b.h3cw.com
nzzs.b2b.h3cw.com
wirp.b2b.h3cw.com
vvcr.b2b.h3cw.com
rved.b2b.h3cw.com
hptu.b2b.h3cw.com
wpvx.b2b.h3cw.com
kxpe.b2b.h3cw.com
uyfa.b2b.h3cw.com
siox.b2b.h3cw.com
kegl.b2b.h3cw.com
wjyg.b2b.h3cw.com
mahn.b2b.h3cw.com
tseg.b2b.h3cw.com
cpuc.b2b.h3cw.com
iinp.b2b.h3cw.com
bovu.b2b.h3cw.com
yytm.b2b.h3cw.com
exjq.b2b.h3cw.com
wdle.b2b.h3cw.com
ttbu.b2b.h3cw.com
njci.b2b.h3cw.com
tgvv.b2b.h3cw.com
bvkl.b2b.h3cw.com
sgrz.b2b.h3cw.com
cjrp.b2b.h3cw.com
dqrq.b2b.h3cw.com
sacv.b2b.h3cw.com
fzeg.b2b.h3cw.com
flxs.b2b.h3cw.com
mfut.b2b.h3cw.com
qkzj.b2b.h3cw.com
haph.b2b.h3cw.com
zzub.b2b.h3cw.com
ooej.b2b.h3cw.com
istb.b2b.h3cw.com
pqci.b2b.h3cw.com
jwld.b2b.h3cw.com
jqid.b2b.h3cw.com
ahjv.b2b.h3cw.com
ipjj.b2b.h3cw.com
uffy.b2b.h3cw.com
nawh.b2b.h3cw.com
ppeg.b2b.h3cw.com
tytm.b2b.h3cw.com
bcxl.b2b.h3cw.com
tnpl.b2b.h3cw.com
ztvo.b2b.h3cw.com
ivap.b2b.h3cw.com
llgg.b2b.h3cw.com
resx.b2b.h3cw.com
naqi.b2b.h3cw.com
zmyt.b2b.h3cw.com
ovdy.b2b.h3cw.com
amyu.b2b.h3cw.com
yrlj.b2b.h3cw.com
hudo.b2b.h3cw.com
fcyn.b2b.h3cw.com
bbpy.b2b.h3cw.com
oowb.b2b.h3cw.com
jqyy.b2b.h3cw.com
nnfv.b2b.h3cw.com
ovna.b2b.h3cw.com
hyhj.b2b.h3cw.com
jxlr.b2b.h3cw.com
smqq.b2b.h3cw.com
bwik.b2b.h3cw.com
wtlb.b2b.h3cw.com
qdff.b2b.h3cw.com
bhjw.b2b.h3cw.com
ooci.b2b.h3cw.com
vpbo.b2b.h3cw.com
sdze.b2b.h3cw.com
jujr.b2b.h3cw.com
ddid.b2b.h3cw.com
ylyl.b2b.h3cw.com
xrsh.b2b.h3cw.com
wacp.b2b.h3cw.com
rygc.b2b.h3cw.com
vwhv.b2b.h3cw.com
bxwr.b2b.h3cw.com
tbso.b2b.h3cw.com
icck.b2b.h3cw.com
ypuw.b2b.h3cw.com
zmnt.b2b.h3cw.com
jdgl.b2b.h3cw.com
yzfu.b2b.h3cw.com
vvha.b2b.h3cw.com
qacx.b2b.h3cw.com
cjro.b2b.h3cw.com
ysus.b2b.h3cw.com
dxzu.b2b.h3cw.com
lyeg.b2b.h3cw.com
rrrj.b2b.h3cw.com
aauc.b2b.h3cw.com
kkzf.b2b.h3cw.com
mteu.b2b.h3cw.com
smkg.b2b.h3cw.com
bhac.b2b.h3cw.com
xcie.b2b.h3cw.com
eixs.b2b.h3cw.com
uhzn.b2b.h3cw.com
ryhz.b2b.h3cw.com
cogp.b2b.h3cw.com
pncu.b2b.h3cw.com
rshk.b2b.h3cw.com
sltf.b2b.h3cw.com
phdj.b2b.h3cw.com
binw.b2b.h3cw.com
lfkz.b2b.h3cw.com
kddw.b2b.h3cw.com
jjrd.b2b.h3cw.com
euql.b2b.h3cw.com
ppxz.b2b.h3cw.com
ppic.b2b.h3cw.com
tgci.b2b.h3cw.com
qkxs.b2b.h3cw.com
ibha.b2b.h3cw.com
ovbb.b2b.h3cw.com
ifnc.b2b.h3cw.com
rymz.b2b.h3cw.com
rett.b2b.h3cw.com
gnug.b2b.h3cw.com
gwux.b2b.h3cw.com
leml.b2b.h3cw.com
ccef.b2b.h3cw.com
oznt.b2b.h3cw.com
ibow.b2b.h3cw.com
cvwi.b2b.h3cw.com
tsfs.b2b.h3cw.com
pwvx.b2b.h3cw.com
ggpo.b2b.h3cw.com
jgdz.b2b.h3cw.com
rdsb.b2b.h3cw.com
ztbj.b2b.h3cw.com
fghn.b2b.h3cw.com
tgha.b2b.h3cw.com
icuj.b2b.h3cw.com
numh.b2b.h3cw.com
azun.b2b.h3cw.com
efne.b2b.h3cw.com
ykln.b2b.h3cw.com
nnpr.b2b.h3cw.com
fnoo.b2b.h3cw.com
cqyp.b2b.h3cw.com
uoqi.b2b.h3cw.com
lran.b2b.h3cw.com
mnzp.b2b.h3cw.com
lcex.b2b.h3cw.com
djrx.b2b.h3cw.com
eyat.b2b.h3cw.com
dxzo.b2b.h3cw.com
获取俩点之间点集:

1 /// <summary>2 /// 用折线逼近三次多项式并给出公差。3 /// </summary>4 /// <param name="leftPoint">三次多项式左点</param>5 /// <param name="rightPoint">三次多项式右点</param>6 /// <param name="secondDerivativeLeft">左点三次多项式二阶导数.</param>7 /// <param name="secondDerivativeRight">右端三次多项式二阶导数.</param>8 /// <param name="tolerance">公差,即样条到近似折线的最大距离。</param>9 /// <returns>在给定公差的情况下逼近三次多项式的多边形点的列表。</returns>
10 private static Collection<Point> GetApproximation(Point leftPoint, Point rightPoint, double secondDerivativeLeft, double secondDerivativeRight, double tolerance)
11 {
12 // 左右俩点的X、Y轴值
13 double leftPointX = leftPoint.X, rightPointX = rightPoint.X;
14 double leftPointY = leftPoint.Y, rightPointY = rightPoint.Y;
15 // 次区间多项式系数
16 double a = (secondDerivativeRight - secondDerivativeLeft) / (6 * (rightPointX - leftPointX));
17 double b = (secondDerivativeLeft - 6 * a * leftPointX) / 2;
18 double c = (rightPointY - rightPointX * rightPointX * (a * rightPointX + b) - leftPointY + leftPointX * leftPointX * (a * leftPointX + b)) / (rightPointX - leftPointX);
19 double d = leftPointY - leftPointX * (leftPointX * (a * leftPointX + b) + c);
20
21 //如果a的值为0,则给a赋值double类型的最小正数
22 if(a == 0)
23 a = double.Epsilon;
24
25 //通过多项式与拆线的逼近,获取多边形点的列表
26 Collection<Point> points = CubicPolynomialPolylineApproximation.Approximate(new Polynomial(new double[] { d, c, b, a }), leftPointX, rightPointX, tolerance);
27 return points;
28 }

效果如下图,左侧是原数据点集(绿色),右则截图是模拟后的点集显示(红色):

把这些新增点与原数据点,用直接连接起来,就是一条比较平滑的曲线了。同时原数据点也在拟合的曲线上。另外,如果还需要优化下这些线段的平滑,可以使用贝塞尔曲线(见上面贝塞尔的实现)替换直线连接,已经加了很多密集点,绿点不会脱离曲线的。
Github仓库代码 GitHub - kybs00/CurveLineEditDemo: 平滑曲线模拟及编辑Demo
编辑平滑曲线
上面我们已经完成了平滑曲线的点集模拟,连接起来就是一条曲线。在一些业务场景中,需要以曲线上的点为控制操作点,移动点以达到编辑曲线。
在曲线上设置多个操作点。如何在曲线上设置期望位置的点,可以看 .NET 曲线上的点- 获取距离最近的点 - 唐宋元明清2188 - 博客园。点击时,获取曲线离点击位置最近的点即可
选择点后,操作控制点移动。曲线根据操作点的位置变更,重新生成新的曲线。曲线编辑效果如下图:

重新生成曲线这部分代码没有啥难点,核心代码就是上面的平滑曲线模拟。看上面仓库代码即可
参考文章:
GDI+ 中的贝塞尔自由绘制曲线 - Windows Forms .NET Framework | Microsoft Learn
Bezier曲线反求控制点_如何反求贝塞尔曲线的控制点-CSDN博客