前面我们不是学过了绘制图形
Qt—2D绘图—1基本图型绘制与填充
现在我们学习下绘制其他2D东西
除了绘制图形以外,还可以使用QPainter::darwText()
函数来绘制文字,也可以使用QPainter::setFont()
设置文字所使用的字体,使用QPainter::fontInfo()
函数可以获取字体的信息,它返回QFontInfo类对象。绘制文字时会默认使用抗锯齿。
新建Qt Widgets应用,项目名称为mydrawing2,基类选择QWidget,类名为 Widget。建立完成后,在widget.h文件中声明重绘事件处理函数:
protected:
void paintEvent(QPaintEvent *event);
然后到widget.cpp文件中添加头文件QPainter
下面添加paintEvent函数的定义:
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QRectF rect(10.0, 10.0, 380.0, 280.0);
painter.setPen(Qt::red);
painter.drawRect(rect);
painter.setPen(Qt::blue);
painter.drawText(rect, Qt::AlignHCenter, tr("AlignHCenter"));
painter.drawText(rect, Qt::AlignLeft, tr("AlignLeft"));
painter.drawText(rect, Qt::AlignRight, tr("AlignRight"));
painter.drawText(rect, Qt::AlignVCenter, tr("AlignVCenter"));
painter.drawText(rect, Qt::AlignBottom, tr("AlignBottom"));
painter.drawText(rect, Qt::AlignCenter, tr("AlignCenter"));
painter.drawText(rect, Qt::AlignBottom | Qt::AlignRight,
tr("AlignBottom\nAlignRight"));
这里使用了绘制文本函数的一种重载形式QPainter::drawText(const QRectF&.rectangle,int flags,const QString&text,QRectF * boundingRect = 0)
.
Qt:: AlignmentFlag
枚举类型进行定义,不同对齐方式也可以使用按位或“|”操作符同时使用,这里还可以使用Qt::TextFlag
定义的其他一些标志,比如自动换行等;如果绘制的文字和它的布局不用经常改动,那么也可以使用drawStaticText()
函数,它更加高效。
下面继续添加
QFont font("宋体", 15, QFont::Bold, true);
//设置下划线
font.setUnderline(true);
//设置上划线
font.setOverline(true);
//设置字母大小写
font.setCapitalization(QFont::SmallCaps);
//设置字符间的间距
font.setLetterSpacing(QFont::AbsoluteSpacing, 10);
//使用字体
painter.setFont(font);
painter.setPen(Qt::green);
painter.drawText(120, 80, tr("yafeilinux"));
//平移100,100,旋转90°
painter.translate(100, 100);
painter.rotate(90);
painter.drawText(0, 0, tr("helloqt"));
这里创建了QFont 字体对象,使用的构造函数为QFont::QFont (const QString&family, int pointSize = -1, int weight=-1, bool italic = false)
QFontDatabase
类来获取所支持的所有字体;然后又使用了其他几个函数来设置字体的格式,最后调用setFont()函数
来使用该字体,并使用drawText()函数
的另一种重载形式在点(120,80)绘制了文字。
后面又将坐标系统平移并旋转,然后再次绘制了文字。
如果要绘制复杂的图形,可以使用路径绘制,类似于ps的路径
可以使用QPainter-Path
类,并使用QPainter::drawPath()
进行绘制。QPainterPath
类为绘制操作提供了一个容器,可以用来创建图形并且重复使用。
一个绘图路径就是由多个矩形、椭圆、线条或者曲线等组成的对象,一个路径可以是封闭的,如矩形和椭圆;也可以是非封闭的,如线条和曲线。
现在来绘制一个路径,将上面的程序中的paintEvent中的内容注释掉,重新写如下:
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QPainterPath path;
// 移动当前点到点(50, 250)
path.moveTo(50, 250);
// 从当前点即(50, 250)绘制一条直线到点(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);
// 平移坐标系统后重新绘制路径
path.translate(200,0);
painter.setPen(Qt::darkBlue);
painter.drawPath(path);
}
创建一个QPainterPath
对象后就会以坐标原点为当前点进行绘制,可以随时使用moveTo()
函数改变当前点,比如程序中移动到了点(50,250),那么下次就会从该点开始进行绘制;
可以使用lineTo() ,arcTo(),cubicTo()和 quadTo()
等函数将直线或者曲线添加到路径中,其中,QPainterPath::cubicTo(const QPointF&c1,const QPointF&c2,const QPointF& endPoint)
函数可以在当前点
和 endPoint
点之间添加一个3次贝塞尔曲线
,其中的c1和 c2是控制点
,
如图 10-18所示:
quadTo()函数
可以绘制一个二次贝塞尔曲线
;
可以使用addEllipse() ,addPath() , addRect() ,addRegion() 、addText()和 addPolygon()
来向路径中添加一些图形或者文字,它们都从当前点开始进行绘制,绘制完成后以结束点作为新的当前点,这些图形都是由一组直线或者曲线组成的。
例如,矩形就是顺时针添加的一组直线,绘制完成后当前点在矩形的左上角;
而椭圆由一组顺时针曲线组成,开始点和结束点都在0度处(3点钟的位置)。
另外还可以使用addPath()
来添加其他的路径,这样会从本路径的当前点和要添加路径的第一个组件间添加一条直线。
可以使用currentPosition()函数
获取当前点
使用moveTo()函数
改变当前点;
当组建好路径后可以使用drawPath()函数
来绘制路径
这里使用translate()函数
将路径平移后又重新绘制了路径。
运行程序可以看到,这样就能够重复绘制复杂的图形了,这也是QPainterPath的主要作用:
前面在绘制多边形时就提到了填充规则Qt:: FillRule
填充路径时也要使用填充规则.
这里一共有两个填充规则:Qt::OddEvenFill和 Qt::WindingFill
。
其中,Qt::OddEvenFill
使用的是奇偶填充规则
,具体来说就是:如果要判断一个点是否在图形中,那么可以从该点向图形外引一条水平线﹐如果该水平线与图形的交点的个数为奇数,那么该点就在图形中。这个规则是默认值;
而Qt::WindingFill
使用的是非零弯曲规则
,具体来说就是:如果要判断一个点是否在图形中,那么可以从该点向图形外引一条水平线,如果该水平线与图形的边线相交,这个边线是顺时针绘制的,就记为1;是逆时针绘制的就记为-1。然后将所有数值相加,如果结果不为0,那么该点就在图形中。
图10- 19是这两种规则的示意图:
对于Qt::OddEvenFill规则,第一个交点记为1,第二个交点记为2
;
对于Qt::WindingFill规则,因为椭圆和矩形都是以顺时针进行绘制的,所以各个交点对应的边都使用1来代表。
项目paintEvent代码如下:
void Widget::paintEvent(QPaintEvent *)
{
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::OddEvenFill规则
.然后将路径进行平移,重新使用Qt:: W indingFill规则绘制了该路径。
QPainter::fillPath()
来填充一个路径;QPainter::strokePath()
函数来绘制路径的轮廓;QPainterPath::elementAt()
来获取路径中的一个元素;QPainterPath::elementCount()
获取路径中元素的个数;QPainterPath::contains()
函数来判断一个点是否在路径中;QPainterPath::toFillPolygon()
函数将路径转换为一个多边形。这部分内容可以参考一下Painter Paths Example 示例程序,另外Qt还提供了一个Vector Deformation演示程序。
Qt提供了4个类来处理图像数据:QImage、QPixmap、QBitmap和QPicture
,都是常用的绘图设备。
其中
QImage
主要用来进行I/O处理
,它对I/O处理操作进行了优化,而且也可以用来直接访问和操作像素;QPixmap
主要用来在屏幕上显示图像
,它对在屏幕上显示图像进行了优化;QBitmap
是QPixmap
的子类,用来处理颜色深度为1的图像,即只能显示黑白两种颜色;QPicture
用来记录并重演QPainter命令。下面来看一下在这几个绘图设备上绘制图形的效果。
新建QT Widgets 应用,项目名称为mydrawing3,基类选择QWidget,类名为Widget。建立完成后,在widget.h文件中声明重绘事件函数的声明,然后到widget.cpp中添加这个头文件
#include
#include
#include
#include
#include
这些是需要的头文件
下面添加paintEvent()函数的定义:
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter;
// 绘制image
QImage image(100, 100, QImage::Format_ARGB32);
painter.begin(&image);
painter.setPen(QPen(Qt::green, 3));
painter.setBrush(Qt::yellow);
painter.drawRect(10, 10, 60, 60);
painter.drawText(10, 10, 60, 60, Qt::AlignCenter, tr("QImage"));
painter.setBrush(QColor(0 , 0, 0, 100));
painter.drawRect(50, 50, 40, 40);
painter.end();
// 绘制pixmap
QPixmap pix(100, 100);
painter.begin(&pix);
painter.setPen(QPen(Qt::green, 3));
painter.setBrush(Qt::yellow);
painter.drawRect(10, 10, 60, 60);
painter.drawText(10, 10, 60, 60, Qt::AlignCenter, tr("QPixmap"));
painter.setBrush(QColor(0 , 0, 0, 100));
painter.drawRect(50, 50, 40, 40);
painter.end();
// 绘制bitmap
QBitmap bit(100, 100);
painter.begin(&bit);
painter.setPen(QPen(Qt::green, 3));
painter.setBrush(Qt::yellow);
painter.drawRect(10, 10, 60, 60);
painter.drawText(10, 10, 60, 60, Qt::AlignCenter, tr("QBitmap"));
painter.setBrush(QColor(0 , 0, 0, 100));
painter.drawRect(50, 50, 40, 40);
painter.end();
// 绘制picture
QPicture picture;
painter.begin(&picture);
painter.setPen(QPen(Qt::green, 3));
painter.setBrush(Qt::yellow);
painter.drawRect(10, 10, 60, 60);
painter.drawText(10, 10, 60, 60, Qt::AlignCenter, tr("QPicture"));
painter.setBrush(QColor(0 , 0, 0, 100));
painter.drawRect(50, 50, 40, 40);
painter.end();
// 在widget部件上进行绘制
painter.begin(this);
painter.drawImage(50, 20, image);
painter.drawPixmap(200, 20, pix);
painter.drawPixmap(50, 170, bit);
painter.drawPicture(200, 170, picture);
}
这里分别在4个绘图设备上绘制了两个相交的正方形,较小的正方形使用了透明的黑色进行填充,在较大正方形的中间绘制了文字。
在定义在QImage、QPixmap和QBitmap对象
时均指定了它们的大小,即宽和高均为100。
而且,各个绘图设备都有自己的坐标系统,它们的左上角为原点。
在进行绘制时,因为所绘制的图形没有占完设置的大小,而我们也没有设置背景填充色,所以背景应该为透明的。这里还要看到,在各个不同的绘图设备上进行绘制时,都使用了begin()函数来指定设备
,等绘制完成后再使用end()函数来结束绘制
。
最后,将这4张图像绘制到了窗口界面上。
运行程序,效果如图所示。
可以看到,QPixmap的透明背景显示为黑色,QBitmap只能显示路径的轮廓。
QImage类提供了一个与硬件无关的图像表示方法,可以直接访问像素数据,也可以作为绘图设备。
因为QImage
是 QPaintDevice
的子类,所以QPainter可以直接在QImage对象上进行绘制
。
当在QImage上使用QPainter时,绘制操作会在当前GUI线程以外的其他线程中执行。
QImage支持的图像格式如表10-3所列,它们包含了单色,8位、32位和 alpha混合格式图像
。
QImage提供了获取图像各种信息的相关函数,还提供了一些转换图像的函数。
QImage使用了隐式数据共享,所以可以进行值传递
。
QImage对象可以使用数据流,也可以进行比较。
使用前面的项目,但是重新写paintEvent()函数里的内容,更改如下:
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QImage image;
// 加载一张图片
image.load("../mydrawing3/image.png");
// 输出图片的一些信息
qDebug() << image.size() << image.format() << image.depth();
// 在界面上绘制图片
painter.drawImage(QPoint(10, 10), image);
// 获取镜像图片
QImage mirror = image.mirrored();
// 将图片进行扭曲
QTransform transform;
transform.shear(0.2, 0);//shear() 可以实现图片的扭曲。它有两个参数,前面的参数实现横向变形,后面的参数实现纵向变形。当它们的值为 0 时,表示不扭曲。
QImage image2 = mirror.transformed(transform);
painter.drawImage(QPoint(10, 160), image2);
// 将镜像图片保存到文件
image2.save("../mydrawing3/mirror.png");
}
这里先为QImage对象加载了一张图片(需要向源码目录中放一张图片),然后输出了图片的一些信息,并将图片绘制到了界面上。
然后使用QImage::mirrored(bool horizontal=false,bool vertical=true)函数
获取了该图片的镜像图片,默认返回的是垂直方向的镜像,也可以设置为水平方向的镜像。
使用transformed()函数
可以将图片进行各种坐标变换
,最后使用了save()函数
将图片存储到文件中。
效果就是先画一个image在10,10位置,再进行扭曲shear(0.2, 0) 横向0.2扭曲,然后画10,160位置
最后图片保存到项目文件中:
QImage类还提供了强大的操作像素的功能,这里就不再举例讲解,有需要可以参考QImage类的帮助文档。
QPixmap
可以作为一个绘图设备将图像显示在屏幕上。
QPixmap
中的像素在内部由底层的窗口系统进行管理。
因为QPixmap是QPaintDevice
的子类,所以QPainter也可以直接在它上面进行绘制。
要想访问像素,只能使用QPainter的相应函数,或者将QPixmap转换为QImage。
而与QImage不同,QPixmap中的fill()
函数可以使用指定的颜色初始化整个pixmap图像。
可以使用toImage( )和fromImage()函数
在QImage和QPixmap之间进行转换。
通常情况下,Qlmage类用来加载一个图像文件,随意操纵图像数据,然后将QImage对
象转换为QPixmap类型再显示到屏幕上。
当然,如果不需要对图像进行操作,那么也可以直接使用QPixmap来加载图像文件。
另外,与QImage不同之处是QPixmap依赖于具体的硬件
。
QPixmap类
也是使用隐式数据共享
,可以作为值进行传递。
QPixmap
可以很容易地通过QLabel或QAbstractButton的子类(比如QPushButton)显示在屏幕上。
QLabel
拥有一个pixmap属性
,而QAbstractButton
拥有一个icon属性
。
QPixmap
可以使用copy()
复制图像上的一个区域,还可以使用mask()
实现遮罩效果。
删除前面程序中paintEvent函数里的内容,然后将其更改如下:
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QPixmap pix;
pix.load("../mydrawing3/yafeilinux.png");
painter.drawPixmap(0, 0, pix.width(), pix.height(), pix);
painter.setBrush(QColor(255, 255, 255, 100));
painter.drawRect(0, 0, pix.width(), pix.height());
painter.drawPixmap(100, 0, pix.width(), pix.height(), pix);
painter.setBrush(QColor(0, 0, 255, 100));
painter.drawRect(100, 0, pix.width(), pix.height());
}
这里使用QPixmap先将同一图片并排绘制了二次,然后分别在其上面绘制了一个使用不同的透明颜色填充的矩形,这样就可以使图像显示出不同的颜色,这使用到了下节的复合模式。
下面来实现截取屏幕的功能:
在widget.cpp文件中再添加头文件:
#include
#include
#include
#include
然后在构造函数中添加如下代码:
QWindow window;
QPixmap grab = window.screen()->grabWindow(QApplication::desktop()->winId());
grab.save("../mydrawing3/screen.png");
QLabel *label = new QLabel(this);
label->resize(400, 200);
QPixmap pix = grab.scaled(label->size(), Qt::KeepAspectRatio,
Qt::SmoothTransformation);
label->setPixmap(pix);
label->move(0, 100);
使用QPixmapQScreen::grabWindow(WId window,int x=0,int y=0,int width=-1,int height=-1)
函数可以截取屏幕的内容到一个QPixmap中,这里要指定窗口系统标识符(The window system identifier, Wid),还有要截取屏幕的内容所在的矩形,默认是截取整个屏幕的内容。
除了截取屏幕,还可以使用QWidget::grab()
来截取窗口部件上的内容。然后将截取到的图像显示在一个标签中,为了显示整张图像,这里将其进行了缩放,使用了函数
QPixmap::scaled(const QSize &size,Qt::AspectRatioMode aspectRatioMode=Qt ::IgnoreAspectRatio,Qt::Transformation Mode transformMode=Qt::FastTransformation)
函数需要指定缩放后图片的大小size
,还要指定宽高比模式Qt::AspectRatioMode
和转换模Qt::TransformationMode
。
/
而转换模式默认是快速转换Qt::FastTransformation
,还有一种就是程序中使用的平滑转换Qt::SmoothTransformation
。
截屏功能可以参考Screenshot Example示例程序。
代码中:
QPixmap pix = grab.scaled(label->size(), Qt::KeepAspectRatio,
Qt::SmoothTransformation);
//label大小,矩形框中放大,保持宽高比,平滑模式
然后整体代码是这样的:
#include "widget.h"
#include "ui_widget.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
QWindow window;
QPixmap grab = window.screen()->grabWindow(QApplication::desktop()->winId());
grab.save("../mydrawing3/screen.png");
QLabel *label = new QLabel(this);
label->resize(400, 200);
QPixmap pix = grab.scaled(label->size(), Qt::KeepAspectRatio,
Qt::SmoothTransformation);
label->setPixmap(pix);
label->move(0, 100);
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QPixmap pix;
pix.load("../mydrawing3/yafeilinux.png");
painter.drawPixmap(0, 0, pix.width(), pix.height(), pix);
painter.setBrush(QColor(255, 255, 255, 100));
painter.drawRect(0, 0, pix.width(), pix.height());
painter.drawPixmap(100, 0, pix.width(), pix.height(), pix);
painter.setBrush(QColor(0, 0, 255, 100));
painter.drawRect(100, 0, pix.width(), pix.height());
}
QPixmap grab = window.screen()->grabWindow(QApplication::desktop()->winId());
grab.save("../mydrawing3/screen.png");
QLabel *label = new QLabel(this);
label->resize(400, 200);
QPixmap pix = grab.scaled(label->size(), Qt::KeepAspectRatio,
Qt::SmoothTransformation);
label->setPixmap(pix);
label->move(0, 100);
截取了屏幕图像,然后保存到mydrawing3/screen.png,然后显示再label
定义了一个QPixmap pix,定义模式,然后显示
而在paintEvent事件中则使用
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QPixmap pix;
pix.load("../mydrawing3/yafeilinux.png");
painter.drawPixmap(0, 0, pix.width(), pix.height(), pix);
painter.setBrush(QColor(255, 255, 255, 100));
painter.drawRect(0, 0, pix.width(), pix.height());
painter.drawPixmap(100, 0, pix.width(), pix.height(), pix);
painter.setBrush(QColor(0, 0, 255, 100));
painter.drawRect(100, 0, pix.width(), pix.height());
}
读取图像yafeilinux.png,然后painter.drawPixmap 画出来,设置颜色setBrush
最终效果如图:
只能显示指定大小,而截取图像只能显示在矩形中大小
QPicture
是一个可以记录和重演QPainter命令的绘图设备。
QPicture可以使用一个平台无关的格式(. pic格式)将绘图命令序列化到I/О设备中,所有可以绘制在QWidget部件或者QPixmap上的内容,都可以保存在QPicture中。
QPicture与分辨率无关,在不同设备上的显示效果都是一样的。
要记录QPainter命令,可以像如下代码这样进行:
QPicture picture;
QPainter painter;
painter.begin(&picture);
painter.drawEllipse(10,20,80,70);
painter.end();
picture.save("drawing.pic");
要重演QPainter命令,可以像如下代码这样进行:
QPicture picture;
picture.load("drawing. pic");
QPainter painter;
painter.begin(&myImage);
painter.drawPicture(0,0.picture);
painter.end();
QPainter提供了复合模式(Composition Modes)
来定义如何完成数字图像的复合,即如何将源图像的像素和目标图像的像素进行合并。
QPainter提供的常用复合模式及其效果如图10-22所示
所有的复合模式可以在QPainter的帮助文档中进行查看。
其中,最普通的类型是SourceOver(通常被称为alpha混合)
,就是正在绘制的源像素混合在已经绘制的目标像素上,源像素的alpha分量
定义了它的透明度
,这样源图像就会以透明效果在目标图像上进行显示。
若绘图设备是QImage
,图像的格式一定要指定为Format_ARGB32Premultiplied
或者Format_ARGB32
,不然复合模式就不会产生任何效果。
当设置了复合模式,它就会应用到所有的绘图操作中,如画笔,画刷、渐变和 pixmap/image绘制等。
新建Qt widget项目,项目名称为mycomposition,基类选择QTwidget。
在widget.cpp文件中重绘事件处理函数,然后到widget.cpp文件中添加头文件
#include< QPainter>
然后定义paintEvent函数如下:
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);
painter.setBrush(QColor(0, 0, 255, 150));
painter.drawRect(50, 0, 100, 100);
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
painter.drawRect(250, 0, 100, 100);
painter.setCompositionMode(QPainter::CompositionMode_DestinationOver);
painter.drawRect(50, 200, 100, 100);
painter.setCompositionMode(QPainter::CompositionMode_Xor);
painter.drawRect(250, 200, 100, 100);
painter.end();
painter.begin(this);
painter.drawImage(0, 0, image);
}
这里先在 QImage 上绘制了一个矩形﹐然后又在这个矩形的4个角分别绘制了4个小矩形,每个小矩形都使用了不同的复合模式,并且使用了半透明的颜色进行填充。
第一个小矩形没有明确指定复合模式,默认使用的是SourceOver模式
。
复合模式的使用可以参考Image Composition Example示例程序,还可以看一下Composition Modes演示程序。