【Qt学习】07:绘图与绘图设备

OVERVIEW

绘图与绘图设备


一、QPainter

Qt 的绘图系统允许使用API在屏幕和其它打印设备上进行绘制,整个绘图系统基于QPainter,QPainterDevice和QPaintEngine三个类:

  • QPainter用来执行绘制的操作;
  • QPaintDevice是一个二维空间的抽象,这个二维空间允许QPainter在其上面进行绘制,也就是QPainter工作的空间;
  • QPaintEngine提供画笔(QPainter)在不同的设备上进行绘制的统一的接口。QPaintEngine类应用于QPainter和QPaintDevice之间,通常对开发人员是透明的。除非需要自定义一个设备,否则不需要关心QPaintEngine类。

可以将QPainter理解成画笔,QPaintDevice理解成使用画笔的地方,比如纸张、屏幕等;为了使用统一的画笔绘制设计了QPaintEngine类,该类让不同的纸张屏幕都能使用一种画笔。

下图给出了这三个类之间的层次结构:

Qt 的绘图系统实际上是使用QPainter在QPainterDevice上进行绘制,它们之间通过QPaintEngine进行通讯(翻译QPainter的指令)

cpp 复制代码
class PaintedWidget : public QWidget {
    Q_OBJECT
public:
    PaintedWidget(QWidget *parent = 0);
protected:
    //绘图事件
    void paintEvent(QPaintEvent *);
    int x = 0;
    int y = 0;
}

重写了QWidget的paintEvent()函数,以下是PaintedWidget的源代码:

cpp 复制代码
PaintedWidget::PaintedWidget(QWidget *parent):QWidget(parent) , ui(new Ui::PaintedWidget) {
    ui->setupUi(this);
    resize(800, 600);
    setWindowTitle(tr("Paint Demo"));
    connect(ui->btn, &QPushButton::clicked, this, [=](){
        //手动调用绘图事件 需要使用update函数更新
        x += 50; y += 50;
        update();
    });
}

void PaintedWidget::paintEvent(QPaintEvent *) {
	//实例化painter对象 this指定绘图设备
    QPainter painter(this);
    //设置画笔样式
    QPen pen(QColor(255, 0,0));
    pen.setWidth(3);
    pen.setStyle(Qt::DotLine);
    painter.setPen(pen);
    painter.setRenderHint(QPainter::Antialiasing);//抗锯齿但效率降低
    painter.translate(500, 0);//painter移动
    painter.save();//保存painter状态
    painter.restore();//恢复painter状态
    //设置画刷样式
    //QBrush brush(QColor(0, 255, 0));
    //painter.setBrush(brush);

    //drawLine
    painter.drawLine(QPoint(0, 0), QPoint(100, 100));
    //drawEllipse
    painter.drawEllipse(QPoint(100, 100), 50, 50);
    painter.drawEllipse(QPoint(100, 100), 100, 50);
    //drawRect
    painter.drawRect(QRect(100, 100, 100, 100));
    //drawText
    painter.drawText(QRect(200, 200, 100, 100), "this is a sentence.");

    //使用painter绘制资源图片
    painter.translate(-500, 0);//painter回归原点
    QPixmap pxm = QPixmap(":/res/img/backgroud.jpg").scaledToWidth(500);
    painter.drawPixmap(x, y, pxm);
}

在构造函数中设置了窗口的大小和标题,paintEvent()函数则是绘制的代码。

  1. 首先在栈上创建了一个QPainter对象(每次运行paintEvent()函数的时候,都会重建这个QPainter对象),注意这可能会引发某些细节问题:由于我们每次重建QPainter,因此第一次运行时所设置的画笔颜色、状态等,第二次再进入这个函数时就会全部丢失。有时候我们希望保存画笔状态,就必须自己保存数据,否则的话则需要将QPainter作为类的成员变量。
  2. QPainter接收一个QPaintDevice指针作为参数。QPaintDevice有很多子类,比如QImage,以及QWidget。QPaintDevice可以理解成要在哪里去绘制,而现在希望画在这个组件中因此传入的是 this 指针。
  3. QPainter有很多以 draw 开头的函数,用于各种图形的绘制,比如这里的drawLine(),drawRect()以及drawEllipse()等。当绘制轮廓线时,使用QPainter的pen()属性。

二、QPainterDevice

绘图设备是指继承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是针对屏幕进行特殊优化的,因此与实际的底层显示设备息息相关。(这里的显示设备并不是硬件,而是操作系统提供的原生绘图引擎)所以在不同的操作系统平台下,QPixmap的显示可能会有所差别。

cpp 复制代码
MainWindow::MainWindow(QWidget *parent):QMainWindow(parent), ui(new Ui::MainWindow) {
    ui->setupUi(this);
    QPixmap pixmap(600, 600);//绘图设备
    pixmap.fill(Qt::white);
    QPainter painter(&pixmap);//创建painter对象
    painter.setPen(QPen(Qt::green));
    painter.drawEllipse(QPoint(300, 300), 150, 100);
    pixmap.save("E:\\pixmap.png");
}

2.QBitmap

QBitmap继承自QPixmap,因此具有QPixmap的所有特性提供单色图像。

QBitmap的色深始终为1(即使用1个二进制位表示颜色)因此它所表示的颜色就只有两种黑和白,所以QBitmap实际上是只有黑白两色的图像数据。由于QBitmap色深小,因此只占用很少的存储空间,所以适合做光标文件和笔刷。

同个图像文件在QPixmap和QBitmap下的不同表现:

