找回密码
 立即注册
收起左侧

第18篇 2D绘图(八)双缓冲绘图

19
回复
28834
查看
[复制链接]
累计签到:882 天
连续签到:1 天
来源: 2013-5-2 22:07:23 显示全部楼层 |阅读模式
双缓冲绘图


版权声明

该文章原创于Qter开源社区(www.qter.org),作者yafeilinux,转载请注明出处!


导语

在前面一节中,讲述了如何实现简单的涂鸦板,这一次我们将实现在涂鸦板上绘制图形,这里以矩形为例进行讲解。在后面还会提出双缓冲绘图的概念。


环境:Windows Xp + Qt 4.8.4+QtCreator 2.6.2


目录

一、绘制矩形
二、双缓冲绘图


正文


一、绘制矩形

1.我们仍然在前面程序的基础上进行修改,先更改painEvent()函数:
void Dialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    int x,y,w,h;
    x = lastPoint.x();
    y = lastPoint.y();
    w = endPoint.x() - x;
    h = endPoint.y() - y;
    painter.drawRect(x, y, w, h);
}
       这里就是通过lastPointendPoint两个点来确定要绘制的矩形的起点、宽和高的。运行程序,用鼠标拖出一个矩形,效果如下图所示。



2. 上面已经可以拖出一个矩形了,但是这样直接在窗口上绘图,以前画的矩形是不能保存下来的。所以我们下面加入画布,在画布上进行绘图。将paintEvent()函数更改如下:
void Dialog::paintEvent(QPaintEvent *)
{
    int x,y,w,h;
    x = lastPoint.x();
    y = lastPoint.y();
    w = endPoint.x() - x;
    h = endPoint.y() - y;
    QPainter pp(&pix);
    pp.drawRect(x, y, w, h);
    QPainter painter(this);
    painter.drawPixmap(0, 0, pix);
}
       这里就是将图形先绘制在了画布上,然后将画布绘制到窗口上。我们运行程序,然后使用鼠标拖出一个矩形,发现出现了很多重影,效果如下图所示。


       为什么会出现这种现象呢?大家可以尝试分别快速拖动鼠标和慢速拖动鼠标来绘制矩形,结果会发现,拖动速度越快,重影越少。其实,在我们拖动鼠标的过程中,屏幕已经刷新了很多次,也可以理解为paintEvent()函数执行了多次,每执行一次就会绘制一个矩形。知道了原因,就有方法来避免这个问题发生了。


二、双缓冲绘图

1.我们再添加一个辅助画布,如果正在绘图,也就是鼠标按键还没有释放的时候,就在这个辅助画布上绘图,只有当鼠标按键释放的时候,才在真正的画布上绘图。
       首先在dialog.h文件中添加两个私有变量:

QPixmap tempPix; //辅助画布
bool isDrawing;   //标志是否正在绘图

       然后到dialog.cpp的构造函数中对变量进行初始化:

isDrawing = false;

下面再更改paintEvent()函数:
void Dialog::paintEvent(QPaintEvent *)
{
    int x,y,w,h;
    x = lastPoint.x();
    y = lastPoint.y();
    w = endPoint.x() - x;
    h = endPoint.y() - y;
    QPainter painter(this);
    if(isDrawing) //如果正在绘图,就在辅助画布上绘制
    {
       //将以前pix中的内容复制到tempPix中,保证以前的内容不消失
       tempPix = pix;
       QPainter pp(&tempPix);
       pp.drawRect(x,y,w,h);
       painter.drawPixmap(0, 0, tempPix);
    } else {
       QPainter pp(&pix);
       pp.drawRect(x,y,w,h);
       painter.drawPixmap(0,0,pix);
    }
}

       下面还需要更改鼠标按下事件处理函数和鼠标释放事件处理函数的内容:

void Dialog::mousePressEvent(QMouseEvent *event)
{
    if(event->button()==Qt::LeftButton) //鼠标左键按下
    {
       lastPoint = event->pos();
       isDrawing = true;   //正在绘图
    }
}
void Dialog::mouseReleaseEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton) //鼠标左键释放
    {
       endPoint = event->pos();
       isDrawing = false;    //结束绘图
       update();
    }
}
       当鼠标左键按下时我们开始标记正在绘图,当按键释放时我们取消正在绘图的标记。现在运行程序,已经可以实现正常的绘图了。效果如下图所示。


2.双缓冲绘图
       根据这个例子所使用的技巧,我们引出所谓的双缓冲绘图的概念。双缓冲(double-buffers)绘图,就是在进行绘制时,先将所有内容都绘制到一个绘图设备(如QPixmap)上,然后再将整个图像绘制到部件上显示出来。使用双缓冲绘图可以避免显示时的闪烁现象。从Qt 4.0开始,QWidget部件的所有绘制都自动使用了双缓冲,所以一般没有必要在paintEvent()函数中使用双缓冲代码来避免闪烁。
       虽然在一般的绘图中无需手动使用双缓冲绘图,不过要想实现一些绘图效果,还是要借助于双缓冲的概念。比如这个程序里,我们要实现使用鼠标在界面上绘制一个任意大小的矩形。这里需要两张画布,它们都是QPixmap实例,其中一个tempPix用来作为临时缓冲区,当鼠标正在拖动矩形进行绘制时,将内容先绘制到tempPix上,然后将tempPix绘制到界面上;而另一个pix作为缓冲区,用来保存已经完成的绘制。当松开鼠标完成矩形的绘制后,则将tempPix的内容复制到pix上。为了绘制时不显示拖影,而且保证以前绘制的内容不消失,那么在移动鼠标过程中,每绘制一次,都要在绘制这个矩形的原来的图像上进行绘制,所以需要在每次绘制tempPix之前,先将pix的内容复制到tempPix上。因为这里有两个QPixmap对象,也可以说有两个缓冲区,所以称之为双缓冲绘图。



结语

       对于Qt基本绘图的内容,我们就讲到这里,如果大家还想更加系统深入的学习这些基础知识,可以参考《Qt Creator快速入门》的第10章。从下一节开始,我们将简单介绍一下Qt中得图形视图框架。




涉及到的源码:



上一篇:    第17篇 2D绘图(七)涂鸦板

下一篇:    第19篇 2D绘图(九)图形视图框架(上)

返回:系列教程目录    


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

累计签到:1 天
连续签到:1 天
2013-5-2 23:20:39 显示全部楼层
最近版主更新好快呀,作为初学者我表示压力开始变大了,不过我会每一篇都认真去看,动手实践、体会。

点评

恩,其实后面很多篇已经写好了,只是还没有发布出来!如果是刚开始学,要循序渐进,把基础打好,这样后面才可以越学越快!  发表于 2013-5-3 08:47
回复 支持 反对

使用道具 举报

累计签到:7 天
连续签到:1 天
2014-6-4 15:48:55 显示全部楼层
请问一下这句话
QPainter pp(&tempPix);
意思是说:
新建一个painter并且在temppix里面绘图,如果不指定绘图地点也就是QPainter pp;那么就在主界面上绘制
请问是这个意思嘛?
回复 支持 反对

使用道具 举报

累计签到:882 天
连续签到:1 天
2014-7-2 22:58:59 显示全部楼层
卿可津 发表于 2014-6-4 15:48
请问一下这句话
QPainter pp(&tempPix);
意思是说:

恩,是的。
回复 支持 反对

使用道具 举报

尚未签到

2014-8-5 21:25:49 显示全部楼层
Good! 於 Arch Linux 測試無誤.
回复 支持 反对

使用道具 举报

累计签到:1 天
连续签到:1 天
2014-8-7 11:45:17 显示全部楼层
为什么windows下不正常运行呢
回复 支持 反对

使用道具 举报

累计签到:1 天
连续签到:1 天
2014-8-7 11:47:06 显示全部楼层
额,我搞错了                       
回复 支持 反对

使用道具 举报

尚未签到

