Qt鼠标绘制平滑曲线解决方案(1)

Qt鼠标实时绘制平滑曲线(1)

Qt的QWidget提供了一些鼠标相关的事件,QWidget::mousePressEvent、QWidget::mouseReleaseEvent、QWidget::mouseMoveEvent三个事件配合使用可以实现简单的绘图操作。
QWidget::mouseMoveEvent采集坐标点间隔与移动速度有关。简单绘图是使用QPainter::drawLine连接相邻的两个点,或生成QPainterPath,可以设置线段连接处的样式。
QPainterPath提供了一些常见的曲线绘图接口,对于鼠标采集到的点,仅利用这些接口绘制一条平滑的曲线,是几乎做不到的。

主要的原因有:

1. 采集到的点相邻间距多数比较小
    常见的绘制平滑曲线是以最近采集到的N个点计算某两点之间的Bezier曲线,再使用QPainterPath::cubicTo绘制该曲线。比如P0、P1、P2、P3,四个点,可以简单计算P1和P2之间的一条Bezier曲线。不过对于比较密集的点,计算的P1的控制点可能等于P1

2. 鼠标抖动
    由于鼠标的坐标只能是整数,数学上计算的线段上的点距离绘制的点会有偏移。这种情况下,QPainter开启抗锯齿可以解决较长线段的绘制。当鼠标缓慢移动时,比如,用手移动鼠标可能时希望从P0(0,0)移动至P1(1,1),但实际可能也采集到了P(1,0)或者P(0,1)。

解决办法:
    自己也试过一些办法,比如使用Catmull-Rom算法,该算法是计算两个控制点之间去线上的点坐标,可以用采集到的点作为控制点计算连续的点,再通过降低采样减少上面的问题。不过能同时解决两个问题的办法就是拟合。生成几段Bezier曲线,使采集到的鼠标点在一定的误差范围内。

    后来自己在使用 Snipaste 这个截图工具的时候发现,该工具能够在鼠标绘制完成后,生成一条平滑的曲线。找了一天,查到该工具使用的是Paper.js里的曲线拟合算法。

Paper.js拟合曲线的示例

Paper.js中拟合算法简单理解

  1. 输入所有的坐标点 [ P0 ,…..Pn ];
  2. 计算出 P0 和 Pn 之间的一条Bezier曲线C
  3. 找出 [ P0 , ….Pn ]中 距离曲线 C 最大的坐标点 Pi以及距离distance
  4. 如果distance 在误差范围内则拟合成功,产生一条Bezier曲线并添加到列表。否则分别拟合 [ P0 ,….Pi ] 和[ Pi ,….Pn ] ,也就是有分治的思想在内

该算法拟合 [ Pa ,….Pb ] 之间的Bezier
时,需要一开始指定Bezier曲线的两个控制点方向。P0的方向是向量P0 Pn ,Pn的方向是向量Pn P0,中间拟合过程中Pi左右的控制点方向是Pi-1Pi+1-Pi-1Pi+1


接下来产生的问题是

如果每采集一个点就重新拟合一次,由于每次分治的点不一样,整个曲线会像琴弦一样抖动。


猜你喜欢

转载自blog.csdn.net/eiilpux17/article/details/78585589