Qt实现画板
基于QGraphicsView实现绘画曲线注意点_qgraphicsview绘制曲线图-CSDN博客
Qt桌面白板工具其一(解决曲线不平滑的问题——贝塞尔曲线)_qt贝塞尔曲线-CSDN博客
先实现一个最简单的不考虑任何优化的曲线画板
我们知道鼠标移动时,会在其移动轨迹上留下一系列的点,我们可以将这些点用直线连起来的方式,实现曲线的绘画。
虽然说这样子连起来的曲线在鼠标移动过快的情况下会出现不平滑的现象,但现在我们主要是把能绘画的程序做出来,而非将其做成很好的地步。毕竟万事开头难,这里主要是为了让我们对如何搭建绘画曲线的程序有一个初步的认知。
我们可以让QGraphicsScene
通过处理鼠标事件,然后为我们收集到关于这条曲线的一组散点,然后交给自定义的Item,对这组散点进行模拟绘画。
关于散点的处理,可以是简单地将这些散点用直线连接起来,也可以是经过数值分析之后,使用插值或者其他数值分析方法得出来的模拟图。
我们这里只讨论最简单的处理方式,也就是简单地将散点连接起来,然后进行绘制。
最终取得的结果如下:
最终的绘画结果大概是这样的,虽然能够将曲线绘画出来,但是缺少了平滑性,这远远没有达到我的预期。画直线还好,一旦散点不再是单纯的直线,那么就会出现线不连续,这种现象一般是不现实的,我们更进一步的目的是优化使用这些散点进行绘图的算法,这需要使用到数值分析之类的知识。
void CustomItem::pointMove(const QPointF& newPoint)
{
maxX = newPoint.x() > maxX? newPoint.x() : maxX;
minX = newPoint.x() < minX? newPoint.x() : minX;
maxY = newPoint.y() > maxY? newPoint.y() : maxY;
minY = newPoint.y() < minY? newPoint.y() : minY;
points.append(newPoint);
scene()->update();
}
CustomItem所在的场景需要通过检测鼠标移动事件,将轨迹散点交给CustomItem。
然后CustomItem在paint()中使用这些记录好的散点进行绘图操作。一旦场景的移动事件完毕,这个CustomItem的形状就固定了。
void CustomItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
if (points.size() == 1) {
painter->drawPoint(points.front());
}
else {
auto lastPoint = points.begin();
for (auto newPoint = ++lastPoint; newPoint != points.end(); lastPoint = newPoint, ++newPoint) {
painter->drawLine(*lastPoint, *newPoint);
}
}
}
具体的草稿代码可以查看:Aderversa/SimpleDrawCurves (github.com)
贝塞尔曲线学习
这里博主学习了其他人的博客:贝塞尔曲线(Bezier Curve)原理、公式推导及matlab代码实现-CSDN博客
如何将贝塞尔曲线的算法应用到画板中?
我们前面已经通过检测鼠标事件,收集到一堆的点。假设这些点有N个,那么贝塞尔曲线如何利用这些点呢?
- 程序推导出N-1阶贝塞尔曲线的公式,然后利用这个公式将曲线的所有点都画出来。
暂时只想出这种方法,但是如果程序需要自己推导这个公式其算法的复杂度将会是很高的,并且我们Qt不像Python那样有numpy来辅助你来完成这些数值分析工作。所以这种方法被我否决了。
我们能不能只利用二阶或者三阶的贝赛尔曲线公式来解决?如果可以,需要怎样做?
他人的解决方案
这里参考了别人的一篇文章:Qt桌面白板工具其一(解决曲线不平滑的问题——贝塞尔曲线)_qt贝塞尔曲线-CSDN博客
这里面主要描述了画板的两种实现方法:
(1)第一种是利用GraphicsView框架,通过采集点然后点间连线的方式形成图形项呈现在屏幕上。
但是这样做有一个问题,当我们要实现“橡皮擦”功能时,情况将会变得异常复杂,因为我们橡皮擦所过之处,会碰到一些图元如果是通过点来绘制的,那么他需要看看自己内部的点集合有没有与橡皮擦相交,如果点非常多,那么每次擦除都需要进行很大开销的查找操作。
(2)利用QImage间接绘制窗口
通过重写QWidget的paintEvent(),将鼠标事件映射到QImage中。这样其实是考虑了撤销的问题,撤销需要记录上一步的画面,与其记住繁杂的路径点位,不如直接记住上一次的QImage,并且限定10次。
这里文章的博主采用了第二种解决方案。我们暂且不论刚开始我们所做东西的优劣,这里应关注的是他如何使用贝塞尔曲线:
假设我们在鼠标移动的过程中有A、B、C、D、E、F、G这6个点。如何画出平滑的曲线呢?
我们取B点和C点的中点为B1。
作为第一条贝塞尔曲线的终点,B点为控制点:
接下来,算出C和D的终点C1,以C点为控制点,继续绘制:
当我们荣有一系列的点集时,从第二个点开始,该点和下一个点之间计算一个新的中点(直接相加除以2),然后添加进点集队列中。
这样,我们除了第一个点之外的其他实际点,都将作为控制点成为参数,而第一个点和我们手动计算出的中点,反而成为实际点与曲线相连。
制作好这个点集之后呢?该怎样绘制贝塞尔曲线,看不到代码实现我还是没有理解。
代码实现
直接把该文章的算法drawBezier()
移植过来用,最终效果如上所示。感觉没那么尖锐了,但仍然存在不足,通过调整QPen的Style可能有一定程度上的缓解。
标签:Qt,实现,曲线,贝塞尔,CustomItem,画图板,散点,newPoint,鼠标 From: https://www.cnblogs.com/Aderversa/p/18375933