Qt学习笔记(三十):Qt 中的绘图


一、QPainter:绘图对象

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

QPainter 用来执行绘制的操作(相当于画家);

QPaintDevice 是一个绘图设备,允许 QPainter 在其上面进行绘制,也就是 QPainter 工作的空间(相当于画板);

QPaintEngine 提供了 QPainter 在不同的设备上进行绘制的统一的接口(相当于一种协议)。

QPaintEngine 类应用于 QPainter 和 QPaintDevice 之间,通常对开发人员是透明的。除非你需要自定义一个设备,否则你是不需要关心 QPaintEngine 这个类的。我们可以把 QPainter 理解成画家;把 QPaintDevice 理解成画板,比如纸张、屏幕等都可以作为画板;而对于纸张、屏幕而言,肯定要使用不同的画笔绘制,为了统一使用一种画笔,Qt 设计了 QPaintEngine 类,这个类让不同的纸张、屏幕都能使用一种画笔。

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

上面的示意图告诉我们,Qt 的绘图系统实际上是,使用 QPainter 在 QPainterDevice 上进行绘制,它们之间使用QPaintEngine 进行通讯(也就是翻译 QPainter 的指令)。

 

二、画图测试:

新建一个基于 QWidget 的项目,在头文件中重写 paintEvent() 事件方法:

protected:
    void paintEvent(QPaintEvent *); // 重写绘图事件

然后在源文件中实现 paintEvent() 事件方法,paintEvent() 就是当窗口发生重绘时触发的事件方法,那么我们需要画图的代码就应该写在 paintEvent() 事件方法中。

1、画一条直线:

// 窗体发生重绘时触发的事件
void Widget::paintEvent(QPaintEvent *)
{
    // 创建一个局部变量 QPainter 对象,即每次运行 paintEvent() 方法的时候都会重新创建该对象。
    // QPainter 接收一个 QPaintDevice 指针作为参数,QPaintDevice 表示绘图设备,
    // 即指定我们在哪个设备上进行绘图。this 表示当前主窗口对象,即在当前窗口上进行绘图。
    QPainter painter(this);
    
    // 画一条直线:参数为两个点(两点决定一条直线)
    painter.drawLine(QPoint(10, 10), QPoint(100, 100));
}

2、画一个矩形,并指定矩形边框的颜色:

void Widget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    
    // 设置一个红色的画笔
    painter.setPen(Qt::red);
    
    // 画一个矩形
    painter.drawRect(QRect(10, 10, 100, 100));
}

3、画一个椭圆形,指定边框的颜色和粗细,并指定图形的填充颜色:

void Widget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    
    // 设置一个绿色的画笔,并指定画笔的宽度为 8
    painter.setPen(QPen(Qt::green, 8));
    
    // 设置一个蓝色的画刷,用来填充绘制的图形
    painter.setBrush(Qt::blue);
    
    // 画一个椭圆:指定圆心,宽和高
    painter.drawEllipse(QPoint(100, 100), 80, 50);
}

 

三、QPaintDevice:绘图设备

绘图设备是指继承 QPainterDevice 的子类。

Qt一共提供了四个这样的类,分别是 QPixmap、QBitmap、QImage 和 QPicture。

  • QPixmap:专门为图像在屏幕上的显示做了优化;

  • QBitmap:是 QPixmap 的子类,它的色深限定为 1,可以使用 QPixmap 的 isQBitmap() 方法来确定这个 QPixmap 是不是一个 QBitmap;

  • QImage:专门为图像的像素级访问做了优化;

  • QPicture:可以记录和重现 QPainter 的各条指令;

  •  

1、QPixmap:可以接收一个字符串作为一个文件的路径来显示这个文件,使用 QPinter 的 drawPixmap() 方法可以把这个文件绘制到一个指定的绘图设备上。QPixmap 是针对屏幕进行特殊优化的,因此,它与实际的底层显示设备息息相关。注意,这里说的显示设备并不是硬件,而是操作系统提供的原生的绘图引擎。所以,在不同的操作系统平台下,QPixmap 的显示可能会有所差别。

void Widget::paintEvent(QPaintEvent *)
{
    // 创建一个绘图对象,指定绘图设备为当前主窗口
    QPainter painter(this);
    
    // 绘制一个图像:参数为显示图像的开始 x、y 坐标,和要显示的图像
    // 显示的是图像的原始尺寸大小
    painter.drawPixmap(10, 10, QPixmap(":/Image/Luffy.png"));
}

还可以自己指定显示图像的大小:

void Widget::paintEvent(QPaintEvent *)
{
    // 创建一个绘图对象,指定绘图设备为当前主窗口
    QPainter painter(this);
    
    // 绘制一个图像:参数为显示图像的开始 x、y 坐标,和显示图像的宽度和高度,以及要显示的图像
    painter.drawPixmap(10, 10, 100, 100, QPixmap(":/Image/Luffy.png"));
}

还可以在指定的控件上绘制图像,我最开始的做法如下:

