Qt知识回顾(九)——2D绘画

  Qt中提供了强大的2D绘图系统,可以使用相同的API在屏幕和绘图设备上进行绘制,主要基于QPainter、QPaintDevice和QPaintEngine这3个类。其中,QPainter用来执行绘图操作。QPaintDevice提供绘图设备,是一个二维空间的抽象,可以使用QPainter在其上进行绘制;是所有可以进行绘制的对象的基类,它的子类主要有QWidget、QPixmap、QPicture、QImage、QPrinter和QOpenGLPaintDevice等。QPaintEngine提供了一些接口,用于QPainter和QPaintDevice内部,使得QPainter可以在不同的设备上进行绘制;除了创建自定义的绘图设备类型,一般编程中不需要使用该类。
在这里插入图片描述

基本绘制和填充

  绘图系统中由QPainter完成具体的绘制操作,其中,提供了大量高度优化的函数来完成GUI编程所需要的大部分绘制工作。QPainter可以绘制一切想要的图形,从最简单的一条直线到其他任何复杂的图形,还可以绘制文本和图片。QPainter可以在继承自QPaintDevice类的任何对象上进行绘制操作。
  QPainter一般在一个部件重绘时间的处理函数paintEvent()中进行绘制,首先要创建QPainter对象,然后进行图形的绘制,最后销毁QPainter对象。

基本图像的绘制和填充

1.绘制图形
绘制简单的线条:

void widget::paintEvent(QPaintEvent *)
{
   QPainter painter(this);
   painter.drawLine(QPoint(0,0),QPoint(100,100));
   //等价于另一种形式
//   QPainter painter;
//   painter.begin(this);
//   painter.drawLine(QPoint(0,0),QPoint(100,100));
//   painter.end();
}

除了绘制简单的线条以外,QPainter还提供了一些绘制其他常用图形的函数,其中最常见的如下表:

函数功能函数功能
drawArc()绘制圆弧drawPoint()绘制点
drawChord()绘制弦drawPolygon()绘制多边形
drawConvexPolygon绘制凸多边形drawPolyline()绘制折线
drawEllipse()绘制椭圆drawRect()绘制矩形
drawLine()绘制线条drawRoundedRect()绘制圆角矩形
drawPie()绘制扇形

2.使用画笔

void widget::paintEvent(QPaintEvent *)
{
   QPainter painter(this);
   painter.drawLine(QPoint(0,0),QPoint(100,100));
   //等价于另一种形式
//   QPainter painter;
//   painter.begin(this);
//   painter.drawLine(QPoint(0,0),QPoint(100,100));
//   painter.end();
     //创建画笔
     QPen pen(Qt::green,5,Qt::DotLine,Qt::RoundCap,Qt::RoundJoin);
     //使用画笔
     painter.setPen(pen);
     QRectF rectangle(70.0,40.0,80.0,60.0);
     int startAngle = 30*16int spanAngle = 120*16;
     painter.drawArc(rectangle,startAngle,spanAngle);
}

  QPen类为QPainter提供了画笔来绘制线条和形状的轮廓,这里使用的构造函数为QPen::QPen(const QBrush &brush, qreal width, Qt::PenStyle style = Qt::SolidLine, Qt::PenCapStyle cap = Qt::SquareCap, Qt::PenJoinStyle join = Qt::BevelJoin);几个参数依次为画笔使用的画刷、线宽、画笔风格、画笔端点风格和画笔连接风格,也可以使用setBrush()、setWidth()、setStyle()、setCapStyle()和setJoinStyle()等函数进行设置。其中,画刷可以为画笔提供颜色;线宽默认值为0;画笔风格有实线、点线等,Qt中提供的画笔如下图所示;还有一个Qt::NoPen值,表示不进行线条或边框的绘制。还可以使用setDashPattern()函数来定义一个画笔风格。
在这里插入图片描述

  画笔端点风格定义了怎样进行线条端点的绘制,Qt中提供的画笔端点风格及其效果如图所示。其中,Qt::SqureCap表示线条的终点为方形,并且向前延伸了线宽的一半的长度;Qt::FlatCap风格也是方形端点,但并没有延长;使用Qt::RoundCap风格的线条是圆形的端点,这些风格对宽度为0的线条没有作用。
在这里插入图片描述

