Qt 的绘图系统允许使用相同的 API 在屏幕和其它打印设备上进行绘制。整个绘图系统基于QPainter,QPainterDevice和QPaintEngine三个类。
下图给出了这三个类之间的层次结构:
上面的示意图告诉我们,Qt 的绘图系统实际上是,使用QPainter在QPainterDevice上进行绘制,它们之间使用QPaintEngine进行通讯(也就是翻译QPainter的指令)。
一、QPainter 绘图
1)绘图事件 void paintEvent()
重写QWidget的paintEvent()函数。
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget();
//绘图事件
void paintEvent(QPaintEvent* qe);
private:
Ui::Widget *ui;
};
2)声明一个画家对象
QPainter接收一个QPaintDevice指针作为参数。QPaintDevice有很多子类,比如QImage,以及QWidget。QPaintDevice可以理解成要在哪里去绘制,而现在我们希望画在这个组件,因此传入的是 this 指针。
//实例画家类 this指定的是绘图的设备
QPainter painter(this);
3)画线、画圆、画矩形、画文字
//画线
void drawLine(const QLineF &line)
void drawLine(const QLine &line)
void drawLine(int x1, int y1, int x2, int y2)
void drawLine(const QPoint &p1, const QPoint &p2)
void drawLine(const QPointF &p1, const QPointF &p2)
//画圆
void drawEllipse(const QRectF &rectangle)
void drawEllipse(const QRect &rectangle)
void drawEllipse(int x, int y, int width, int height)
void drawEllipse(const QPointF ¢er, qreal rx, qreal ry)
void drawEllipse(const QPoint ¢er, int rx, int ry)
//画矩形
void drawRect(const QRectF &rectangle)
void drawRect(int x, int y, int width, int height)
void drawRect(const QRect &rectangle)
//画文字
void drawText(const QPointF &position, const QString &text)
void drawText(const QPoint &position, const QString &text)
void drawText(int x, int y, const QString &text)
void drawText(const QRectF &rectangle, int flags, const QString &text, QRectF *boundingRect = nullptr)
void drawText(const QRect &rectangle, int flags, const QString &text, QRect *boundingRect = nullptr)
void drawText(int x, int y, int width, int height, int flags, const QString &text, QRect *boundingRect = nullptr)
void drawText(const QRectF &rectangle, const QString &text, const QTextOption &option = QTextOption())
//画直线
painter.drawLine(QPoint(0,0),QPoint(100,100));
//画圆 圆心 rx ry
painter.drawEllipse(QPoint(100,100),50,50);
//画矩形 x,y,w ,h
painter.drawRect(QRect(20,20,50,50));
//画文字 在一个范围内的框内 QRect(x,y,w,h),string
painter.drawText(QRect(10,200,100,50),"啦啦啦啦啦啦");
4)设置画笔 QPen
设置画笔宽度 、风格
//设置画笔
QPen pen(QColor(255,0,0));
//设置画笔宽度
pen.setWidth(5);
//设置画笔风格
pen.setStyle(Qt::DotLine);//虚线
//使用画笔
painter.setPen(pen);
5)设置画刷 QBrush
设置画刷 风格
//设置画刷 填充实心图形颜色
QBrush brush(Qt::green);
//设置画刷风格
brush.setStyle(Qt::Dense7Pattern);
//使用画刷
painter.setBrush(brush);
6 示例
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>//画家类
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
}
void Widget::paintEvent(QPaintEvent* qe)
{
//实例画家类 this指定的是绘图的设备
QPainter painter(this);
//画直线
painter.drawLine(QPoint(0,0),QPoint(100,100));
//画圆 圆心 rx ry
painter.drawEllipse(QPoint(100,100),50,50);
//设置画笔
QPen pen(QColor(255,0,0));
//设置画笔宽度
pen.setWidth(5);
//设置画笔风格
pen.setStyle(Qt::DotLine);//虚线
//使用画笔
painter.setPen(pen);
//设置画刷 填充实心图形颜色
QBrush brush(Qt::green);
//设置画刷风格
brush.setStyle(Qt::Dense7Pattern);
//使用画刷
painter.setBrush(brush);
//画矩形 x,y,w ,h
painter.drawRect(QRect(20,20,50,50));
//画文字 在一个范围内的框内 QRect(x,y,w,h),string
painter.drawText(QRect(10,200,100,50),"啦啦啦啦啦啦");
}
Widget::~Widget()
{
delete ui;
}
二、绘图高级设置
1、抗锯齿
效率低
void setRenderHint(QPainter::RenderHint hint, bool on = true)
enum QPainter::RenderHint
{
QPainter::Antialiasing
QPainter::HighQualityAntialiasing
...
};
具体使用:
QPainter painter(this);
painter.drawEllipse(QPoint(100,100),100,100);
//设置抗锯齿能力 效率较低
painter.setRenderHint(QPainter::Antialiasing);
painter.drawEllipse(QPoint(300,100),100,100);
2、对画家进行移动
//移动
void QPainter::translate(const QPointF &offset)
void QPainter::translate(const QPoint &offset)
void QPainter::translate(qreal dx, qreal dy)
//保存画家坐标状态
void QPainter::save()
//还原画家坐标状态
void QPainter::restore()
具体使用:
//画矩形
painter.drawRect(20,20,100,100);
//移动画家
painter.translate(150,0);
//保存画家状态
painter.save();
painter.drawRect(20,20,100,100);
painter.translate(150,0);
//还原画家保存的状态
painter.restore();//恢复到之前保存的坐标
painter.drawRect(20,20,150,150);
3、利用画家画图片
void QPainter::drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source)
void QPainter::drawPixmap(const QRect &target, const QPixmap &pixmap, const QRect &source)
void QPainter::drawPixmap(int x, int y, int w, int h, const QPixmap &pixmap, int sx, int sy, int sw, int sh)
void QPainter::drawPixmap(int x, int y, const QPixmap &pixmap, int sx, int sy, int sw, int sh)
void QPainter::drawPixmap(const QPointF &point, const QPixmap &pixmap, const QRectF &source)
void QPainter::drawPixmap(const QPoint &point, const QPixmap &pixmap, const QRect &source)
void QPainter::drawPixmap(const QPointF &point, const QPixmap &pixmap)
void QPainter::drawPixmap(const QPoint &point, const QPixmap &pixmap)
void QPainter::drawPixmap(int x, int y, const QPixmap &pixmap)
void QPainter::drawPixmap(const QRect &rectangle, const QPixmap &pixmap)
void QPainter::drawPixmap(int x, int y, int width, int height, const QPixmap &pixmap)
具体使用:
///利用画家画资源图片
QPainter painter(this);
painter.drawPixmap(posX,posY,QPixmap(":/Image/up.png"));
4、手动调用绘图事件
利用update
void QWidget::update()
具体示例:
图片会每秒自动往下移动,点击按钮会往右移动
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget();
//绘图事件
void paintEvent(QPaintEvent* qe);
private:
Ui::Widget *ui;
int posX= 0;
int posY= 0;
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>//画家类
#include <QTimer>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//点击移动按钮 移动图片
connect(ui->btn,&QPushButton::clicked,[=]()
{
posX += 20;
//如果超出屏幕宽度 从0开始
if(posX > this->width()) //this->height()高
posX = 0;
//如果要手动调用绘图事件,用update更新
update();
});
//自动移动
QTimer* timer = new QTimer(this);
timer->start(1000);//1S
connect(timer,&QTimer::timeout,this,[=](){
posY += 20;
//如果超出屏幕高度 从0开始
if(posY > this->height())
posY = 0;
//如果要手动调用绘图事件,用update更新
update();
});
}
void Widget::paintEvent(QPaintEvent* qe)
{
///利用画家画资源图片
QPainter painter(this);
painter.drawPixmap(posX,posY,QPixmap(":/Image/up.png"));
}
Widget::~Widget()
{
delete ui;
}
三、绘图设备
绘图设备是指继承QPainterDevice的子类。Qt一共提供了四个这样的类,分别是QPixmap、QBitmap、QImage和 QPicture。其中,
- QPixmap专门为图像在屏幕上的显示做了优化
- QBitmap是QPixmap的一个子类,它的色深限定为1,可以使用 QPixmap的isQBitmap()函数来确定这个QPixmap是不是一个QBitmap。
- QImage专门为图像的像素级访问做了优化。
- QPicture则可以记录和重现QPainter的各条命令。
1、QPixmap
QPixmap继承了QPaintDevice,因此,可以使用QPainter直接在上面绘制图形。
QPixmap也可以接受一个字符串作为一个文件的路径来显示这个文件,比如想在程序之中打开png、jpeg之类的文件,就可以使用 QPixmap。
使用QPainter的drawPixmap()函数可以把这个文件绘制到一个QLabel、QPushButton或者其他的设备上面。
QPixmap是针对屏幕进行特殊优化的,因此,它与实际的底层显示设备息息相关。
//PixMap绘图设备 专门为平台做了显示的优化
QPixmap pix(300,300);
//默认为黑色
//填充颜色 白色
pix.fill(Qt::white);
//声明画家
QPainter painter(&pix);
//设置画笔颜色
painter.setPen(Qt::green);
painter.drawEllipse(QPoint(150,150),100,100);
//保存到电脑上
pix.save("D:/pix.png");
2、QBitmap
QBitmap继承自QPixmap,因此具有QPixmap的所有特性,提供单色图像。
QBitmap的色深始终为1. 色深这个概念来自计算机图形学,是指用于表现颜色的二进制的位数。计算机里面的数据都是使用二进制表示的。为了表示一种颜色,我们也会使用二进制。比如我们要表示8种颜色,需要用3个二进制位,这时色深是3. 因此,所谓色深为1,也就是使用1个二进制位表示颜色。1个位只有两种状态:0和1,因此它所表示的颜色就有两种,黑和白。所以说,QBitmap实际上是只有黑白两色的图像数据。
由于QBitmap色深小,因此只占用很少的存储空间,所以适合做光标文件和笔刷。
void PaintWidget::paintEvent(QPaintEvent *)
{
QPixmap pixmap(":/Image/butterfly.png");
QPixmap pixmap1(":/Image/butterfly1.png");
QBitmap bitmap(":/Image/butterfly.png");
QBitmap bitmap1(":/Image/butterfly1.png");
QPainter painter(this);
painter.drawPixmap(0, 0, pixmap);
painter.drawPixmap(200, 0, pixmap1);
painter.drawPixmap(0, 130, bitmap);
painter.drawPixmap(200, 130, bitmap1);
}
白色的背景在QBitmap中消失了,而透明色在QBitmap中转换成了黑色;其他颜色则是使用点的疏密程度来体现的。
3、QImage
QImage则是使用独立于硬件的绘制系统,实际上是自己绘制自己,因此提供了像素级别的操作,并且能够在不同系统之上提供一个一致的显示形式。
简单使用例子:
声明了一个QImage对象,大小是300 x 300,颜色模式是RGB32,即使用32位数值表示一个颜色的RGB值,也就是说每种颜色使用8位。
//QImage绘图设备 可以对像素进行访问
QImage img(300,300,QImage::Format_RGB32);
//默认黑色 填充颜色
img.fill(Qt::white);
QPainter painter(&img);
painter.setPen(Qt::blue);
painter.drawEllipse(QPoint(150,150),100,100);
//保存
img.save("D:/img.png");
利用QImage 对像素进行修改:
//绘图事件
void Widget::paintEvent(QPaintEvent *pe)
{//利用QImage 对像素进行修改
QPainter painter(this);
QImage img;
//加载资源文件
img.load(":/Image/sunny.png");
//修改像素点
for(int i =100;i<150;i++)
{
for(int j =100;j<150;j++)
{
QRgb value = qRgb(0,255,255);
img.setPixel(i,j,value);
}
}
painter.drawImage(0,0,img);
}
QImage与QPixmap的区别
- QPixmap主要是用于绘图,针对屏幕显示而最佳化设计,QImage主要是为图像I/O、图片访问和像素修改而设计的
- QPixmap依赖于所在的平台的绘图引擎,故例如反锯齿等一些效果在不同的平台上可能会有不同的显示效果,QImage使用Qt自身的绘图引擎,可在不同平台上具有相同的显示效果
- 由于QImage是独立于硬件的,也是一种QPaintDevice,因此我们可以在另一个线程中对其进行绘制,而不需要在GUI线程中处理,使用这一方式可以很大幅度提高UI响应速度。
- QImage可通过setPixpel()和pixel()等方法直接存取指定的像素。
QImage与QPixmap之间的转换:
- QImage转QPixmap
使用QPixmap的静态成员函数: fromImage()
QPixmap fromImage(const QImage & image,
Qt::ImageConversionFlags flags = Qt::AutoColor)
- QPixmap转QImage:
使用QPixmap类的成员函数: toImage()
QImage toImage() const
4、QPicture
QPicture是一个可以记录和重现QPainter命令的绘图设备。
QPicture将QPainter的命令序列化到一个IO设备,保存为一个平台独立的文件格式。这种格式有时候会是“元文件(meta- files)”。Qt的这种格式是二进制的,不同于某些本地的元文件,Qt的pictures文件没有内容上的限制,只要是能够被QPainter绘制的元素,不论是字体还是pixmap,或者是变换,都可以保存进一个picture中。
QPicture是平台无关的,因此它可以使用在多种设备之上,比如svg、pdf、ps、打印机或者屏幕。QPicture使用系统的分辨率,并且可以调整 QPainter来消除不同设备之间的显示差异。
如果要记录下QPainter的命令,首先要使用QPainter::begin()函数,将QPicture实例作为参数传递进去,以便告诉系统开始记录,记录完毕后使用QPainter::end()命令终止。代码示例如下:
//QPicture 绘图设备 可以记录和重现绘图指令
QPicture pic;
QPainter painter;
//指定画图设备 开始往Pic上画
painter.begin(&pic);
painter.setPen(QPen(Qt::black));
painter.drawEllipse(QPoint(150,150),100,100);
painter.end();//结束
//保存到磁盘
pic.save("D:/pic.aaa");//后缀无法直接打开
//绘图事件
void Widget::paintEvent(QPaintEvent *pe)
{
//重现QPicture绘图指令
QPicture pic;
pic.load("D:/pic.aaa");//加载绘图指令
QPainter painter(this);
painter.drawPicture(0,0,pic);
}