Qt开发:绘图编程

Qt提供了强大的2D绘图功能,主要通过QPainter类和相关工具类实现。

核心内容

基本绘图系统

Qt的绘图系统基于:

  • QPainter - 执行绘图操作的主要类

  • QPaintDevice - 绘图设备的抽象基类(如QWidget, QImage, QPixmap等)

  • QPaintEngine - 提供不同设备的具体实现

基本绘图步骤

在paint事件中绘图(对于QWidget派生类):

void MyWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this); // 创建QPainter对象,指定绘图设备
    
    // 设置画笔和画刷
    painter.setPen(Qt::blue);
    painter.setBrush(Qt::yellow);
    
    // 绘制图形
    painter.drawRect(10, 10, 100, 100);
    painter.drawEllipse(50, 50, 100, 100);
    
    // 绘制文本
    painter.drawText(10, 150, "Hello Qt!");
}
///绘制饼图
void drawPieChart(QPainter &painter, const QRect &rect, const QMap<QString, double> &data)
{
    painter.save();
    painter.setRenderHint(QPainter::Antialiasing);
    
    double total = 0.0;
    for(auto value : data.values()) {
        total += value;
    }
    
    int startAngle = 0;
    int colorIndex = 0;
    const QList<QColor> colors = {Qt::red, Qt::blue, Qt::green, Qt::yellow, Qt::cyan};
    
    for(auto it = data.begin(); it != data.end(); ++it) {
        double percentage = it.value() / total;
        int spanAngle = static_cast<int>(percentage * 360 * 16);
        
        painter.setBrush(colors[colorIndex++ % colors.size()]);
        painter.drawPie(rect, startAngle, spanAngle);
        
        startAngle += spanAngle;
    }
    
    painter.restore();
}

///绘制柱状图
void drawBarChart(QPainter &painter, const QRect &area, const QMap<QString, double> &data)
{
    painter.save();
    painter.setRenderHint(QPainter::Antialiasing);
    
    int barWidth = area.width() / (data.size() * 2);
    int x = area.left() + barWidth/2;
    double maxValue = *std::max_element(data.begin(), data.end());
    
    QFontMetrics fm(painter.font());
    int textHeight = fm.height();
    
    for(auto it = data.begin(); it != data.end(); ++it) {
        int barHeight = static_cast<int>((it.value() / maxValue) * area.height());
        QRect bar(x, area.bottom() - barHeight, barWidth, barHeight);
        
        painter.setBrush(QColor::fromHsv(360 * x / area.width(), 255, 200));
        painter.drawRect(bar);
        
        // 绘制标签
        painter.drawText(x - barWidth/2, area.bottom() + textHeight, it.key());
        
        x += barWidth * 2;
    }
    
    painter.restore();
}

常用绘图操作

画笔(QPen)控制
QPen pen;
pen.setColor(QColor(255, 0, 0)); // 红色
pen.setWidth(3); // 3像素宽
pen.setStyle(Qt::DashLine); // 虚线样式
pen.setCapStyle(Qt::RoundCap); // 线端圆角
pen.setJoinStyle(Qt::RoundJoin); // 连接点圆角
painter.setPen(pen);
 画刷(QBrush)控制
QBrush brush;
brush.setColor(Qt::green); // 绿色填充
brush.setStyle(Qt::Dense4Pattern); // 填充样式
painter.setBrush(brush);

// 渐变填充
QLinearGradient gradient(0, 0, 100, 100);
gradient.setColorAt(0, Qt::white);
gradient.setColorAt(1, Qt::black);
painter.setBrush(QBrush(gradient));
基本图形绘制
// 直线
painter.drawLine(QPoint(10, 10), QPoint(100, 100));

// 矩形
painter.drawRect(QRect(10, 10, 100, 50));

// 圆角矩形
painter.drawRoundedRect(QRect(10, 70, 100, 50), 10, 10);