void Widget::paintEvent(QPaintEvent *)
{
    // 创建一个 QLabel 对象
    QLabel *label = new QLabel(this);
    label->setGeometry(10, 10, 200, 200);
    
    // 创建一个绘图对象,指定绘图设备为 QLabel
    QPainter painter(label);
    
    // 绘制一个图像
    painter.drawPixmap(0, 0, 200, 200, QPixmap(":/Image/Luffy.png"));
}

程序运行的时候报错:

QWidget::paintEngine: Should no longer be called

QPainter::begin: Paint device returned engine == 0, type: 1

后来测试发现,好像是想要在哪个控件上绘制,就要在哪个控件类中重写 paintEvent() 方法才行。比如想要在 QLabel 上绘图,就需要新建一个类继承于 QLabel,然后重写 paintEvent() 方法,如下所示:

mylabel.h:

#include <QLabel>

class MyLabel : public QLabel
{
    Q_OBJECT
public:
    explicit MyLabel(QWidget *parent = 0);
    
protected:
    void paintEvent(QPaintEvent *); // 重写绘图事件
};

mylabel.cpp:

#include "mylabel.h"
#include <QPainter>

MyLabel::MyLabel(QWidget *parent) : QLabel(parent)
{
}

// 在控件发生重绘时触发的事件
void MyLabel::paintEvent(QPaintEvent *)
{
    // 创建一个绘图对象,指定绘图设备为 QLabel
    QPainter painter(this);
    
    // 绘制一个图像
    painter.drawPixmap(0, 0, this->width(), this->height(), QPixmap(":/Image/Luffy.png"));
}

在主窗口程序中调用 MyLabel:  

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    
    // 调用 Mylabel
    MyLabel *label = new MyLabel(this);
    label->setGeometry(10, 10, 100, 100);
}

 

2、QBitmap:继承自 QPixmap,因此具有 QPixmap 的所有特性。QBitmap 的色深始终为 1,色深这个概念来自于计算机图形学,是指用于表现颜色的二进制位数。我们知道,计算机中的数据都是使用二进制来表示的。为了表示一种颜色,我们也会使用二进制。比如我们要表示 8 种颜色,需要用 3 个二进制位,这时我们就说色深是 3。因此,所谓色深为 1,也就是使用 1 个二进制表示颜色。一个二进制数只有两种状态,0 和 1,因此他所表示的颜色只有两种,黑和白。所以,QBitmap 实际上是只有黑白两色的图像数据。

由于 QBitmap 色深小,因此只占用很少的内存空间,所以适合做光标文件和笔刷。

下面我们来看同一个图像文件在 QPixmap 和 QBitmap 下的不同表现:

void Widget::paintEvent(QPaintEvent *)
{
    // 创建两个 QPixmap 图像
    QPixmap pixmap(":/Image/butterfly.png");
    QPixmap pixmap1(":/Image/butterfly1.png");
    
    // 创建两个 QBitmap 图像
    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);
}

显示效果如下:

我们使用了两张图片,butterfly.png 具有透明色的背景,butterfly1.png 是没有透明色的纯白背景。使用 QPixmap 加载出的图像是正常效果;但是使用 QBitmap 加载,可以看到 butterfly.png 的透明色背景变成了黑色,而 butterfly1.png 的纯白色背景反而消失了,变成了透明色。至于图像的其他颜色,则是使用点的疏密程度来表现的。

 

3、QImage: QPixmap 使用底层平台绘制系统进行绘制,无法提供像素级别的操作;而 QImage 则是使用独立于硬件的绘制系统,实际上是自己绘制自己,因此提供了像素级别的操作,并且能够在不同系统之上提供一个一致的显示形式。

我们声明一个 QImage 对象,大小是 200x200,颜色模式是 RGB32,即使用 32 位数值表示一个颜色的 RGB 值,也就是说每种颜色使用 8 位。然后我们为每个像素进行颜色赋值,从而构成了这个图像。

void Widget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    
    // 创建一个 QImage 对象
    QImage image(200, 200, QImage::Format_RGB32);
    
    QRgb value;
    
    // 将图片背景填充为绿色
    image.fill(Qt::green);
    
    // 改变指定区域的像素点的值
    for (int i = 50; i < 100; i++)
    {
        for(int j = 50; j < 100; j++)
        {
            value = qRgb(255, 0, 0);        // 红色
            image.setPixel(i, j, value);    // 设置每个像素点的值
        }
    }
    
    // 将图像绘制到窗口中
    painter.drawImage(10, 10, image);
}

QImage 与 QPixmap 的区别:

  • QPixmap 主要用于绘图,针对屏幕显示而最佳化设计,QImage 主要是为图像 I/O、图片访问和像素修改而设计的。

  • QPixmap 依赖于所在平台的绘图引擎,顾例如反锯齿等一些效果在不同的平台上可能会有不同的效果,QImage 使用 Qt 自身的绘图引擎,可在不同平台上具有相同的显示效果。

  • 由于 QImage 是独立于硬件的,也是一种 QPaintDevie,因此我们可以在另一个线程中对其进行绘制,而不需要在 GUI 线程中处理,使用这一方式可以很大程序提高 UI 响应速度。

  • QImage 可通过 setPixel() 和 pixel() 等方法直接存取指定的像素。

