Qt图形渲染相关例子

例1:嵌入式风格 - 轻量级工业仪表盘

widget.h
复制代码
#ifndef WIDGET_H          // 防止头文件重复包含
#define WIDGET_H

#include <QWidget>        // Qt窗口基类
#include <QTimer>         // 定时器类

class Widget : public QWidget   // 继承QWidget的自定义窗口类
{
    Q_OBJECT              // Qt元对象宏,启用信号槽机制

public:
    Widget(QWidget *parent = nullptr);  // 构造函数,parent指定父窗口
    ~Widget();                           // 析构函数

protected:
    void paintEvent(QPaintEvent *event) override;  // 重写绘制事件

private slots:
    void updateValue();   // 私有槽函数:更新数值

private:
    double m_value;       // 当前仪表盘数值 (0-100)
    QTimer *m_timer;      // 定时器指针,用于周期性更新
};

#endif // WIDGET_H        // 结束头文件保护
widget.cpp
复制代码
#include "widget.h"               // 引入自定义头文件
#include <QPainter>               // 绘图类
#include <QtMath>                 // 数学函数库
#include <QRandomGenerator>       // 随机数生成器

Widget::Widget(QWidget *parent)   // 构造函数实现
    : QWidget(parent), m_value(50) // 初始化父类和成员变量m_value为50
{
    setFixedSize(300, 320);       // 设置窗口固定大小,模拟嵌入式屏幕分辨率
    m_timer = new QTimer(this);   // 创建定时器对象,指定父对象为this以便自动释放
    connect(m_timer, &QTimer::timeout, this, &Widget::updateValue); // 连接定时器超时信号到更新槽
    m_timer->start(1000);         // 启动定时器,间隔1000毫秒(1秒)
}

Widget::~Widget() {}             // 析构函数,Qt会自动删除子对象,无需手动释放

void Widget::paintEvent(QPaintEvent *)  // 重写绘制事件,参数未使用故省略名称
{
    QPainter painter(this);             // 创建画家对象,绘制目标为本窗口
    painter.setRenderHint(QPainter::Antialiasing); // 开启抗锯齿渲染

    // 绘制黑色背景
    painter.fillRect(rect(), Qt::black); // 用黑色填充整个窗口区域

    // 计算表盘中心点和半径
    int centerX = width() / 2;           // 中心X坐标
    int centerY = height() / 2 + 10;     // 中心Y坐标,向下偏移10像素
    int radius = qMin(centerX, centerY) - 20; // 表盘半径,取宽高较小值减20

    // 绘制白色外圈
    painter.setPen(QPen(Qt::white, 4));  // 设置画笔:白色,宽度4像素
    painter.drawEllipse(QPoint(centerX, centerY), radius, radius); // 画椭圆(正圆)

    // 绘制刻度线和数字
    painter.setPen(QPen(Qt::cyan, 2));   // 设置画笔:青色,宽度2像素
    for (int i = 0; i <= 8; ++i) {       // 循环9个刻度(0到8)
        double angle = (180.0 + i * 22.5) * M_PI / 180.0; // 角度从180度开始,步长22.5度,转换为弧度
        int x1 = centerX + (radius - 15) * cos(angle);    // 刻度线起点X
        int y1 = centerY - (radius - 15) * sin(angle);    // 刻度线起点Y(Y轴向下为正,故用减法)
        int x2 = centerX + (radius - 30) * cos(angle);    // 刻度线终点X
        int y2 = centerY - (radius - 30) * sin(angle);    // 刻度线终点Y
        painter.drawLine(x1, y1, x2, y2);                 // 绘制刻度线

        // 显示刻度对应的数值(0-200)
        QString numStr = QString::number(i * 25);         // 将索引乘以25得到数值字符串
        painter.setFont(QFont("Arial", 10));              // 设置字体:Arial,字号10
        painter.setPen(Qt::green);                        // 设置画笔颜色:绿色
        int nx = centerX + (radius - 45) * cos(angle) - 10; // 数字显示位置的X坐标
        int ny = centerY - (radius - 40) * sin(angle) + 5;  // 数字显示位置的Y坐标
        painter.drawText(nx, ny, numStr);                 // 在指定位置绘制文本
    }

    // 绘制红色指针
    double pointerAngle = (180.0 + m_value * 18.0 / 10.0) * M_PI / 180.0; // 将数值映射到180度范围(0对应180°,100对应360°)
    int pointerLen = radius - 35;                         // 指针长度
    int px = centerX + pointerLen * cos(pointerAngle);    // 指针尖端X坐标
    int py = centerY - pointerLen * sin(pointerAngle);    // 指针尖端Y坐标
    painter.setPen(QPen(Qt::red, 3));                     // 设置画笔:红色,宽度3
    painter.drawLine(centerX, centerY, px, py);           // 从中心画线到指针尖端

    // 绘制中心黄色圆点
    painter.setBrush(Qt::yellow);                         // 设置画刷:黄色
    painter.drawEllipse(QPoint(centerX, centerY), 6, 6);  // 画半径为6的实心圆

    // 显示当前数值文本
    painter.setPen(Qt::white);                            // 设置画笔:白色
    painter.setFont(QFont("Arial", 14, QFont::Bold));     // 设置字体:Arial,14号,粗体
    painter.drawText(QRect(0, height()-40, width(), 30), Qt::AlignCenter, // 在底部区域居中显示
                     QString("Speed: %1 km/h").arg(m_value, 0, 'f', 1));  // 格式化数值,保留一位小数
}