// 椭圆/圆
painter.drawEllipse(QPoint(150, 50), 50, 30);

// 多边形
QPolygon polygon;
polygon << QPoint(10, 10) << QPoint(100, 10) << QPoint(100, 100);
painter.drawPolygon(polygon);

// 路径
QPainterPath path;
path.moveTo(20, 20);
path.lineTo(100, 20);
path.cubicTo(150, 20, 150, 70, 100, 70);
painter.drawPath(path);
文本绘制
painter.setFont(QFont("Arial", 12));
painter.drawText(10, 100, "Qt绘图示例");

// 带格式的文本
QRect textRect(10, 120, 200, 100);
painter.drawText(textRect, Qt::AlignCenter | Qt::TextWordWrap, 
                "这是一段可以自动换行的长文本...");
图像绘制
QPixmap pixmap(":/images/example.png");
painter.drawPixmap(10, 10, pixmap);

// 缩放绘制
painter.drawPixmap(QRect(50, 50, 100, 100), pixmap);

坐标系统与变换

Qt提供了灵活的坐标系统控制:

// 平移
painter.translate(100, 100);

// 缩放
painter.scale(2.0, 1.5);

// 旋转
painter.rotate(45);

// 保存和恢复状态
painter.save();    // 保存当前状态
painter.translate(50, 50);
// 绘制操作...
painter.restore(); // 恢复到保存的状态

高级特性

双缓冲绘图
void MyWidget::paintEvent(QPaintEvent *)
{
    QPixmap pixmap(size());
    pixmap.fill(Qt::white);
    
    QPainter bufferPainter(&pixmap);
    // 在pixmap上绘制...
    
    QPainter painter(this);
    painter.drawPixmap(0, 0, pixmap);
}
自定义图形项(QGraphicsItem)
class MyItem : public QGraphicsItem {
public:
    QRectF boundingRect() const override {
        return QRectF(-50, -50, 100, 100);
    }
    
    void paint(QPainter *painter, 
              const QStyleOptionGraphicsItem *,
              QWidget *) override {
        painter->setBrush(Qt::green);
        painter->drawEllipse(-50, -50, 100, 100);
    }
};
图形视图框架(Graphics View Framework)

对于复杂场景,可以使用QGraphicsView/QGraphicsScene框架:

QGraphicsScene *scene = new QGraphicsScene(this);
QGraphicsView *view = new QGraphicsView(scene);

// 添加图形项
scene->addEllipse(0, 0, 100, 100);
scene->addText("Graphics View 示例");

// 显示视图
view->show();

性能优化技巧

  1. 尽量减少paintEvent中的计算,减少绘图区域:

  2. 使用双缓冲减少闪烁

  3. 对于静态内容,考虑缓存到QPixmap

  4. 使用QGraphicsView框架处理大量图形项

  5. 合理使用QPainter状态保存/恢复

双缓冲减少Qt绘图闪烁

双缓冲技术是解决绘图闪烁问题的有效方法,其核心思想是在内存中完成所有绘图操作,然后一次性将结果绘制到屏幕上。

基本原理

  1. 传统绘图:直接在屏幕上绘制,每次绘制都可见,导致闪烁

  2. 双缓冲

    • 在内存中创建一个与屏幕绘制区域大小相同的缓冲图像

    • 所有绘图操作先在缓冲图像上完成

    • 最后将缓冲图像一次性绘制到屏幕上

Qt中的双缓冲实现

方法一:手动双缓冲(推荐)
void MyWidget::paintEvent(QPaintEvent *event)
{
    // 1. 创建与窗口大小相同的缓冲图像
    QPixmap pixmap(this->size());
    pixmap.fill(Qt::transparent); // 或填充背景色
    
    // 2. 在缓冲图像上绘图
    QPainter bufferPainter(&pixmap);
    
    // 设置抗锯齿
    bufferPainter.setRenderHint(QPainter::Antialiasing, true);
    
    // 在这里执行所有绘图操作
    drawContent(bufferPainter);
    
    // 3. 将缓冲图像绘制到窗口
    QPainter painter(this);
    painter.drawPixmap(0, 0, pixmap);
}

