Qt 鼠标/触屏绘制平滑曲线(支持矢量/非矢量方式)

所属分类: 软件编程 / C 语言 阅读数: 103
收藏 0 赞 0 分享

前言

Qt通过鼠标或者触屏,实时绘制平滑曲线,通常有两种方式实现:矢量绘图和非矢量绘图,这两种画线方式从实现上有些不同,其原理也不太一样,稍后会做详细介绍。而鼠标或者触屏画线也不大一样,通常如果只实现鼠标画线的话,那么只需要重新实现鼠标事件即可(mousePressEvent、mouseMoveEvent、mouseReleaseEvent),而要在触控屏上画线,如果需要支持多点画线的话,就必须处理QTouchEvent事件才行,但是如果触屏上只支持单点画线,那也可以直接实现鼠标事件,因为第一个触点的事件会同时进入到QTouchEvent和Mouse事件中。QTouchEvent中可以区分出多点时每个触点的id,通过id进行区分每个点的数据。

通常情况下,为了提升绘图效率,要实现这种绘图的功能,都是用QGraphics体系来完成,因为QGraphics刷新机制和QWidget不太一样,它可以做区域刷新,这样能保证效率更高,特别是针对一些分辨率较高的设备,就很明显了。具体这两个体系间的区别就不在这里进行描述。

所以,接下来为了演示矢量和非矢量画图方式,我们在QGraphics体系中实现一个简单的画板程序。注重画线效率,保证线条平滑无折线,无锯齿,支持多点画线。

效果图

先开看看非矢量绘图的效果:

再看矢量绘图效果:

二者区别

通过上面的两个图对比,相信大家已经看出了一些区别。我们再详细介绍一下这两者的区别。

非矢量绘图

  • 优点:速度快。非矢量绘图原理是直接在一张图片上进行绘制,其渲染速度很快,即便是画了很多线条, 也不会有卡顿的效果,擦除时同样很快。
  • 缺点:缩放失真。由于非矢量绘图是在图片上渲染,当缩放图片时,会导致线条模糊不清晰,如果只是小范围的缩放还能接受,无限缩放的话就会很明显了。

矢量绘图

  • 优点:无限缩放,不失真。矢量绘图是将点数据绘制生成一个单独的对象,当进行缩放的时候,会重新进行渲染,所以矢量绘图的方式不会导致图像失真。
  • 缺点:线条多时会卡顿,擦除尤其明显。由于矢量绘图是生成一个单独的对象,所以当画线多的情况下,会触发所有有交集的对象进行刷新,擦除的时候,会去计算线条之间的交集并做删减,这个过程会很慢,并且会将整个对象item进行刷新,所以卡顿明显(上述效果图就可以看出来了)。

通过以上两者的优缺点对比,根据实际需要进行选择实际的画线模式。

解决实时绘图折线问题

折线效果:


可以看到上述画线有很明显的折线,线条不平滑。

通常绘制这种线条,第一反应想到的是讲两个点直接连接起来行成一条直线,但是,由于两点之间距离比较大,特别是触控屏,点与点之间并不是很密集,因为上层应用在主线程渲染的时候,系统会自动丢弃一些数据点,即便是底层上报的点很多,上层应用接收到的点也会减少,所以不能直接用连接两点的方式来实现。

那么,该怎么解决呢?
绘制贝塞尔曲线。

在move的过程中实时生成贝塞尔曲线path,这样就能保证线条无折线。QPainterPath支持贝塞尔曲线绘制,参加以下函数:

void QPainterPath::quadTo(const QPointF &c, const QPointF &endPoint)

Adds a quadratic Bezier curve between the current position and the given endPoint with the control point specified by c.
After the curve is added, the current point is updated to be at the end point of the curve.

注意该函数,第一个参数是控制点,该点就是上一个触控点,而第二个参数是前一个点和当前点的中点,也就是两个点坐标加起来除以2.

非矢量绘图实现方式

所谓的非矢量绘图,就是在一张图片上进行绘制,然后将图片渲染到QGraphicsItem的背景上面,前面我们已经提到,该方式渲染速度非常快,无论画多少线条都不会影响速度,而擦除功能只需要按照同样的方式绘制背景色即可。

但是该方式在缩放过后图片会有些模糊,如果只是小范围的缩放还好,无限缩放就需要用到矢量绘图的方式了。