QImage 和 QPixmap 的相互转换:

	// 将 QImage 转换成 QPixmap:使用 QPixmap 的静态成员函数 fromImage();
    QPixmap pixmap = QPixmap::fromImage(image);
    
    // 将 QPixmap 转换成 QImage:使用 QPixmap 类的成员函数 toImage();
    QImage img = pixmap.toImage();

 

4、QPicture:这是一个可以记录和重现 QPainter 命令的绘图设备。QPicture 将 QPainter 的命令序列化到一个 IO 设备,保存为一个平台独立的文件格式。这种格式有时候会是 “元文件(mate-files)”。Qt 的这种格式是二进制的,不同于某些本地的元文件,Qt 的 pictures 没有内容上的限制,只要是能够被 QPainter 绘制的元素,不论是字体,还是 pixmap,或者是变换,都可以保存在一个 picture 文件中。

QPicture 是平台无关的,因此他可以使用在多种设备之上,比如 svg、pdf、ps、打印机或者屏幕。QPicture 使用系统的分辨率,并且可以调整 QPainter 来消除不同设备之间的显示差异。

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

void Widget::paintEvent(QPaintEvent *)
{
    QPicture picture;
    QPainter painter;
    
    // 将图像绘制到 QPicture 中,并保存到文件
    // 开始记录
    painter.begin(&picture);
    
    // 开始绘图
    painter.drawEllipse(10, 10, 100, 50);       // 画一个椭圆
    painter.fillRect(10, 70, 100, 50, Qt::red); // 填充一个矩形
    
    // 结束记录
    painter.end();
    
    // 保存文件
    picture.save("D://drawing.pic");
    
    // 加载保存的绘图动作
    picture.load("D://drawing.pic");
    
    // 将保存的绘图重新绘制到设备上 
    painter.begin(this);
    painter.drawPicture(10, 10, picture);
    painter.end();
}

 

四、手动更新窗口

如下图所示:实现一个当按钮被点击时,图片向右移动的功能,使用绘图事件实现。

widget.h:

class Widget : public QWidget
{
    Q_OBJECT
    
public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();
    
private:
    Ui::Widget *ui;
    
    // 声明一个全局变量
    int x = 0;
    
protected:
    void paintEvent(QPaintEvent *); // 重写绘图事件
    
private slots:
    void on_btnMove_clicked();
};

widget.cpp:

#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QPainter>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::paintEvent(QPaintEvent *)
{
    // 在主窗口上画一个图片
    QPainter painter(this);    
    painter.drawPixmap(x, 100, QPixmap(":/Image/LuffyQ.png"));
}

// 点击一下移动按钮,重绘窗口,将图片向右移动
void Widget::on_btnMove_clicked()
{
    // 绘制图片的 x 坐标增加,向右移动
    x += 20;
    
    // 如果图片移动超出窗口宽度,则恢复 x 坐标为初始值
    if (x > this->width())
    {
        x = 0;
    }
    
    // 重绘窗口,整个窗口都会重绘
//    this->update();
    
    // 重绘指定的区域
    this->update(x - 20, 100, 150, 150);
}

 

  • 8
    点赞
  • 95
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Qt绘制图形时,可以使用QPainter类来完成。如果需要在绘制图形时添加延时效果,可以通过使用Qt提供的延时函数QThread::msleep()来实现。 使用延时函数的方法是在绘制每个图形元素之后,调用一次延时函数,将延时的时间设置为适当的值。这样可以在绘制完一个图形元素后暂停一段时间,再继续绘制下一个图形元素,从而在绘制过程产生延时效果。 下面是一个简单的示例代码,演示了如何使用Qt实现延时的绘图效果: ```cpp #include <QPainter> #include <QThread> void DelayedDrawing() { // 创建一个QImage对象作为绘制目标 QImage image(300, 300, QImage::Format_RGB32); image.fill(Qt::white); // 创建一个QPainter对象进行绘制 QPainter painter(&image); // 按照需求绘制图形元素 for (int i = 0; i < 5; i++) { // 绘制一个矩形 painter.drawRect(i * 10, i * 10, 100, 100); // 刷新画布,将绘制的内容显示出来 painter.end(); // 延时500毫秒 QThread::msleep(500); // 重新设置Painter对象的绘制目标 painter.begin(&image); // 清除前一次的绘制内容 painter.eraseRect(0, 0, 300, 300); } // 绘制完毕后,将最终结果显示出来 painter.end(); painter.begin(&image); painter.end(); // display the image... } ``` 以上代码通过在绘制每个矩形后延时500毫秒,从而在绘制过程产生延时效果。可以根据实际需求调整延时的时间和绘制的图形元素,以达到想要的效果。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值