void MyWidget::drawContent(QPainter &painter)
{
    // 实际的绘图代码
    painter.setPen(Qt::blue);
    painter.setBrush(Qt::yellow);
    painter.drawRect(10, 10, 100, 100);
    painter.drawEllipse(50, 50, 100, 100);
}
方法二:使用Qt自动双缓冲

Qt为QWidget提供了自动双缓冲属性:

MyWidget::MyWidget(QWidget *parent) : QWidget(parent)
{
    // 启用自动双缓冲
    setAttribute(Qt::WA_StaticContents);  // 对于静态内容
    setAttribute(Qt::WA_OpaquePaintEvent); // 不自动填充背景
}

注意:自动双缓冲在某些情况下可能不如手动双缓冲灵活。

双缓冲的最佳实践

  1. 缓冲图像大小管理

    void MyWidget::resizeEvent(QResizeEvent *event)
    {
        // 窗口大小改变时重新创建缓冲图像
        m_buffer = QPixmap(size());
        update(); // 触发重绘
    }
  2. 部分更新优化

    void MyWidget::paintEvent(QPaintEvent *event)
    {
        if(m_buffer.isNull()) {
            m_buffer = QPixmap(size());
            m_buffer.fill(Qt::white);
        }
        
        QPainter bufferPainter(&m_buffer);
        
        // 只更新需要重绘的区域
        if(!event->region().isEmpty()) {
            bufferPainter.setClipRegion(event->region());
        }
        
        // 绘图操作...
        
        QPainter painter(this);
        painter.drawPixmap(0, 0, m_buffer);
    }
  3. 复杂图形的缓存
    对于不经常变化的复杂图形,可以预先绘制到缓冲中:

    void MyWidget::updateComplexGraphics()
    {
        QPainter painter(&m_buffer);
        // 绘制复杂图形...
        update(); // 只触发屏幕更新
    }

手动双缓冲完整示例

下面是一个完整的Qt手动双缓冲示例,展示如何通过双缓冲技术实现平滑的无闪烁绘图。

示例1:基本双缓冲实现

#include <QWidget>
#include <QPaintEvent>
#include <QPainter>
#include <QTimer>

class DoubleBufferWidget : public QWidget
{
    Q_OBJECT
    
public:
    DoubleBufferWidget(QWidget *parent = nullptr) : QWidget(parent)
    {
        // 设置固定大小
        setFixedSize(400, 300);
        
        // 初始化动画计时器
        QTimer *timer = new QTimer(this);
        connect(timer, &QTimer::timeout, this, [this](){
            m_angle += 1; // 每次增加1度
            if(m_angle >= 360) m_angle = 0;
            update(); // 触发重绘
        });
        timer->start(16); // ~60FPS
    }

protected:
    void paintEvent(QPaintEvent *event) override
    {
        // 1. 创建与窗口大小相同的缓冲图像
        QPixmap buffer(size());
        buffer.fill(Qt::white); // 填充白色背景
        
        // 2. 在缓冲图像上绘图
        QPainter bufferPainter(&buffer);
        bufferPainter.setRenderHint(QPainter::Antialiasing, true);
        
        // 绘制内容
        drawRotatingRect(bufferPainter);
        
        // 3. 将缓冲图像绘制到窗口
        QPainter windowPainter(this);
        windowPainter.drawPixmap(0, 0, buffer);
    }
    