最后的画笔连接风格定义了怎样绘制两个线条的连接,Qt提供的画笔端点风格及其效果如下图。其中Qt::BeveJoin风格填充了两个线条之间的空缺三角形,而Qt::RoundJoin使用圆弧来填充这个三角形,这样显得更圆滑;使用Qt::MiterJoin风格是将两个线条的外部边线进行扩展而相交,然后填充形成的三角形区域。
在这里插入图片描述

3.使用画刷

void widget::paintEvent(QPaintEvent *)
{
   QPainter painter(this);
   painter.drawLine(QPoint(0,0),QPoint(100,100));
   //等价于另一种形式
//   QPainter painter;
//   painter.begin(this);
//   painter.drawLine(QPoint(0,0),QPoint(100,100));
//   painter.end();
     //创建画笔
     QPen pen(Qt::green,5,Qt::DotLine,Qt::RoundCap,Qt::RoundJoin);
     //使用画笔
     painter.setPen(pen);
     QRectF rectangle(70.0,40.0,80.0,60.0);
     int startAngle = 30*16int spanAngle = 120*16;
     painter.drawArc(rectangle,startAngle,spanAngle);
     //重新设置画笔
     pen.setWidth(1);
     pen.setStyle(Qt::SolidLine);
     painter.setPen(pen);
     //绘制一个矩形
     painter.drawRect(160,20,50,40);
     //创建画刷
     QBrush brush(QColor(0,0,255),Qt::Dense4Pattern);
     //使用画刷
     painter.setBrush(brush);
     //绘制椭圆
     painter.drawEllipse(220,20,50,50);
     //设置纹理
     brush.setTexture(QPixmap("../mydrawing/banjun.png"));
     //重新使用画刷
     painter.setBrush(brush);
     //定义四个点
     static const QPointF points[4] ={
       QPointF(270.0,80.0),
       QPointF(290.0,10.0),
       QPointF(350.0,30.0),
       QPointF(3900.0,70.0)
     };
     //使用四个点绘制多边形
     painter.drawPolygon(points,4);
}

  QBrush类提供了画刷来对图形进行填充,一个画刷使用它的颜色和风格来定义。填充模式使用Qt::BrushStyle枚举类型来定义,包含了基本模式填充、渐变填充和纹理填充。Qt提供的画刷风格及其效果如下图:
在这里插入图片描述

渐变填充

  在画刷中可以使用渐变填充,而QGradient类就是用来和QBrush一起指定渐变填充的。Qt支持三种类型的渐变填充:1)线性渐变:在开始点和结束点之间插入颜色;2)辐射渐变:在焦点和环绕它的圆环间插入颜色;3)锥形渐变:在圆心周围插入颜色。这三种渐变分别由QGradient的三个子类来表示,QLinearGradient表示线性渐变,QRadialGradient表示辐射渐变,QConicalGradient表示锥形渐变。
在前面程序paintEvent()函数中继续添加如下代码:

//线性渐变
QLinearGradient linearGradient(QPointF(40,190),QPointF(70,190));
//插入颜色
linearGradient.setColorAt(0,Qt::yellow);
linearGradient.setColorAt(0,Qt::yellow);
linearGradient.setColorAt(0,Qt::yellow);
//指定渐变区域以外的区域的扩扩散方式
linearGradient.setSpread(QGradient::RepeatSpread);
//使用渐变作为画刷
painter.setBrush(linearGradient);
painter.drawRect(10,170,90,40);

//辐射渐变
QRadialGradient radialGradient(QPointF(200,190),50,QPointF(275,200));
radialGradient.setColorAt(0,QColor(255,255,100,150));
radialGradient.setColorAt(1,QColor(0,0,0,50));
painter.setBrush(radialGradient);
painter.drawEllipse(QPointF(200,190),50,50);

QConicalGradient conicalGradient(QPointF(350,190),60);
conicalGradient.setColorAt(0.2,Qt::cyan);
conicalGradient.setColorAt(0.9,Qt::black);
painter.setBrush(conicalGradient);
painter.drawEllipse(QPointF(350,190),50,50);

//画笔使用线性渐变来绘制直线和文字
painter.setPen(QPen(linearGradient,2));
painter.drawLine(0,280,100,280);
painter.drawText(150,280,tr("helloQt"));