2015-3-28 15:27:16 显示全部楼层
楼主,您好。
请教一个问题,下面代码,我是这么理解的,我认为还是会出现重影才对,因为鼠标在移动,paintEvent也一直在执行这样在辅助画布上还是会出现重影吧,只不过在鼠标释放后主画布会覆盖掉辅助画布。

下面再更改paintEvent()函数:
void Dialog::paintEvent(QPaintEvent *)
{
    int x,y,w,h;
    x = lastPoint.x();
    y = lastPoint.y();
    w = endPoint.x() - x;
    h = endPoint.y() - y;
    QPainter painter(this);
    if(isDrawing) //如果正在绘图,就在辅助画布上绘制
    {
       //将以前pix中的内容复制到tempPix中,保证以前的内容不消失
       tempPix = pix;
       QPainter pp(&tempPix);
       pp.drawRect(x,y,w,h);
       painter.drawPixmap(0, 0, tempPix);
    } else {
       QPainter pp(&pix);
       pp.drawRect(x,y,w,h);
       painter.drawPixmap(0,0,pix);
    }
}
回复 支持 反对

使用道具 举报

累计签到:882 天
连续签到:1 天
2015-3-30 16:19:26 显示全部楼层
zhangxiang 发表于 2015-3-28 15:27
楼主,您好。
请教一个问题,下面代码,我是这么理解的,我认为还是会出现重影才对,因为鼠标在移动,paint ...

在论坛下载页面有个涂鸦板程序,你可以参考下。
回复 支持 反对

使用道具 举报

尚未签到

2015-4-29 20:10:17 显示全部楼层
本帖最后由 breezefu 于 2015-4-29 20:17 编辑

以前没有接触过双缓冲绘图的概念,初次看到楼主给的下面代码,有些不理解:
  1.     if(isDrawing) //如果正在绘图,就在辅助画布上绘制
  2.     {
  3.        //将以前pix中的内容复制到tempPix中,保证以前的内容不消失
  4.        tempPix = pix;
  5.        QPainter pp(&tempPix);
  6.        pp.drawRect(x,y,w,h);
  7.        painter.drawPixmap(0, 0, tempPix);
  8.     } else {
  9.        QPainter pp(&pix);
  10.        pp.drawRect(x,y,w,h);
  11.        painter.drawPixmap(0,0,pix);
  12.     }
复制代码
其中最让我疑惑的地方是,为什么这样处理后就不会出现重影问题了?
经过注释掉部分代码,调试了几次我才总算搞明白,这里记录一下,希望对其他人理解有所帮助。

这里有两个画布tempPix和pix,在鼠标处于按下和松开两种状态时,分别负责绘制图形,保证窗口中不出现空白的情况。
实际绘制操作是下面两条语句完成的:
  1.    painter.drawPixmap(0, 0, tempPix); // 鼠标按下时,绘制画布tempPix中的内容
  2.    painter.drawPixmap(0, 0, pix); // 鼠标松开时,绘制画布pix中的内容
复制代码
绘制操作很简单,关键是要画什么。这几行代码中最关键的是下面这条语句:
  1.    tempPix = pix;
复制代码
这条语句除了楼主说的“将以前pix中的内容复制到tempPix中,保证以前的内容不消失”这个作用外,还有另一个作用,就是在鼠标按下过程中,不断的用pix中旧的内容覆盖擦除tempPix中的内容,这样就不会出现重影的问题了。也就是说,在鼠标按下时,每次执行了 tempPix = pix; 这条语句后,tempPix中的内容都恢复成上次在鼠标松开状态时pix中绘制的内容,然后再接着根据tempPix创建Qpainter对象,执行drawRect操作后,tempPix中就只保存了这次鼠标按下前原来pix中绘制的图形和鼠标按下后最近这次drawRect操作绘制的最新图形。

为了起到上面说的两个作用,在鼠标按下过程中,每次刷新界面执行paintEvent操作时都要用pix对tempPix赋值,所以其实tempPix只要定义成局部变量就可以了。
另外,如果单说这个示例的实现,在鼠标松开后,pix都是自己执行drawRect操作,绘制最新的矩形的,并没有像楼主说的将tempPix的内容复制到pix上。