    void resizeEvent(QResizeEvent *event) override
    {
        // 窗口大小改变时强制重绘
        update();
    }

private:
    void drawRotatingRect(QPainter &painter)
    {
        // 设置中心点
        QPoint center(width() / 2, height() / 2);
        
        // 保存当前状态
        painter.save();
        
        // 移动到中心点并旋转
        painter.translate(center);
        painter.rotate(m_angle);
        
        // 绘制旋转的矩形
        painter.setPen(QPen(Qt::blue, 2));
        painter.setBrush(Qt::green);
        painter.drawRect(-50, -50, 100, 100);
        
        // 恢复状态
        painter.restore();
        
        // 绘制中心点
        painter.setPen(Qt::red);
        painter.drawEllipse(center, 3, 3);
        
        // 显示帧率信息
        painter.drawText(10, 20, QString("双缓冲示例 - 角度: %1°").arg(m_angle));
    }
    
    int m_angle = 0;
};

示例2:更复杂的双缓冲实现(带部分更新)

#include <QWidget>
#include <QPaintEvent>
#include <QPainter>
#include <QMouseEvent>
#include <QVector>
#include <QPoint>

class DrawingWidget : public QWidget
{
    Q_OBJECT
    
public:
    DrawingWidget(QWidget *parent = nullptr) : QWidget(parent)
    {
        setAttribute(Qt::WA_StaticContents); // 优化静态内容
        setMouseTracking(true); // 启用鼠标跟踪
        
        // 初始化缓冲为透明
        m_buffer = QPixmap(size());
        m_buffer.fill(Qt::transparent);
    }

protected:
    void paintEvent(QPaintEvent *event) override
    {
        // 创建临时绘制器
        QPainter painter(this);
        
        // 如果缓冲大小与窗口大小不匹配,调整缓冲大小
        if(m_buffer.size() != size()) {
            QPixmap newBuffer(size());
            newBuffer.fill(Qt::white);
            QPainter tempPainter(&newBuffer);
            tempPainter.drawPixmap(0, 0, m_buffer);
            m_buffer = newBuffer;
        }
        
        // 绘制缓冲到窗口
        painter.drawPixmap(0, 0, m_buffer);
        
        // 如果有临时绘图(如橡皮筋效果)
        if(m_isDrawing) {
            painter.setPen(QPen(Qt::black, 2, Qt::DashLine));
            painter.drawLine(m_startPoint, m_endPoint);
        }
    }
    
    void mousePressEvent(QMouseEvent *event) override
    {
        if(event->button() == Qt::LeftButton) {
            m_startPoint = event->pos();
            m_endPoint = event->pos();
            m_isDrawing = true;
        }
    }
    
    void mouseMoveEvent(QMouseEvent *event) override
    {
        if(m_isDrawing) {
            m_endPoint = event->pos();
            update(); // 更新橡皮筋线
        }
    }
    
    void mouseReleaseEvent(QMouseEvent *event) override
    {
        if(event->button() == Qt::LeftButton && m_isDrawing) {
            m_endPoint = event->pos();
            m_isDrawing = false;
            
            // 在缓冲上绘制最终线条
            QPainter bufferPainter(&m_buffer);
            bufferPainter.setPen(QPen(Qt::black, 2));
            bufferPainter.drawLine(m_startPoint, m_endPoint);
            
            update();
        }
    }
    
    void resizeEvent(QResizeEvent *event) override
    {
        // 保存原有内容
        QPixmap newBuffer(size());
        newBuffer.fill(Qt::white);
        QPainter painter(&newBuffer);
        painter.drawPixmap(0, 0, m_buffer);
        m_buffer = newBuffer;
        
        QWidget::resizeEvent(event);
    }

private:
    QPixmap m_buffer;       // 双缓冲图像
    QPoint m_startPoint;    // 绘图起点
    QPoint m_endPoint;      // 绘图终点
    bool m_isDrawing = false; // 是否正在绘图
};

示例3:带背景和前景的双缓冲

#include <QWidget>
#include <QPaintEvent>
#include <QPainter>
#include <QTimer>
#include <QDateTime>