1.线性渐变
  线性渐变QLinearGradient::QLinearGradient(const QPointF &start,const QPointF &finalStop)需要 指定开始点start和结束点finalStop,然后将开始点和结束点之间的区域进行等分,开始点的位置为0.0,结束点的位置为1.0,它们之间的位置按照距离比例进行设定,然后使用QGradient::setColorAt(qreal position,const QColor& color)函数在指定的位置position插入指定的颜色color。当然,这里的位置的值要在0-1之间。
  这里还可以使用setSpread()函数来设置填充的扩散方式,即指明在指定区域以外的区域怎样填充。扩散方式由QGradient::Spread枚举类型定义,他一共由3个值,分别是QGradient::PadSpread使用最接近的颜色进行填充,这是默认值。QGradient::RepeatSpread在渐变区域以外的区域以外的区域重复渐变;QGradient::ReflectSpread在渐变区域以外将反射渐变。
在这里插入图片描述

2.辐射渐变
  QRadialGradient(const QPointF &center, qreal radius, const QPointF &focalPoint)需要指定圆心和半径,这样就确定了一个圆,然后再指定一个焦点focalPoint。焦点的位置为0,圆环的位置为1,然后在焦点和圆环间插入颜色。辐射渐变也可以使用setSpread()函数设置渐变区域以外的区域的扩展方式。三种扩散方式的效果下图:
在这里插入图片描述

3.锥形渐变
  锥形渐变QConicalGradient(const QPointF &center, qreal angle)需要指定中心点center和一个角度angle,然后沿逆时针从给定的角度开始环绕中心点插入颜色。
在这里插入图片描述

坐标系统

  Qt的坐标系统是由QPainter类控制的,而QPainter是在绘图设备上进行绘制的。一个绘图设备的默认坐标系统中,原点(0,0)在其左上角,x坐标向右增长,y坐标向下增长。
  QPainter的逻辑坐标与绘图设备的物理坐标之间的映射由QPainter的变换矩阵、视口和窗口处理。逻辑坐标和物体坐标默认是一致的。可以在帮助中通过Coordinate System关键字查看。

抗锯齿绘图

  抗锯齿又称为反锯齿或者反走样,就是对图像的边缘进行平滑处理,使其看起来更加柔和流畅的一种技术。QPainter进行绘制时可以使用QPainter::RenderHint渲染提示来指定是否要使用抗锯齿功能,渲染提示的取值如下表,不同宽度画笔渲染如下图

常量描述
QPainter::Antialiasing指示绘图引擎在可能的情况下应该进行边缘的抗锯齿
QPainter::TextAntialiasing指示绘图引擎在可能的情况下应该会之抗锯齿的文字
QPainter::SmoothPixmapTransform指示绘图引擎应该使用一个平滑pixmap转换算法而不是最邻近插值算法

在这里插入图片描述

坐标变换

  基本变换:坐标系统的2D变换由QTransform类实现,可以使用QPainter::scale()函数缩放坐标系统,使用QPainter::rotate()函数顺时针旋转坐标系统,使用QPainter::translate()函数平移坐标系统,还可以使用QPainter::shear()围绕原点来扭曲坐标系统。当然也可以通过QTransform类实现,而且QTransform类对象可以存储多个变换操作,当同样的变换要多次使用时,建议使用QTransform类对象。坐标系统的变换是通过变换矩阵实现的,可以在平面上变换一个点到另一个点。进行所有变换操作的变换矩阵都可以使用QPainter::worldTransform()函数获得;如果要设置一个变换矩阵,可以使用QPainter::setWorldTransform()函数。这两个函数也可以分别使用QPainter::transform()和QPainter::setTransform()函数来代替。
  在进行变换操作时,可能需要多次改变坐标系统,然后再恢复,这样编码会很乱,而且很容易出现操作失误。这时可以使用QPainter::save()函数来保存QPainter的变换矩阵,它会把变换矩阵保存到一个内部栈中,需要恢复变换矩阵时再使用QPainter::restore()函数。

  窗口-视口转换:使用QPainter进行绘制时,会使用逻辑坐标进行绘制,然后再转换为绘图设备的物体坐标。逻辑坐标到物理坐标的映射由QPainter的worldTRansform()函数、QPainter的vierport()以及window()函数进行处理。其中,视口表示物理坐标下指定的一个任意矩形,而窗口表示逻辑坐标系下的相同矩形。默认的,逻辑坐标和物理坐标是重合的,它们都相当于绘图设备上的矩形。
  使用窗口-视口转换可以使逻辑坐标系统适合应用要求,这个机制也可以用来让绘图代码独立于绘图设备。窗口-视口转换仅仅是线性变换,不会执行裁剪操作。这意味着如果绘制范围超出了当前设置的窗口,那么仍然会使用相同的线性代数方法将绘制变换到视口上。绘制过程中先使用坐标矩阵进行变换,在使用窗口-视口变换。