void Widget::updateValue()        // 槽函数实现:更新数值
{
    // 模拟传感器数据波动:生成10~90之间的随机浮点数
    m_value = QRandomGenerator::global()->bounded(80.0) + 10.0;
    update();                     // 调用update()请求重绘,触发paintEvent
}

例2:图形渲染基础 - 动态粒子系统

particlewidget.h
复制代码
#ifndef PARTICLEWIDGET_H        // 防止头文件重复包含
#define PARTICLEWIDGET_H

#include <QWidget>              // Qt窗口基类
#include <QList>                // Qt列表容器
#include <QPointF>              // 浮点坐标点
#include <QColor>               // 颜色类

// 定义粒子结构体
struct Particle {
    QPointF pos;    // 粒子当前位置(浮点坐标)
    QPointF vel;    // 粒子速度向量
    float size;     // 粒子大小(半径)
    float alpha;    // 透明度(0~255)
    QColor color;   // 粒子颜色
};

class ParticleWidget : public QWidget  // 继承QWidget的自定义窗口类
{
    Q_OBJECT                          // Qt元对象宏

public:
    explicit ParticleWidget(QWidget *parent = nullptr); // 显式构造函数

protected:
    void paintEvent(QPaintEvent *event) override;       // 重写绘制事件
    void mousePressEvent(QMouseEvent *event) override;  // 重写鼠标按下事件

private:
    void addParticles(const QPointF &center);           // 在指定位置生成一批粒子
    void updateParticles();                             // 更新所有粒子的状态

    QList<Particle> m_particles;    // 存储所有存活粒子的列表
    QTimer *m_timer;                // 定时器指针,驱动动画帧
};

#endif // PARTICLEWIDGET_H
particlewidget.cpp
cpp 复制代码
#include "particlewidget.h"           // 引入自定义头文件
#include <QPainter>                   // 绘图类
#include <QMouseEvent>                // 鼠标事件类
#include <QTimer>                     // 定时器类
#include <QRandomGenerator>           // 随机数生成器
#include <QtMath>                     // 数学函数库
#include <QtGlobal>

ParticleWidget::ParticleWidget(QWidget *parent) // 构造函数实现
    : QWidget(parent)                           // 初始化父类
{
    setWindowTitle("Qt 粒子系统");               // 设置窗口标题
    resize(600, 400);                           // 设置窗口初始大小
    setMouseTracking(true);                     // 启用鼠标追踪(即使不按下也跟踪移动)

    m_timer = new QTimer(this);                 // 创建定时器对象
    connect(m_timer, &QTimer::timeout, [this](){ // 连接定时器超时信号到lambda表达式
        updateParticles();                       // 更新粒子状态
        update();                                // 请求重绘
    });
    m_timer->start(16); // 启动定时器,间隔16毫秒,约60帧每秒
}

void ParticleWidget::addParticles(const QPointF &center) // 在指定中心位置生成粒子
{
    auto *gen = QRandomGenerator::global(); // 获取全局随机数生成器指针
    for (int i = 0; i < 30; ++i) {          // 每次生成30个粒子
        Particle p;                         // 创建临时粒子对象
        // 随机方向,速度2-6
        double angle = gen->bounded(2 * M_PI);           // 随机角度0~2π
        double speed = gen->bounded(4.0) + 2.0;          // 随机速度2~6
        p.pos = center;                                   // 粒子起始位置为点击点
        p.vel = QPointF(cos(angle) * speed, sin(angle) * speed); // 根据角度和速度计算速度向量
        p.size = gen->bounded(6.0) + 2.0;                // 随机大小2~8
        p.alpha = 255;                                    // 初始透明度完全不透明
        // 随机亮色(RGB各分量0~255)
        p.color = QColor(gen->bounded(256), gen->bounded(256), gen->bounded(256));
        m_particles.append(p);                            // 将粒子添加到列表中
    }
}

