QT—双缓冲绘图

x33g5p2x  于2022-07-04 转载在 其他  
字(2.5k)|赞(0)|评价(0)|浏览(468)

所谓双缓冲(double-buffers)绘图,就是在进行绘制时,先将所有内容都绘制到一个绘图设备(如 QPixmap)上,然后再将整个图像绘制到部件上显示出来

使用双缓冲绘图可以避免显示时的闪烁现象
从Qt 4.0开始,QWidget部件的所有绘制都自动使用了双缓冲,所以一般没有必要在 paintEvent()函数中使用双缓冲代码来避免闪烁。

虽然在一般的绘图中无须手动使用双缓冲绘图,不过要想实现一些绘图效果,还是要借助于双缓冲的概念。

下面的程序实现使用鼠标在界面上绘制一个任意大小的矩形的功能

这里需要两张画布,它们都是QPixmap实例,其中一个tempPix用来作为临时缓冲区,当鼠标正在拖动矩形进行绘制时,将内容先绘制到tempPix上,然后将tempPix绘制到界面上;而另一个pix作为缓冲区,用来保存已经完成的绘制

当松开鼠标完成矩形的绘制后,则将tempPix的内容复制到pix上。

为了绘制时不显示拖影,在移动鼠标过程中,每绘制一次都要在刚开始绘制这个矩形的图像上进行绘制,所以需要在每次绘制tempPix之前,先将pix的内容复制到tempPix 上

新建Qt Widgets应用,项目名称为mydoublebuffers,基类选择QWidget,类名为 Widget。
建立完成后,在 widget.h文件中添加如下内容:

  1. private:
  2. Ui::Widget *ui;
  3. // 缓冲区
  4. QPixmap pix;
  5. // 临时缓冲区
  6. QPixmap tempPix;
  7. QPoint startPoint;
  8. QPoint endPoint;
  9. // 是否正在绘图的标志
  10. bool isDrawing;
  11. protected:
  12. void mousePressEvent(QMouseEvent *event);
  13. void mouseMoveEvent(QMouseEvent *event);
  14. void mouseReleaseEvent(QMouseEvent *event);
  15. void paintEvent(QPaintEvent *event);

在这里定义了方法和绘画设备等
然后到widget.cpp文件中,先添加头文件

  1. #include <QMouseEvent>
  2. #include <QPainter>

然后到构造函数里面添加一些变量并初始化

  1. pix = QPixmap(400, 300);
  2. pix.fill(Qt::white);
  3. tempPix = pix;
  4. isDrawing = false;

pix是400,300尺寸的QPixmap设备,白色,复制给tempPix防止重影,isDrawing 关闭

下面是鼠标事件处理函数:

  1. void Widget::mousePressEvent(QMouseEvent *event)
  2. {
  3. if(event->button() == Qt::LeftButton) {
  4. // 当鼠标左键按下时获取当前位置作为矩形的开始点
  5. startPoint = event->pos();
  6. // 标记正在绘图
  7. isDrawing = true;
  8. }
  9. }
  10. void Widget::mouseMoveEvent(QMouseEvent *event)
  11. {
  12. if(event->buttons() & Qt::LeftButton) {
  13. // 当按着鼠标左键进行移动时,获取当前位置作为结束点,绘制矩形
  14. endPoint = event->pos();
  15. // 将缓冲区的内容复制到临时缓冲区,这样进行动态绘制时,
  16. // 每次都是在缓冲区图像的基上进行绘制,就不会产生拖影现象了
  17. tempPix = pix;
  18. // 更新显示
  19. update();
  20. }
  21. }
  22. void Widget::mouseReleaseEvent(QMouseEvent *event)
  23. {
  24. if(event->button() == Qt::LeftButton) {
  25. // 当鼠标左键松开时,获取当前位置为结束点,完成矩形绘制
  26. endPoint = event->pos();
  27. // 标记已经结束绘图
  28. isDrawing = false;
  29. update();
  30. }
  31. }

这里在鼠标按下事件处理函数中获取了要绘制矩形左上角的位置,然后标记正在绘制矩形

在鼠标移动事件处理函数中获取了要绘制矩形的右下角的位置,然后动态绘制矩形,这里为了不会绘制出一堆小矩形而产生所谓的拖影现象,就要在绘制临时缓冲区前,将缓冲区的内容复制到临时缓冲区中。这样每次都是在缓冲区图像的基础上进行绘制的,所以不会产生拖影现象

最后在鼠标按键释放事件处理函数中,获取矩形的右下角坐标,标记已经结束绘制。

下面添加重绘事件处理函数的定义:

  1. void Widget::paintEvent(QPaintEvent *event)
  2. {
  3. int x = startPoint.x();
  4. int y = startPoint.y();
  5. int width = endPoint.x() - x;
  6. int height = endPoint.y() - y;
  7. QPainter painter;
  8. painter.begin(&tempPix);
  9. painter.drawRect(x, y, width, height);
  10. painter.end();
  11. painter.begin(this);
  12. painter.drawPixmap(0, 0, tempPix);
  13. // 如果已经完成了绘制,那么更新缓冲区
  14. if(!isDrawing)
  15. pix = tempPix;
  16. }

这里先在临时缓冲区tempPix中进行绘图,然后将其绘制到界面上。

最后判断是否已经完成了绘制,如果是,则将临时缓冲区中的内容复制到缓冲区中,这样就完成了整个矩形的绘制

这个例子中的关键是pix和 tempPix的相互复制,如果想将这个程序进行扩展,可以查看一下网站上的涂鸦板程序。

与这个例子很相似的一个应用是橡皮筋线,就是我们在Windows桌面上拖动鼠标出现的橡皮筋选择框。Qt中提供了QRubberBand类来实现橡皮筋线,使用它只需要在几个鼠标事件处理函数中进行设置即可,具体应用可以查看该类的帮助文档。

相关文章