其他绘制

绘制文字

  除了绘制图形以外,还可以使用QPainter::drawText()函数来绘制文字,也可以使用QPainter::setFont()设置文字所使用的字体,使用QPainter::fontInfo()函数可以获取字体的信息,它返回QFontInfo类对象。绘制文字时会默认使用抗锯齿。

绘制路径

  如果要绘制一个复杂的图形,尤其是要重复绘制这样的图形,可以使用QPainterPath类,并使用QPainter::drawPath()进行绘制。QPainterPath类为绘制操作提供了一个容器,可以用来创建图形并重复使用。一个绘图路径就是由多个矩形、椭圆、线条或曲线等组成的对象,一个路径可以是封闭的,如椭圆和矩形;也可以是非封闭的,如线条和曲线。例子如下:

//组成一个路径
void Widget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    QPainterPath path;
    //移动当前点到点(50,200)
    path.moveTo(50,200);
    //从当前点即(50,200)绘制一条直线到点(50,230),完成后更改当前点更改为(50,230)
    path.lineTo(50,230);
    //从当前点和点(120,60)之间绘制一条三次贝塞尔曲线
    path.cubicTo(QPointF(105,40),QPointF(115,80),QPointF(120,60));
    path.lineTo(130,130);
    //向路径中添加一个椭圆
    path.addEllipse(QPoint(130,130),30,30);
    painter.setPen(Qt::darkYellow);
    //绘制路径
    painter.drawPath(path);
    //平移坐标系统后重新绘制路径
    painter.translate(200,0);
    painter.setPen(Qt::darkBlue);
    painter.drawPath(path);

}

//填充规则
void Widget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    QPainterPath path;
    path.addEllipse(10,50,100,100);
    path.addRect(50,100,100,100);
    painter.setBrush(Qt::cyan);
    painter.drawPath(path);

    painter.translate(180,0);
    path.setFillRule(Qt::WindingFill);
    painter.drawPath(path);
}

填充效果效果如下:
在这里插入图片描述

绘制图像

  Qt中提供了四个类来处理图像数据:QImage、QPixmap、QBitmap和QPicture,都是常用的绘图设备。其中,QImage主要用来进行I/O处理,它对I/O处理操作进行了优化,而且也可以用来直接访问和操作像素;QPixmap主要用来在屏幕上显示图像,他对在屏幕上显示图像进行了优化;QBitmap是QPixmap的子类,用来处理颜色深度为1的图像,即只能显示黑白两种颜色;QPicture用来记录并重演QPainter命令。

1.QImage
  QIamge类提供了一个与硬件无关的图像表示方法,可以直接访问像素数据,也可以作为绘图设备。因为QImage是QPaintDevice的子类,所有QPainter可以直接在QImage对象上进行绘制。当在QImage上使用QPainter时,绘制操作会在当前GUI线程以外的其他线程中执行。QImage支持的图像格式如下表所示:

格式Qt的支持格式Qt的支持
BMP读、写PBM
GIFPGM
JPG读、写PPM读、写
JPEG读、写XBM读、写
PNG读、写XPM读、写

例子:

void Widget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    QImage image;
    image.load("../mytransformation/banjun.jpg");
    //输出图片的一些信息
    qDebug()<<image.size()<<image.format()<<image.depth();
    //在界面上绘制图片
    painter.drawImage(QPoint(10,10),image);
    //获取镜像图片
    QImage mirror = image.mirrored();
    //将图片扭曲
    QTransform transform;
    transform.shear(0.2,0);
    QImage image2 = mirror.transformed(transform);
    painter.drawImage(QPoint(10,160),image2);
    //将镜像图片保存到文件
    image2.save("../mytransformation/mirror.jpg");
}

