QLabel显示图像性能低,影响QChart效果,动态仪表的优化

背景:

之前项目中用到了动态图表QChart和动态仪表(QPainter、QLabel),其中QLabel实现了故障灯。

QtChart做个动态更新的曲线_qtchart坐标轴动态变化-CSDN博客

Qt绘制动态仪表(模仿汽车仪表指针、故障灯)-CSDN博客

原本以为万事大吉。结果测试中发现,仪表和故障灯动态更新时,图表中的曲线闪烁严重。于是开始优化。

先说结果,最终使用画笔重做了故障灯,完美解决,很多朋友还用画笔模拟动画,性能也不错。

下面是分析过程。

图表:

之前做图表时,涉及到series动态更新,需要更新前后解绑和重新绑定坐标轴,亦即detach/attach。所以本次依然尝试在关键的地方重新绑定坐标轴。

这里直接说结果,无论如何还是不解决图表受影响的问题。

仪表优化:

网上看到很多人说QLabel呈现图像消耗cpu,性能不好,但我尝试的结果是,它真正最拉胯的应该是绘制的过程,也就是给它设定图像资源的这个过程,比如动态仪表的故障灯,当数值超过限度时需要设置QLabel加载报警图像。这就影响性能了。但QLabel的好处是使用简单,可以直接使用QMovie播放gif。

首先我用QPainter重做了一个故障灯,gif就没戏了,只能把故障灯亮起的状态单独做个静态图片,然后用定时器切换它,在paintEvent中使用画笔绘制图片。据说,画笔使用的是GPU。

先做一个widget子类,用于取代label显示图像。

guage_pic.h:

cpp 复制代码
/**************************************************************************************************
 ** File name:      guage_pic.h (Guage_Pic)
 ** Created by:     Henrick.Nie at 2024-9-23
 ** Used for:       Show the picture.
 ** Using method:
 **
 **     Guage_Pic *wPic = new Guage_Pic;
 **     wPic->f_Init(...);
 **     wPic->f_SetWarning(...);
 **
 **************************************************************************************************/

#ifndef GUAGE_PIC_H
#define GUAGE_PIC_H

#include <QWidget>
#include <QPainter>
#include <QTimerEvent>
#include <QDebug>

class Guage_Pic : public QWidget
{
    Q_OBJECT
public:
    explicit Guage_Pic(QWidget *parent = nullptr);

    inline void f_Init(const QString &sPicPath1, const QString &sPicPath2, const int &iMsec = 500)
    {
        m_sPicPath1 = sPicPath1;
        m_sPicPath2 = sPicPath2;
        m_iMsec = iMsec;
        m_sPicPath = m_sPicPath1;
    }
    void f_SetWarning(const bool &bIsOn);
    bool f_GetIsOn() const { return m_bIsOn; }

private:
    QString m_sPicPath, m_sPicPath1, m_sPicPath2;
    int m_iTimerID = 0;
    int m_iMsec = 0;
    bool m_bIsOn = false;

    void paintEvent(QPaintEvent *event);
    void f_Draw(QPainter *p);
    void timerEvent(QTimerEvent *event);
};

#endif // GUAGE_PIC_H

guage_pic.cpp:

cpp 复制代码
#include "guage_pic.h"

Guage_Pic::Guage_Pic(QWidget *parent) :
    QWidget(parent)
{

}
void Guage_Pic::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event)
    QPainter painter(this);
    f_Draw(&painter);
}
void Guage_Pic::f_Draw(QPainter *p)
{
    QPixmap pic(m_sPicPath);
    int iPicWidth = pic.width(), iPicHeight = pic.height();
    int iLeft = (width() - iPicWidth) / 2, iTop = (height() - iPicHeight) / 2;
    p->save();
    p->drawPixmap(iLeft, iTop, iPicWidth, iPicHeight, pic);
    p->restore();
    p->end();
}
void Guage_Pic::f_SetWarning(const bool &bIsOn)
{
    if (bIsOn == m_bIsOn)
    {
        return;
    }

    m_bIsOn = bIsOn;
    m_sPicPath.clear();

    if (bIsOn)
    {
        m_iTimerID = this->startTimer(m_iMsec);
        m_sPicPath = m_sPicPath2;
//        qDebug() << this->objectName() << "timeerid = " << m_iTimerID;
    }
    else
    {
        if (0 != m_iTimerID)
        {
//            qDebug() << this->objectName()  << "timeerid kill " << m_iTimerID;
            this->killTimer(m_iTimerID);
        }

        m_sPicPath = m_sPicPath1;
    }

    this->update();
}
void Guage_Pic::timerEvent(QTimerEvent *event)
{
    if (event->timerId() != m_iTimerID)
    {
        return;
    }

//    qDebug() << this->objectName() << m_iTimerID;
    m_sPicPath = (m_sPicPath != m_sPicPath1) ? m_sPicPath1 : m_sPicPath2;
    this->update();
}