矢量绘图实现方式

相比之下,矢量绘图就会稍微麻烦一点,所谓矢量绘图,就是将path曲线直接生成一个独立的对象,将该对象添加到scene中,这种模式下会有一个缺陷,就是当画线较多的情况下,刷新会比较慢,因为会导致整条曲线(只要有交集)刷新,从而导致卡顿的效果,并且在擦除时,需要实时计算擦除的path与实际线条path的交集,然后进行计算,减去擦除的path,这个过程是最耗时的,并且也会引发整个item刷新。前面写过文章介绍QGraphics体系的刷新机制,在这里

由于矢量绘图需要生成一条完整的path进行绘制,而触控点是在move事件中取到,如果实时生成贝塞尔曲线去绘制,那么当一直不松手的画一条线时,画到后面将会越来越慢,因为动态生成path后会重新将整条path进行渲染,随着线条越长,那么刷新区域就会越大,这就会导致刷新变慢而延迟变高。那么怎么解决这个问题呢?答案就是通过双缓冲的方式来实现绘制。

双缓冲绘图

上面介绍到,通过非矢量绘图的方式,速度会非常快,那么双缓冲绘图就是要结合非矢量来进行,其原理就是:在press事件中生成一条path,接着move中动态增加这条path,然后在临时层上进行非矢量绘图,这时候绘制的速度会非常快,最后在release事件中将完整的path绘制成矢量图,然后将临时层画线清空。基本原理就是这样。

双缓冲绘图方式,在绘制过程中是通过非矢量的方式在临时层进行,release后生成完整的矢量path,这种方式速度会非常快,并且直接绘制完整的一条path不会有锯齿。所以这是最佳选择。

代码太多,就不附代码了。

更多精彩内容其他人还在看

用标准c++实现string与各种类型之间的转换

这个类在头文件中定义, < sstream>库定义了三种类:istringstream、ostringstream和stringstream,分别用来进行流的输入、输出和输入输出操作。另外,每个类都有一个对应的宽字符集版本
收藏 0 赞 0 分享

C++如何通过ostringstream实现任意类型转string

再使用整型转string的时候感觉有点棘手,因为itoa不是标准C里面的,而且即便是有itoa,其他类型转string不是很方便。后来去网上找了一下,发现有一个好方法
收藏 0 赞 0 分享

C/C++指针小结

要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区
收藏 0 赞 0 分享

C++ 类的静态成员深入解析

在C++中类的静态成员变量和静态成员函数是个容易出错的地方,本文先通过几个例子来总结静态成员变量和成员函数使用规则,再给出一个实例来加深印象
收藏 0 赞 0 分享

C++类的静态成员初始化详细讲解

通常静态数据成员在类声明中声明,在包含类方法的文件中初始化.初始化时使用作用域操作符来指出静态成员所属的类.但如果静态成员是整型或是枚举型const,则可以在类声明中初始化
收藏 0 赞 0 分享

C++类静态成员与类静态成员函数详解

静态成员不可在类体内进行赋值,因为它是被所有该类的对象所共享的。你在一个对象里给它赋值,其他对象里的该成员也会发生变化。为了避免混乱,所以不可在类体内进行赋值
收藏 0 赞 0 分享

C++中的friend友元函数详细解析

友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类。友元函数的特点是能够访问类中的私有成员的非成员函数。友元函数从语法上看,它与普通函数一样,即在定义上和调用上与普通函数一样
收藏 0 赞 0 分享

static全局变量与普通的全局变量的区别详细解析

以下是对static全局变量与普通的全局变量的区别进行了详细的分析介绍,需要的朋友可以过来参考下,希望对大家有所帮助
收藏 0 赞 0 分享

C++ explicit关键字的应用方法详细讲解

C++ explicit关键字用来修饰类的构造函数,表明该构造函数是显式的,既然有"显式"那么必然就有"隐式",那么什么是显示而什么又是隐式的呢?下面就让我们一起来看看这方面的知识吧
收藏 0 赞 0 分享

教你5分钟轻松搞定内存字节对齐

随便google一下,人家就可以跟你解释的,一大堆的道理,我们没怎么多时间,讨论为何要对齐.直入主题,怎么判断内存对齐规则,sizeof的结果怎么来的,请牢记以下3条原则
收藏 0 赞 0 分享
查看更多