2.QPixmap
  QPixmap可以作为一个绘图设备将图像显示在屏幕上。QPixmap中的像素在内部由底层的窗口系统进行管理。因为QPixmap是QPaintDevice的子类,所有QPainter也可以直接在它上面进行绘制。要想访问像素,只能使用QPainter的相应函数,或者将QPixmap转换为QImage。而与QImage不同,QPixmap中的fill()函数可以使用指定的颜色初始化整个pixmap图像。
  可以使用toImage()和fromImage()函数在QImage和QPixmap之间进行转换,通常情况下,QImage类用来加载一个图像文件,随意操纵图像数据,然后将QImage对象转换为QPixmap类型再显示到屏幕上。另外,与QImage不同之处是QPixmap依赖于具体的硬件。QPixmap类也是使用隐式数据共享,可以作为值进行传递。
  QPixmap可以很容易地通过QLabel或QAbstractButton的子类(比如QPushButton)显示在屏幕上。QLabel拥有一个pixmap属性,而QAbstractButton拥有一个icon属性。QPixmap可以使用copy()复制图像上的一个区域,还可以使用mask()实现遮罩效果。

3.Qpicture
  QPicture是一个可以记录和重演QPainter命令的绘图设备。QPicture可以使用一个平台无关的格式(.pic格式)将绘图命令序列化到I/O设备中,所以可以绘制在QWidget部件或者QPixmap上的内容,都可以保存在QPicture中。QPicture与分辨率无关,在不同设备上的显示效果都是一样的。

复合模式

  QPainter提供了复合模式来定义如何完成数字图像的复合,即如何将源图像的像素和目标图像的像素进行合并。QPainter提供的常用符合模式及其效果如下图所示
在这里插入图片描述

双缓冲绘图

  所谓双缓冲绘图,就是在进行绘制时,先将所有内容都绘制到一个绘图设备上,然后再将整个图像绘制到部件上显示出来。使用双缓冲绘图可以避免显示时的闪烁现象。从Qt4.0开始,QWidget部件的所有绘制都自动使用了双缓冲,所以一般没有必要在paintEvent()函数中使用双缓冲代码来避免闪烁。

绘图中的其他问题

1.重绘事件
  一个重绘事件用来重绘一个部件的全部或者部分区域,下面几个原因中的任意一个都会发生重绘事件:
  >repaint()函数或者update()函数被调用
  >被隐藏的部件现在被重新显示
  >其他一些原因
  大部分不见可以简单地重绘它们地全部界面,但是一些绘制比较慢地部件需要进行优化而只需绘制需要的区域,这种速度上的优化不会影响结果。Qt也会通过合并多个重绘事件来加快绘制,当update()被调用多次,或者窗口系统发送了多个重绘事件时,那么Qt就会合并这些事件成为一个事件,要等到Qt返回主事件循环后才会进行,所以多次调用update()函数一般只会引起依次paintEvent()函数调用。而调用repaint()0函数会立即调用paintEvent()函数来重绘部件,只有在必须立即进行重绘操作地情况下(比如动画)才使用repaint()函数。update()函数允许Qt优化速度和减少闪烁,但是repaint()函数不支持这样的优化,所以建议一般情况下尽可能使用update()函数。当重绘事件发生时,要更新地区域一般会被擦除,然后在部件的背景上进行绘制。

2.剪切
  QPainter可以剪切任何的绘制操作,它可以剪切一个矩形、一个区域或者一个路径中的内容,这分别可以使用setClipRect()、setClipRegion()和setClipPath()函数来实现。

  

3.读取和写入图像
  要读取图像,最普通的方法是使用QImage或者QPixmap的构造函数,或者调用QImage::load()和QPixmap::load()函数。Qt还有一个QImageReader类,该类提供了一个格式无关的接口,可以从文件或者其他设备中读取图像。QImageReader类可以在读取图像时提供更多的控制,例如,可以使用getScaledSize()函数将图像以指定的大小进行读取,还可以使用setClipRect()读取一个图像的一个区域。由于依赖于图像格式底层的支持,QImageReader的这些操作可以节省内存和加快图像的读取。另外,Qt还提供了QImageWriter类来存储图像,它支持设置图像格式的特定选项,比如伽马等级、压缩等级和品质。当然,如果不需要设置这些选项,那么可以直接使用QImage::save()和QPixmap::save()函数。

4.播放gif动画
  QMovie类是使用QImageReader来播放动画的便捷类,使用它可以播放不带声音的简单的动画,比如gif文件格式。。。。。。

5.渲染SVG文件
  可缩放矢量图形(Scalable Vector Graphics ,SVG)是一个使用XML来描述二维图形和图形应用程序的语言。在Qt中可以使用QSvgWidget类加载一个SVG文件,而使用QSvgRenderer类在QSvgWidget中进行SVG文件的渲染。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值