然后重做一个故障灯面板。

guage_warning_pic.h:

cpp 复制代码
/**************************************************************************************************
 ** File name:      guage_warning_pic.h (Guage_Warning_Pic)
 ** Created by:     Henrick.Nie at 2024-9-24
 ** Used for:       Show the warning light by the painter.
 ** Using method:
 **
 **     Firstly, a image resource path is required.
 **
 **         obj->f_Init(":/images/");
 **
 **************************************************************************************************/
#ifndef GUAGE_WARNING_PIC_H
#define GUAGE_WARNING_PIC_H

#include <QWidget>
#include <QFrame>

namespace Ui {
class Guage_Warning_Pic;
}

class Guage_Warning_Pic : public QWidget
{
    Q_OBJECT

public:
    explicit Guage_Warning_Pic(QWidget *parent = nullptr);
    ~Guage_Warning_Pic();

    enum EWarningType { eNone, eWater, eOil, eFuel, eEngine, eBattery, eBrake };

    void f_Init(const QString &sRcPathPrefix);
    void f_SetStatus(const EWarningType &eType, const bool &bIsWarning);

private:
    Ui::Guage_Warning_Pic *ui;
    QString m_sRcPathPrefix;
    EWarningType m_eType = eNone;
};

#endif // GUAGE_WARNING_PIC_H

guage_warning_pic.cpp:

cpp 复制代码
#include "guage_warning_pic.h"
#include "ui_guage_warning_pic.h"

Guage_Warning_Pic::Guage_Warning_Pic(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Guage_Warning_Pic)
{
    ui->setupUi(this);
    this->setAttribute(Qt::WA_StyledBackground, true);
    this->setStyleSheet("background: black;");
    ui->gridLayout->setSpacing(0);
}

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

void Guage_Warning_Pic::f_Init(const QString &sRcPathPrefix)
{
    m_sRcPathPrefix = sRcPathPrefix;

    ui->wWater  ->f_Init(m_sRcPathPrefix + "warning_water.jpg",   m_sRcPathPrefix + "warning_water_on.jpg");
    ui->wOil    ->f_Init(m_sRcPathPrefix + "warning_oil.jpg",     m_sRcPathPrefix + "warning_oil_on.jpg");
    ui->wFuel   ->f_Init(m_sRcPathPrefix + "warning_fuel.jpg",    m_sRcPathPrefix + "warning_fuel_on.jpg");
    ui->wEngine ->f_Init(m_sRcPathPrefix + "warning_engine.jpg",  m_sRcPathPrefix + "warning_engine_on.jpg");
    ui->wBattery->f_Init(m_sRcPathPrefix + "warning_battery.jpg", m_sRcPathPrefix + "warning_battery_on.jpg");
    ui->wBrake  ->f_Init(m_sRcPathPrefix + "warning_brake.jpg",   m_sRcPathPrefix + "warning_brake_on.jpg");
}
void Guage_Warning_Pic::f_SetStatus(const EWarningType &eType, const bool &bIsWarning)
{
    Guage_Pic *wPic;
    switch (eType) {
    case eWater:
        wPic = ui->wWater;
        break;
    case eOil:
        wPic = ui->wOil;
        break;
    case eFuel:
        wPic = ui->wFuel;
        break;
    case eEngine:
        wPic = ui->wEngine;
        break;
    case eBattery:
        wPic = ui->wBattery;
        break;
    case eBrake:
        wPic = ui->wBrake;
        break;
    default:
        wPic = nullptr;
        break;
    }

    if (nullptr == wPic)
    {
        return;
    }

    if (wPic->f_GetIsOn() != bIsWarning)
    {
        wPic->f_SetWarning(bIsWarning);
    }
}

guage_warning_pic.ui:

cpp 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Guage_Warning_Pic</class>
 <widget class="QWidget" name="Guage_Warning_Pic">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>267</width>
    <height>120</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <layout class="QGridLayout" name="gridLayout">
   <item row="0" column="2">
    <widget class="Guage_Pic" name="wFuel" native="true"/>
   </item>
   <item row="1" column="1">
    <widget class="Guage_Pic" name="wBattery" native="true"/>
   </item>
   <item row="0" column="1">
    <widget class="Guage_Pic" name="wOil" native="true"/>
   </item>
   <item row="1" column="2">
    <widget class="Guage_Pic" name="wBrake" native="true"/>
   </item>
   <item row="1" column="0">
    <widget class="Guage_Pic" name="wEngine" native="true"/>
   </item>
   <item row="0" column="0">
    <widget class="Guage_Pic" name="wWater" native="true"/>
   </item>
  </layout>
 </widget>
 <customwidgets>
  <customwidget>
   <class>Guage_Pic</class>
   <extends>QWidget</extends>
   <header>guage_pic.h</header>
   <container>1</container>
  </customwidget>
 </customwidgets>
 <resources/>
 <connections/>