class ClockWidget : public QWidget
{
    Q_OBJECT
    
public:
    ClockWidget(QWidget *parent = nullptr) : QWidget(parent)
    {
        setFixedSize(300, 300);
        
        // 背景缓冲只需要生成一次
        m_background = QPixmap(size());
        drawBackground(m_background);
        
        // 定时器每秒更新一次
        QTimer *timer = new QTimer(this);
        connect(timer, &QTimer::timeout, this, [this](){
            update(); // 只更新前景
        });
        timer->start(1000);
    }

protected:
    void paintEvent(QPaintEvent *event) override
    {
        // 1. 创建前景缓冲
        QPixmap foreground(size());
        foreground.fill(Qt::transparent);
        
        // 2. 在前景缓冲上绘制动态内容
        QPainter foregroundPainter(&foreground);
        foregroundPainter.setRenderHint(QPainter::Antialiasing, true);
        drawClockHands(foregroundPainter);
        
        // 3. 将背景和前景合成到窗口
        QPainter windowPainter(this);
        windowPainter.drawPixmap(0, 0, m_background);
        windowPainter.drawPixmap(0, 0, foreground);
    }

private:
    void drawBackground(QPixmap &pixmap)
    {
        QPainter painter(&pixmap);
        painter.setRenderHint(QPainter::Antialiasing, true);
        
        // 绘制渐变背景
        QRadialGradient gradient(width()/2, height()/2, width()/2);
        gradient.setColorAt(0, Qt::white);
        gradient.setColorAt(1, QColor(200, 200, 255));
        painter.fillRect(rect(), gradient);
        
        // 绘制时钟刻度
        painter.setPen(QPen(Qt::black, 2));
        painter.translate(width()/2, height()/2);
        
        for(int i = 0; i < 12; ++i) {
            painter.drawLine(0, -120, 0, -100);
            painter.rotate(30.0);
        }
    }
    
    void drawClockHands(QPainter &painter)
    {
        QTime time = QTime::currentTime();
        
        painter.translate(width()/2, height()/2);
        
        // 时针
        painter.save();
        painter.setPen(QPen(Qt::black, 4));
        painter.rotate(30.0 * (time.hour() + time.minute() / 60.0));
        painter.drawLine(0, 0, 0, -60);
        painter.restore();
        
        // 分针
        painter.save();
        painter.setPen(QPen(Qt::black, 2));
        painter.rotate(6.0 * time.minute());
        painter.drawLine(0, 0, 0, -80);
        painter.restore();
        
        // 秒针
        painter.save();
        painter.setPen(QPen(Qt::red, 1));
        painter.rotate(6.0 * time.second());
        painter.drawLine(0, 0, 0, -90);
        painter.restore();
    }
    
    QPixmap m_background; // 静态背景缓冲
};

关键点总结

  1. 缓冲创建:在paintEvent中创建与窗口大小匹配的缓冲图像

  2. 绘图分离:所有绘图操作先在缓冲上完成

  3. 最终绘制:最后将缓冲图像一次性绘制到窗口

  4. 性能优化

    • 静态内容可以预先绘制到单独缓冲中

    • 动态内容在每次重绘时更新

    • 使用setRenderHint(QPainter::Antialiasing, true)提高绘制质量

  5. 大小变化处理:在resizeEvent中适当调整缓冲大小

这些示例展示了手动双缓冲的不同应用场景,你可以根据需要选择或组合这些技术来实现无闪烁的平滑绘图效果。

双缓冲的适用场景

  1. 频繁更新的动画

  2. 复杂的图形绘制

  3. 需要平滑过渡的效果

  4. 绘图操作耗时的场景

注意事项

  1. 内存使用:双缓冲会占用额外的内存,对于大尺寸窗口要特别注意

  2. 性能权衡:简单的绘图可能不需要双缓冲

  3. 与OpenGL结合:在QOpenGLWidget中,双缓冲由OpenGL本身处理

通过合理使用双缓冲技术,可以显著提高Qt绘图程序的视觉质量和用户体验。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值