点评

tempPix = pix;//恢复原先 painter.drawPixmap(0, 0, tempPix);//画 在鼠标移动过程中,两个语句交替的执行,既可以看到矩形跟随鼠标,又不会产生重影。  发表于 2015-9-22 20:59
理解的很透彻,分析的也很有道理~  发表于 2015-9-22 20:46
回复 支持 反对

使用道具 举报

累计签到:9 天
连续签到:1 天
2015-6-9 17:04:47 显示全部楼层
问一下,我按照您的涂鸦板完整程序写的,为什么我的只能画直线,而且坐标也不十分重合。
回复 支持 反对

使用道具 举报

累计签到:12 天
连续签到:1 天
2015-11-24 11:28:35 显示全部楼层
zhangxiang 发表于 2015-3-28 15:27
楼主,您好。
请教一个问题,下面代码,我是这么理解的,我认为还是会出现重影才对,因为鼠标在移动,paint ...

10楼
有真相。。。。。。。。。。。。。。。。。。。。。。。。。。。
回复 支持 反对

使用道具 举报

累计签到:3 天
连续签到:1 天
2016-7-14 09:06:34 显示全部楼层
您好,使用Qt有一段时间了。不知道您使用过Qt5.7里面的Qt3D模块没有。现在遇到一些问题:关闭主程序的时候会中断,提示Qt3DCored.dll的问题,0x0000005。不知道Qt3D模块能不能画直线和多边形,包括凹多边形。谢谢
回复 支持 反对

使用道具 举报

累计签到:1 天
连续签到:1 天
2016-7-14 20:59:48 显示全部楼层
good!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
回复 支持 反对

使用道具 举报

累计签到:20 天
连续签到:1 天
2016-9-4 14:40:37 显示全部楼层
breezefu 发表于 2015-4-29 20:10
以前没有接触过双缓冲绘图的概念,初次看到楼主给的下面代码,有些不理解:其中最让我疑惑的地方是,为什么 ...

层主,我还是不太理解关于重影的解决和能够实时刷新当前画面的问题,虽然有tempPix=pix,但是每次应该只会覆盖上次鼠标释放的时候pix的图像才对啊,那么在tempPix上拖动鼠标画的时候感觉应该不会图像一直刷新才对,因为只要你的鼠标一直按住不放,pix的内容永远是上次鼠标释放时候的图片才对呀!但实际结果却是一直再刷新,不是太理解
回复 支持 反对

使用道具 举报

累计签到:1 天
连续签到:1 天
2016-11-7 13:52:08 显示全部楼层
楼主,我按照您的方法做的,可是依旧重影是怎么回事呀,求教
回复 支持 反对

使用道具 举报

累计签到:882 天
连续签到:1 天
2016-11-7 14:46:53 显示全部楼层
Qt小菜鸟 发表于 2016-11-7 13:52
楼主,我按照您的方法做的,可是依旧重影是怎么回事呀,求教

关键是搞明白鼠标事件中的处理方法。
回复 支持 反对

使用道具 举报

累计签到:882 天
连续签到:1 天
2016-11-7 14:47:25 显示全部楼层
倔强的蜗牛 发表于 2016-9-4 14:40
层主,我还是不太理解关于重影的解决和能够实时刷新当前画面的问题,虽然有tempPix=pix,但是每次应该只 ...

研究代码,根据实际效果来理解。
回复 支持 反对

使用道具 举报

累计签到:3 天
连续签到:1 天
2017-4-9 11:42:43 显示全部楼层
倔强的蜗牛 发表于 2016-9-4 14:40
层主,我还是不太理解关于重影的解决和能够实时刷新当前画面的问题,虽然有tempPix=pix,但是每次应该只 ...

赋值拷贝之后,还有一个drawRect啊,那个是根据鼠标当前位置绘制的新矩形。
回复 支持 反对

使用道具 举报

尚未签到

2018-5-4 20:49:27 显示全部楼层
非常感谢有这么好的例程
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册