</ui>

结合前篇动态仪表的故障灯用法,很容易就实现。

cpp 复制代码
void MainWindow::on_sdrWaterTemp_valueChanged(int value)
{
    if (m_iSbHandle != 2)
    {
        m_iSbHandle = 1;
        ui->sdrWaterTemp_2->setValue(value);
//        qDebug() << "1";
    }

    ui->labDegree_WaterTemp->setText(QString::number(value));
    ui->labDegree_PoloWater->setText(QString::number(ui->lab_WaterTempPolo->f_GetDegrees()));
    ui->labValueWaterTemp->setText(QString::number(value));
    ui->lab_WaterTemp->f_SetTemp(value);
    ui->lab_WaterTempPolo->f_SetValue(value);
    ui->wWarning->f_SetStatus(Guage_Warning::eWater, (value > 120));
    ui->wWarningPic->f_SetStatus(Guage_Warning_Pic::eWater, (value > 120));
    m_iSbHandle = 0;
}

void MainWindow::on_sdrWaterTemp_2_valueChanged(int value)
{
    if (m_iSbHandle != 1)
    {
        m_iSbHandle = 2;
        ui->sdrWaterTemp->setValue(value);
//        qDebug() << "2";
    }
}

void MainWindow::on_sdrFuel_valueChanged(int value)
{
    ui->labDegree_PoloFuel->setText(QString::number(ui->lab_FuelPolo->f_GetDegrees()));
    ui->labValueFuel->setText(QString::number(value));
    ui->lab_FuelPolo->f_SetValue(value);
    ui->wWarning->f_SetStatus(Guage_Warning::eFuel, (value < 10));
    ui->wWarningPic->f_SetStatus(Guage_Warning_Pic::eFuel, (value < 10));
}

void MainWindow::on_sdrTacho_valueChanged(int value)
{
    ui->lab_Tacho->f_SetValue(value);
    ui->labValueTacho->setText(QString::number(value));
    ui->labDegree_Tacho->setText(QString::number(ui->lab_Tacho->f_GetDegrees()));
    ui->wWarning->f_SetStatus(Guage_Warning::eEngine, (value > 6500));
    ui->wWarningPic->f_SetStatus(Guage_Warning_Pic::eEngine, (value > 6500));
}

调用跟以前一样,一行搞定。

但上面的代码中,其实已经优化了另外一点。当仪表的数值连续变化时,总是不断调用更新故障灯代码,其实之前这样做很不好。于是都加了标记,有变化再更新设置,没变化就return。

当然,我也按照这个想法改善了之前的label。

Label优化:

guage_labelmovie.h:

cpp 复制代码
/**************************************************************************************************
 ** File name:      guage_labelmovie.h (Guage_LabelMovie)
 ** Created by:     Henrick.Nie at 2024-9-10
 ** Used for:       Show the picture (jpg/gifs).
 **************************************************************************************************/

#ifndef GUAGE_LABELMOVIE_H
#define GUAGE_LABELMOVIE_H

#include <QLabel>
#include <QPainter>
#include <QMovie>
#include <QDebug>

class Guage_LabelMovie : public QLabel
{
    Q_OBJECT
public:
    explicit Guage_LabelMovie(QWidget *parent = nullptr);

    void f_SetJpg(const QString &sFileName);
    void f_SetGif(const QString &sFileName);
    bool f_GetIsGif() const { return m_bIsGif; }

private:
    QMovie m_movie;
    bool m_bIsGif = false;
};

#endif // GUAGE_LABELMOVIE_H

guage_labelmovie.cpp:

cpp 复制代码
#include "guage_labelmovie.h"

Guage_LabelMovie::Guage_LabelMovie(QWidget *parent) :
    QLabel(parent)
{

}
void Guage_LabelMovie::f_SetJpg(const QString &sFileName)
{
    if (m_bIsGif)
    {
        m_bIsGif = false;
        this->setPixmap(QPixmap(sFileName));
    }
}
void Guage_LabelMovie::f_SetGif(const QString &sFileName)
{
    if (!m_bIsGif)
    {
        m_bIsGif = true;
        m_movie.stop();
        m_movie.setFileName(sFileName);
        this->setMovie(&m_movie);
        m_movie.start();
    }
}