void ParticleWidget::updateParticles() // 更新所有粒子状态
{
    for (auto &p : m_particles) {       // 遍历每一个粒子(引用方式,可修改)
        // 物理运动
        p.pos += p.vel;                // 位置增加速度
        p.vel *= 0.98;                 // 速度乘以阻尼系数0.98,模拟空气阻力
        p.size += 0.3;                 // 粒子逐渐膨胀
        p.alpha -= 5;                  // 透明度逐渐降低(淡出)
    }
    // 移除死亡粒子:透明度<=0 或 大小>30
    m_particles.erase(std::remove_if(m_particles.begin(), m_particles.end(),
                                     [](const Particle &p){ return p.alpha <= 0 || p.size > 30; }),
                      m_particles.end());
}

void ParticleWidget::paintEvent(QPaintEvent *) // 重写绘制事件
{
    QPainter painter(this);                     // 创建画家对象
    painter.setRenderHint(QPainter::Antialiasing); // 开启抗锯齿
    painter.fillRect(rect(), Qt::black);        // 填充黑色背景

    for (const auto &p : m_particles) {         // 遍历所有存活粒子
        QColor c = p.color;                     // 获取粒子颜色
        float clamped = qBound(0.0f, p.alpha, 255.0f);
        c.setAlpha(static_cast<int>(clamped));
        painter.setBrush(c);                    // 设置画刷颜色
        painter.setPen(Qt::NoPen);              // 取消边框
        painter.drawEllipse(p.pos, p.size, p.size); // 绘制圆形粒子
    }
}

void ParticleWidget::mousePressEvent(QMouseEvent *event) // 重写鼠标按下事件
{
    if (event->button() == Qt::LeftButton) {    // 如果是左键按下
        addParticles(event->pos());              // 在鼠标位置生成粒子
    }
}

例3:行业应用 - 医学影像窗口调整

dicomwidget.h
复制代码
#ifndef DICOMWIDGET_H           // 防止头文件重复包含
#define DICOMWIDGET_H

#include <QWidget>              // Qt窗口基类
#include <QImage>               // 图像类
#include <QPoint>               // 整数坐标点

class DicomWidget : public QWidget  // 继承QWidget的自定义窗口类
{
    Q_OBJECT                      // Qt元对象宏

public:
    explicit DicomWidget(QWidget *parent = nullptr); // 显式构造函数

protected:
    void paintEvent(QPaintEvent *event) override;       // 重写绘制事件
    void mousePressEvent(QMouseEvent *event) override;  // 重写鼠标按下事件
    void mouseMoveEvent(QMouseEvent *event) override;   // 重写鼠标移动事件
    void wheelEvent(QWheelEvent *event) override;       // 重写滚轮事件

private:
    QImage m_source;       // 原始图像(模拟医学影像)
    QImage m_display;      // 经过窗宽窗位处理后的显示图像
    int m_windowWidth;     // 窗宽(Window Width)
    int m_windowLevel;     // 窗位(Window Level)
    bool m_isDragging;     // 是否正在拖拽鼠标
    QPoint m_lastPos;      // 上次鼠标位置
    void updateDisplay();  // 根据当前窗宽窗位更新显示图像
};

#endif // DICOMWIDGET_H
dicomwidget.cpp
复制代码
#include "dicomwidget.h"            // 引入自定义头文件
#include <QPainter>                 // 绘图类
#include <QMouseEvent>              // 鼠标事件类
#include <QWheelEvent>              // 滚轮事件类
#include <cmath>                    // 标准数学函数

DicomWidget::DicomWidget(QWidget *parent) // 构造函数实现
    : QWidget(parent), m_windowWidth(256), m_windowLevel(128), m_isDragging(false) // 初始化成员变量
{
    // 创建一个渐变灰度图作为模拟医学图像(512x512,8位灰度)
    m_source = QImage(512, 512, QImage::Format_Grayscale8);
    for (int y = 0; y < 512; ++y) {         // 遍历每一行
        for (int x = 0; x < 512; ++x) {     // 遍历每一列
            // 模拟不同组织密度:使用正弦余弦组合,并加入两个矩形区域模拟病灶
            int val = static_cast<int>(
                128 + 64 * sin(x * 0.02) + 32 * cos(y * 0.03) + // 基础纹理
                (x < 150 ? 40 : 0) +                              // 左侧高密度区
                (y > 350 ? -30 : 0)                               // 下方低密度区
            );
            m_source.setPixel(x, y, qBound(0, val, 255)); // 设置像素值,限制在0~255
        }
    }
    updateDisplay(); // 初始化显示图像
}