cpp 复制代码
void PaintWidget::paintEvent(QPaintEvent *) {
    QPixmap pixmap(":/Image/butterfly.png");
    QBitmap bitmap(":/Image/butterfly.png");
    QPainter painter(this);
    painter.drawPixmap(0, 0, pixmap);
    painter.drawPixmap(0, 100, bitmap);
}

这里给出了两张png图片:butterfly1.png是没有透明色的纯白背景,而butterfly.png是具有透明色的背景。分别使用QPixmap和QBitmap来加载出现区别:

  • 白色的背景在QBitmap中消失了,
  • 透明色在QBitmap中转换成了黑色;
  • 其他颜色则是使用点的疏密程度来体现的。

3.QImage

QPixmap使用底层平台的绘制系统进行绘制,无法提供像素级别的操作,

QImage使用独立于硬件的绘制系统,实际上是自己绘制自己提供了像素级别的操作,能够在不同系统之上提供相同的显示形式。

在绘图设备中绘制图形:

cpp 复制代码
MainWindow::MainWindow(QWidget *parent):QMainWindow(parent), ui(new Ui::MainWindow) {
    ui->setupUi(this);
    //QImage(对每个像素点都可以访问)
    QImage image(300, 300, QImage::Format_RGB32);
    image.fill(Qt::white);
    QPainter painter2(&image);
    painter2.setPen(QPen(Qt::blue));
    painter2.drawEllipse(QPoint(150, 150), 100, 150);
    image.save("E:\\QImage.png");
}

声明了一个QImage对象,大小是300 x 300,颜色模式是RGB32,即使用32位数值表示一个颜色的RGB值,也就是说每种颜色使用8位。然后对每个像素进行颜色赋值,从而构成了这个图像。我们可以把QImage想象成一个RGB颜色的二维数组,记录了每一像素的颜色。

在窗口中绘制图形:

cpp 复制代码
class MainWindow : public QMainWindow {
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    void paintEvent(QPaintEvent *event);
private:
    Ui::MainWindow *ui;
};

void MainWindow::paintEvent(QPaintEvent *event) {
    //利用QImage在窗口中绘制图形
    QPainter painter(this);
    QImage image = QImage(":/res/img/backgroud.jpg").scaledToWidth(800);
    //修改图像中的像素点
    for (int i = 50; i < 100; ++i) {
        for (int j = 50; j < 100; ++j) {
            QRgb value = qRgb(255, 0, 0);
            image.setPixel(i, j, value);
        }
    }
    painter.drawImage(QPoint(0, 0), image);
}

QImage与QPixmap的区别:

  1. 针对目的不同:QPixmap主要是用于绘图针对屏幕显示而最佳化设计,QImage主要是为图像I/O、图片访问和像素修改而设计的,
  2. 平台依赖不同:QPixmap依赖于所在的平台的绘图引擎,故例如反锯齿等一些效果在不同的平台上可能会有不同的显示效果,QImage使用Qt自身的绘图引擎,可在不同平台上具有相同的显示效果。
  3. 响应速度:由于QImage是独立于硬件的也是一种QPaintDevice,因此可以在另一个线程中对其进行绘制而不需要在GUI线程中处理,使用这一方式可以很大幅度提高UI响应速度。
  4. 像素操作:QImage可通过setPixpel()和pixel()等方法直接存取指定的像素。

QImage与QPixmap之间的转换:

  1. QImage转QPixmap:使用QPixmap的静态成员函数: fromImage()

    cpp 复制代码
    QPixmap	fromImage(const QImage &image, Qt::ImageConversionFlags flags = Qt::AutoColor)
  2. QPixmap转QImage:使用QPixmap类的成员函数: toImage()

    cpp 复制代码
    QImage toImage() const

4.QPicture

QPicture是可以记录和重现QPainter命令的绘图设备,QPicture将QPainter的命令序列化到一个IO设备,保存为一个平台独立的文件格式,这种格式有时候会是元文件meta- files。

Qt的这种格式是二进制的,不同于某些本地的元文件Qt的pictures文件没有内容上的限制,只要是能够被QPainter绘制的元素,不论是字体还是pixmap,或者是变换都可以保存进一个picture中。

QPicture是平台无关的因此可以使用在多种设备之上,比如svg、pdf、ps、打印机或者屏幕。

如果要记录下QPainter的命令,首先要使用QPainter::begin()函数,将QPicture实例作为参数传递进去(告诉系统开始记录),记录完毕后使用QPainter::end()命令终止。示例如下:

cpp 复制代码
void PaintWidget::paintEvent(QPaintEvent *) {
	//QPicture
    QPicture pic;
    QPainter painter;
	//1.记录绘图指令
    painter.begin(&pic);//开始向pic上绘制
	painter.setPen(QPen(Qt::blue));
    painter.drawEllipse(QPoint(150, 150), 100, 150);
    painter.setPen(QPen(Qt::cyan));
    painter.drawEllipse(QPoint(150, 150), 150, 100);
    painter.end();//结束向pic上绘制
    //2.将记录的操作保存到磁盘中
    pic.save("E:\\QPicture.lch");
    //3.重现绘图指令
    QPainter painter(this);
    QPicture pic;
    pic.load("E:\\QPicture.lch");
    painter.drawPicture(100, 100, pic);
}
相关推荐
Felix_One5 天前
Qt 串口通信避坑指南:QSerialPort 的 5 个常见问题
qt
blasit8 天前
笔记:Qt C++建立子线程做一个socket TCP常连接通信
c++·qt·tcp/ip
西岸行者13 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意13 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码13 天前
嵌入式学习路线
学习
毛小茛13 天前
计算机系统概论——校验码
学习
babe小鑫13 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
范特西.i13 天前
QT聊天项目(8)
开发语言·qt
winfreedoms13 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下13 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs