[Qt2D绘图]-06QPainter的复合模式双缓冲绘图绘图中的其他问题本篇读书笔记主要记录QPainter的复合模式&&双缓冲绘图&&绘图中的其他问题
⼤纲:
复合模式
双缓冲绘图
绘图中的其他问题
重绘事件
剪切
读⼊和写⼊图像
播放GIF
渲染SVG
复合模式
QPainter提供了复合模式(Composition Modes)来定义如何完成数字图像的复合,即如何将源图像的像素和⽬标图像的像素进⾏合并。QPainter 提供的常⽤复合模式及其效果如下⾯截图所⽰,所有的复合模式可以在QPainter的帮助⽂档中进⾏查看。
最普通的类型是SourceOver(不设置的话默认是这个)(通常被称为alpha混合),就是正在绘制的源像素混合在已经绘制的⽬标像素上,源像素的alpha分量定义了它的透明度,这样源图像就会以透明效果在⽬标图像上进⾏显⽰。
若绘图设备是QImage,图像的格式⼀定要指定为QImage::Format_ARGB32_Premultiplied或者Format_ARGB32,不然复合模式就不会产⽣任何效果。
当设置了复合模式,它就会应⽤到所有的绘图操作中,如画笔、画刷、渐变和pixmap/image绘制等。
演⽰书上的代码:
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter;
QImage image(400,300,QImage::Format_ARGB32_Premultiplied);
// 使⽤绘图设备,绘制到绘图设备上
painter.begin(&image);
// 绘制⼀个矩形
painter.setBrush(Qt::green);
painter.drawRect(100,50,200,200);
//在四个⾓分别绘制⼀个矩形,使⽤不同的复合模式(composition)
painter.setBrush(QColor(0,0,255,150));
//composition没有设置则使⽤默认的SourceOver
painter.drawRect(50,0,100,100);
/
/QPainter::CompositionMode_SourceIn
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
painter.drawRect(250,0,100,100);
//QPainter::CompositionMode_DestinationOver
painter.setCompositionMode(QPainter::CompositionMode_DestinationOver);
painter.drawRect(50,200,100,100);
//QPainter::CompositionMode_Xor
painter.setCompositionMode(QPainter::CompositionMode_Xor);
painter.drawRect(250,200,100,100);
// 绘制到当前部件(当前绘图设备是QWidget的⼦类,也就是部件)
painter.begin(this);
painter.drawImage(0,0,image);
}
效果图:
双缓冲绘图
所谓双缓冲(double-buffers)绘图,就是在进⾏绘制时,先将所有内容都绘制到⼀个绘图设备(如QPixmap)上,然后再将整个图像绘制到部件上显⽰出来。
使⽤双缓冲绘图可以避免显⽰时的闪烁现象。
从Qt4.0开始,QWidget部件的所有绘制都⾃动使⽤了双缓冲,所以⼀般没有必要在paintEvent()函数中使⽤双缓冲代码来避免闪烁。
虽然在⼀般的绘图中⽆须⼿动使⽤双缓冲绘图,不过要想实现⼀些绘图效果,还是要借助于双缓冲的概念。
下⾯的程序实现使⽤⿏标在界⾯上绘制⼀个任意⼤⼩的矩形的功能。
这⾥需要两张画布,它们都是QPixmap实例。
其中⼀个tempPix⽤来作为临时缓冲区,当⿏标正在拖动矩形进⾏绘制时,将内容先绘制到tempPix上,然后将tempPix绘制到界⾯上;
⽽另⼀个pix作为缓冲区,⽤来保存已经完成的绘制。当松开⿏标完成矩形的绘制后,则将tempPix的内容复制到pix上。
为了绘制时不显⽰拖影,在移动⿏标过程中,每绘制⼀次都要在刚开始绘制这个矩形的图像上进⾏绘制,所以需要在每次绘制tempPix之前,先将pix的内容复制到tempPix上。
#include "widget.h"
#include "ui_widget.h"
#include <QMouseEvent>
查看svg#include <QPainter>
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) {
ui->setupUi(this);
pix_buffer_ = QPixmap(400, 300);
pix_buffer_.fill(Qt::white);
temp_pix_buffer_ = pix_buffer_;
is_drawing_ = false;
}
Widget::~Widget() { delete ui; }
void Widget::mousePressEvent(QMouseEvent *event) {
if(event->button() == Qt::LeftButton){
//当⿏标左键按下时获取当前位置作为矩形的开始点
start_point_ = event->pos();
is_drawing_ = true;
}
}
void Widget::mouseMoveEvent(QMouseEvent *event) {
if(event->button() & Qt::LeftButton){
//当按着⿏标左键进⾏移动时,获取当前位置作为结束点,绘制矩形
end_point_ = event->pos();
//将缓冲区的内容复制到临时缓冲区,这样进⾏动态绘制时
//每次都是在缓冲区图像的基上进⾏绘制,就不会产⽣拖影现象了
temp_pix_buffer_ = pix_buffer_;
/
/ 更新显⽰
update();
}
}
void Widget::mouseReleaseEvent(QMouseEvent *event) {
if(event->button()== Qt::LeftButton){
//当⿏标左键松开时,获取当前位置为结束点,完成矩形绘制
end_point_ = event->pos();
//标记已经结束绘图
is_drawing_ = false;
update();
}
}
void Widget::paintEvent(QPaintEvent *) {
int x = start_point_.x();
int y = start_point_.y();
int width = end_point_.x();
int height = end_point_.y();
QPainter painter;
painter.setPen(QColor(Qt::red));
painter.begin(&temp_pix_buffer_);
painter.drawRect(x,y,width,height);
painter.begin(this);
painter.drawPixmap(0,0,temp_pix_buffer_);
//如果已经完成了绘制,那么更新缓冲区
if(!is_drawing_){
pix_buffer_ = temp_pix_buffer_;
}
}
这⾥先在临时缓冲区中进⾏绘图,然后将其绘制到界⾯上。最后判断是否已经完成了绘制,如果是,则将临时缓冲区中的内容复制到缓冲区中,这样就完成了整个矩形的绘制。这个例⼦中的关键是pix和tempPix的相互复制,如果想将这个程序进⾏扩展,可以查看⼀下⽹站上的涂鸦板
序。
与这个例⼦很相似的⼀个应⽤是橡⽪筋线,就是我们在Windows桌⾯上拖动⿏标出现的橡⽪筋选择框。Qt中提供了QRubberBand 类来实现橡⽪筋线,使⽤它只需要在⼏个⿏标事件处理函数中进⾏设置即可,具体应⽤可以查看该类的帮助⽂档。
绘图中的其他问题
重绘事件 paintEvent(QPaintEvent *event)override; protected
前⾯讲到的所有绘制操作都是在重绘事件处理函数paintEvent()中完成的,它是QWidget类中定义的函数。也就意味着QtWidgets都是绘图设备⼀个重绘事件⽤来重绘⼀个部件的全部或者部分区域,下⾯⼏个原因中的任意⼀个都会发⽣重绘事件:
repaint()函数或者update()函数被调⽤;
被隐藏的部件现在被重新显⽰;
other
⼤部分部件可以简单地重绘它们的全部界⾯,但是⼀些绘制⽐较慢的部件需要进⾏优化⽽只绘制需要的区域(可以使⽤QPaintEvent::region()来获取该区域),这种速度上的优化不会影响结果。
Qt也会通过合并多个重绘事件为⼀个事件来加快绘制,当update()函数被调⽤多次,或者窗⼝系统发送了多个重绘事件时,那么Qt就会合并这些事件成为⼀个事件,⽽这个事件拥有最⼤的需要重绘的区域。
update()函数不会⽴即进⾏重绘,要等到Qt返回主事件循环后才会进⾏,所以多次调⽤update()函数⼀般只会引起⼀次paintEvent()函数调⽤。
但是调⽤repaint()函数会⽴即调⽤paintEvent()函数来重绘部件,只有在必须⽴即进⾏重绘操作的情况下(⽐如在动画中),才使⽤repaint()函数。update()允许Qt优化速度和减少闪烁,但是repaint()函数不⽀持这样的优化,所以建议⼀般情况下尽可能使⽤update()函数。
还要说明⼀下,在程序开始运⾏时就会⾃动发送重绘事件⽽调⽤paintEvent()函数。另外,不要在paintEvent()函数中调⽤update( )或者repaint()函数。
当重绘事件发⽣时,要更新的区域⼀般会被擦除,然后在部件的背景上进⾏绘制。
部件的背景⼀般可以使⽤setBackgroundRole( )来指定,然后使⽤setAutoFillBackground(true)来启⽤指定的颜⾊。
例如,使界⾯显⽰⽐较深的颜⾊,则可以在部件的构造函数中添加如下代码:
setBackgroundRole(QPalette::Dark);
setAutoFillBackgroud(true);
剪切
QPainter可以剪切任何的绘制操作,它可以剪切⼀个矩形、⼀个区域或者⼀个路径中的内容,
这分别可以使⽤setClipRect()、setClipRegion()和setClipPath()函数来实现。
剪切会在QPainter的逻辑坐标系统中进⾏。下⾯的代码实现了剪切⼀个矩形中的⽂字:
QPainter painter(this);
// 剪切矩形中的内容
painter.setClipRect(10,0,20,10);
painter.drawText(10,10,"tudouTestTestTest");
读⼊和写⼊图像
要读取图像,最普通的⽅法是使⽤QImage或者QPixmap的构造函数,或者调⽤Qlmage: :load()和QPixmap::load()函数。
Qt中还有⼀个QImageReader类,该类提供了⼀个格式⽆关的接⼝,可以从⽂件或者其他设备中读取图像。
QImageReader 类可以在读取图像时提供更多的控制,例如,可以使⽤setScaledSize()函数将图像以指定的⼤⼩进⾏读取,还可以使⽤setClipRect()读取图像的⼀个区域。
由于依赖于图像格式底层的⽀持,QImageReader的这些操作可以节省内存和加快图像的读取。
另外,Qt还提供了QImageWriter类来存储图像,它⽀持设置图像格式的特定选项,⽐如伽玛等级、压缩等级和品质等。
当然,如果不需要设置这些选项,那么可以直接使⽤Qlmage: :save()和QPixmap::save( )函数。
播放GIF
QMovie类是使⽤QImageReader来播放动画的便捷类,使⽤它可以播放不带声⾳的简单的动画,⽐如gif⽂件格式。
这个类提供了很⽅便的函数来进⾏动画的开始、暂停和停⽌等操作。
渲染SVG
可缩放⽮量图形( Scalable Vector Graphics,SVG)是⼀个使⽤XML来描述⼆维图形和图形应⽤程序的语⾔。
在Qt中可以使⽤QSvgWidget类加载⼀个SVG⽂件,⽽使⽤QSvgRenderer类在QSvgWidget中进⾏SVG⽂件的渲染。
这两个类的使⽤很简单,可以参考SVG Generator Example和SVG Viewer Example⽰例程序。
Qt参考⽰例和⽂档
⽂档:
⽰例:Image Composition Example , Composition Modes
本篇源代码
github/tudouloveloli/QtExampleCode
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论