void DicomWidget::updateDisplay() // 根据当前窗宽窗位重新计算显示图像
{
    // 创建与源图像同尺寸的32位ARGB图像(用于显示)
    m_display = QImage(m_source.size(), QImage::Format_ARGB32);
    // 计算窗宽窗位对应的灰度映射范围
    double minVal = m_windowLevel - m_windowWidth / 2.0; // 窗的下界
    double maxVal = m_windowLevel + m_windowWidth / 2.0; // 窗的上界

    for (int y = 0; y < m_source.height(); ++y) { // 遍历每一行
        for (int x = 0; x < m_source.width(); ++x) { // 遍历每一列
            int pixel = qGray(m_source.pixel(x, y)); // 获取源图像该点的灰度值(0~255)
            // 将像素值映射到0~255之间:超出窗范围的被截断
            double mapped = (pixel - minVal) / (maxVal - minVal) * 255.0;
            int gray = qBound(0, static_cast<int>(mapped), 255); // 限制在0~255
            m_display.setPixel(x, y, qRgb(gray, gray, gray)); // 设置为灰度颜色
        }
    }
    update(); // 请求重绘窗口
}

void DicomWidget::paintEvent(QPaintEvent *) // 重写绘制事件
{
    QPainter painter(this);                 // 创建画家对象
    painter.setRenderHint(QPainter::SmoothPixmapTransform); // 开启平滑缩放变换
    // 将显示图像按比例缩放以适应窗口,保持宽高比,使用平滑变换
    QImage scaled = m_display.scaled(size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
    // 计算居中显示的左上角坐标
    int x = (width() - scaled.width()) / 2;
    int y = (height() - scaled.height()) / 2;
    painter.drawImage(x, y, scaled);        // 绘制缩放后的图像

    // 在左上角显示当前窗宽窗位参数
    painter.setPen(Qt::cyan);               // 设置画笔颜色:青色
    painter.setFont(QFont("Consolas", 12)); // 设置等宽字体
    painter.drawText(10, 30, QString("WW: %1  WL: %2") // 显示文本
                     .arg(m_windowWidth).arg(m_windowLevel));
}

void DicomWidget::mousePressEvent(QMouseEvent *event) // 重写鼠标按下事件
{
    if (event->button() == Qt::LeftButton) { // 如果是左键按下
        m_isDragging = true;                 // 标记开始拖拽
        m_lastPos = event->pos();            // 记录当前鼠标位置
    }
}

void DicomWidget::mouseMoveEvent(QMouseEvent *event) // 重写鼠标移动事件
{
    if (m_isDragging) {                      // 如果处于拖拽状态
        QPoint delta = event->pos() - m_lastPos; // 计算鼠标位移
        // 水平拖动改变窗宽,垂直拖动改变窗位
        m_windowWidth += delta.x();          // 向右增加窗宽
        m_windowLevel -= delta.y();          // 向上增加窗位(注意Y轴方向)
        // 限制窗宽和窗位的合理范围
        m_windowWidth = qBound(1, m_windowWidth, 511);   // 窗宽至少1,最大511
        m_windowLevel = qBound(0, m_windowLevel, 255);   // 窗位0~255
        m_lastPos = event->pos();            // 更新上次位置
        updateDisplay();                     // 重新计算显示图像
    }
}

void DicomWidget::wheelEvent(QWheelEvent *event) // 重写滚轮事件
{
    // 滚轮滚动改变窗宽:每格120单位,每步增减10
    int steps = event->angleDelta().y() / 120; // 获取滚轮步数(正值向上,负值向下)
    m_windowWidth += steps * 10;               // 调整窗宽
    m_windowWidth = qBound(1, m_windowWidth, 510); // 限制范围
    updateDisplay();                           // 重新计算显示图像
}
相关推荐
秋田君2 小时前
QT_JSON文件操作
开发语言·qt·json
秋田君4 小时前
QT_INI文件操作
开发语言·qt
youqingyike4 小时前
【无标题】
前端·qt
程序员老陆5 小时前
Qt::WidgetAttribute 常用属性详解
开发语言·qt
程序员老陆18 小时前
Qt的QThread::usleep和FFmpeg的libavutil模块的av_usleep哪个精度高一些?
开发语言·qt·ffmpeg·音视频
Quz19 小时前
QML TextArea 入门(二):富文本与换行模式
qt
大白同学4211 天前
初识Qt——创建第一个Qt项目
开发语言·qt
秋田君1 天前
QT_绘图原理双缓冲机制
服务器·数据库·qt
想要入门的程序猿1 天前
Qt插件QPluginLoader
开发语言·qt
大白同学4211 天前
初始Qt——了解Qt背景
开发语言·qt