故障灯面板也加标记,更妥善。

guage_warning.cpp:

cpp 复制代码
#include "guage_warning.h"
#include "ui_guage_warning.h"

Guage_Warning::Guage_Warning(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Guage_Warning)
{
    ui->setupUi(this);
//    this->setAttribute(Qt::WA_StyledBackground, true);
    this->setStyleSheet("background: black;");
    ui->gridLayout->setSpacing(0);
}

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

void Guage_Warning::f_Init(const QString &sRcPathPrefix)
{
    m_sRcPathPrefix = sRcPathPrefix;

    ui->labWater  ->setPixmap(QPixmap(m_sRcPathPrefix + "warning_water.jpg"));
    ui->labOil    ->setPixmap(QPixmap(m_sRcPathPrefix + "warning_oil.jpg"));
    ui->labFuel   ->setPixmap(QPixmap(m_sRcPathPrefix + "warning_fuel.jpg"));
    ui->labEngine ->setPixmap(QPixmap(m_sRcPathPrefix + "warning_engine.jpg"));
    ui->labBattery->setPixmap(QPixmap(m_sRcPathPrefix + "warning_battery.jpg"));
    ui->labBrake  ->setPixmap(QPixmap(m_sRcPathPrefix + "warning_brake.jpg"));
}
void Guage_Warning::f_SetStatus(const EWarningType &eType, const bool &bIsWarning)
{
    Guage_LabelMovie *lab;
    QString sJpg, sGif;
    switch (eType) {
    case eWater:
        lab = ui->labWater;
        sJpg = "warning_water.jpg";
        sGif = "warning_water.gif";
        break;
    case eOil:
        lab = ui->labOil;
        sJpg = "warning_oil.jpg";
        sGif = "warning_oil.gif";
        break;
    case eFuel:
        lab = ui->labFuel;
        sJpg = "warning_fuel.jpg";
        sGif = "warning_fuel.gif";
        break;
    case eEngine:
        lab = ui->labEngine;
        sJpg = "warning_engine.jpg";
        sGif = "warning_engine.gif";
        break;
    case eBattery:
        lab = ui->labBattery;
        sJpg = "warning_battery.jpg";
        sGif = "warning_battery.gif";
        break;
    case eBrake:
        lab = ui->labBrake;
        sJpg = "warning_brake.jpg";
        sGif = "warning_brake.gif";
        break;
    default:
        lab = nullptr;
        break;
    }

    if ((nullptr == lab) || (lab->f_GetIsGif() == bIsWarning))//标记判断
    {
        return;
    }

    void (Guage_LabelMovie::*f_ptr)(const QString &sFileName)
            = bIsWarning
            ? &Guage_LabelMovie::f_SetGif
            : &Guage_LabelMovie::f_SetJpg;
    QString sPic = bIsWarning ? sGif : sJpg;
    (lab->*f_ptr)(m_sRcPathPrefix + sPic);
}

总结:

对于我的项目,即使优化了label,也还是会影响chart,所以最终使用了画笔实现故障灯。

可我还是保留了label的实现方法,从代码也能看出来,label的实现就是好用,很简洁。如果是不用频繁更新显示的情况,还是label更方便。

所以都整理了一下,以后做项目会用到。

本文完。

相关推荐
wkd_0076 小时前
【Qt | QList 】QList<T> 容器详细介绍和例子代码
开发语言·qt·qlist·qlist 详细介绍·qlist 使用总结
得鹿梦鱼、8 小时前
Qt/C++ 了解NTFS文件系统,解析0x80 $Data属性,获取Run Lists数据列表
c++·qt·ntfs mft
得鹿梦鱼、8 小时前
Qt/C++ 了解NTFS文件系统,遍历Run Lists数据列表,读取0x30 $FILE_NAME属性,获取所有文件/目录数据
c++·qt·ntfs mft
阳光开朗_大男孩儿10 小时前
阻塞信号(`blockSignals(true)`)的作用
linux·c++·qt·ui
flower98032310 小时前
基于QT的C++中小项目软件开发架构源码
c++·qt·架构
martian66512 小时前
QT开发:详解 Qt 多线程编程核心类 QThread:基本概念与使用方法
开发语言·c++·qt
景天科技苑13 小时前
【python】PyQt5中QButtonGroup的详细用法解析与应用实战
开发语言·python·qt·pyqt5·qbuttongroup
不是笨小孩i19 小时前
【QT】QWidget 重要属性
开发语言·qt
cefler21 小时前
【QT】系统-下
开发语言·qt
比特 GOK21 小时前
9_23_QT窗口
qt