Qt C++ 图形绘制完全指南:从基础到进阶实战

前言

Qt框架提供了强大的2D图形绘制能力,通过QPainter类及其相关组件,开发者可以轻松实现各种复杂的图形绘制需求。本文将系统介绍Qt图形绘制的核心技术,并通过实例代码演示各种绘制技巧。

一、Qt图形绘制基础架构

1.1 核心类介绍

Qt图形绘制主要涉及以下核心类:

  • QPainter:执行绘制操作的主要类
  • QPaintDevice:绘制设备的抽象基类
  • QPaintEngine:定义QPainter如何在特定平台绘制
  • QWidget:最常用的绘制设备
  • QPixmap/QImage:图像绘制设备

1.2 绘制系统架构图

┌─────────────┐
│  QPainter   │ ← 绘制工具
└──────┬──────┘
       │
┌──────▼──────┐
│QPaintDevice │ ← 绘制表面
└──────┬──────┘
       │
┌──────▼──────┐
│QPaintEngine │ ← 底层引擎
└─────────────┘

二、基础图形绘制实战

2.1 创建自定义绘制Widget

首先创建一个自定义Widget类,重写paintEvent方法:

DrawWidget.h

#ifndef DRAWWIDGET_H
#define DRAWWIDGET_H

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

class DrawWidget : public QWidget
{
    Q_OBJECT
    
public:
    explicit DrawWidget(QWidget *parent = nullptr);
    
protected:
    void paintEvent(QPaintEvent *event) override;
    
private slots:
    void updateAnimation();
    
private:
    int m_animationAngle;
    QTimer *m_timer;
    
    void drawBasicShapes(QPainter &painter);
    void drawGradients(QPainter &painter);
    void drawTransformations(QPainter &painter);
    void drawPath(QPainter &painter);
    void drawText(QPainter &painter);
};

#endif // DRAWWIDGET_H

DrawWidget.cpp

#include "DrawWidget.h"
#include <QLinearGradient>
#include <QRadialGradient>
#include <QPainterPath>
#include <QFont>

DrawWidget::DrawWidget(QWidget *parent) 
    : QWidget(parent), m_animationAngle(0)
{
    setFixedSize(800, 600);
    setWindowTitle("Qt图形绘制示例");
    
    // 设置定时器用于动画
    m_timer = new QTimer(this);
    connect(m_timer, &QTimer::timeout, this, &DrawWidget::updateAnimation);
    m_timer->start(50); // 20 FPS
}

void DrawWidget::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);
    
    // 设置白色背景
    painter.fillRect(rect(), Qt::white);
    
    // 绘制各种图形
    drawBasicShapes(painter);
    drawGradients(painter);
    drawTransformations(painter);
    drawPath(painter);
    drawText(painter);
}

void DrawWidget::drawBasicShapes(QPainter &painter)
{
    painter.save();
    
    // 绘制标题
    painter.setPen(QPen(Qt::black, 2));
    painter.setFont(QFont("Arial", 12, QFont::Bold));
    painter.drawText(20, 30, "基础图形绘制");
    
    // 设置画笔和画刷
    QPen pen(Qt::blue, 3, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
    painter.setPen(pen);
    
    // 绘制直线
    painter.drawLine(50, 50, 150, 50);
    
    // 绘制矩形
    painter.setBrush(QBrush(Qt::yellow, Qt::SolidPattern));
    painter.drawRect(50, 70, 100, 60);
    
    // 绘制圆角矩形
    painter.setBrush(QBrush(Qt::cyan, Qt::Dense4Pattern));
    painter.drawRoundedRect(170, 70, 100, 60, 15, 15);
    
    // 绘制椭圆
    painter.setBrush(QBrush(Qt::magenta, Qt::DiagCrossPattern));
    painter.drawEllipse(290, 70, 100, 60);
    
    // 绘制多边形
    QPolygon polygon;
    polygon << QPoint(420, 70) << QPoint(470, 70) 
            << QPoint(495, 100) << QPoint(470, 130) 
            << QPoint(420, 130) << QPoint(395, 100);
    painter.setBrush(QBrush(Qt::green, Qt::CrossPattern));
    painter.drawPolygon(polygon);
    
    // 绘制弧形
    painter.setBrush(Qt::NoBrush);
    painter.setPen(QPen(Qt::red, 3));
    painter.drawArc(520, 70, 80, 60, 30 * 16, 120 * 16);
    
    // 绘制扇形
    painter.setBrush(QBrush(Qt::darkCyan));
    painter.drawPie(620, 70, 80, 60, 45 * 16, 90 * 16);
    
    painter.restore();
}

void DrawWidget::drawGradients(QPainter &painter)
{
    painter.save();
    
    // 绘制标题
    painter.setPen(QPen(Qt::black, 2));
    painter.setFont(QFont("Arial", 12, QFont::Bold));
    painter.drawText(20, 180, "渐变效果");
    
    // 线性渐变
    QLinearGradient linearGrad(50, 200, 150, 260);
    linearGrad.setColorAt(0, Qt::red);
    linearGrad.setColorAt(0.5, Qt::yellow);
    linearGrad.setColorAt(1, Qt::green);
    painter.setBrush(linearGrad);
    painter.drawRect(50, 200, 100, 60);
    
    // 径向渐变
    QRadialGradient radialGrad(220, 230, 50);
    radialGrad.setColorAt(0, Qt::white);
    radialGrad.setColorAt(0.5, Qt::cyan);
    radialGrad.setColorAt(1, Qt::blue);
    painter.setBrush(radialGrad);
    painter.drawEllipse(170, 200, 100, 60);
    
    // 锥形渐变
    QConicalGradient conicalGrad(320, 230, 0);
    conicalGrad.setColorAt(0, Qt::red);
    conicalGrad.setColorAt(0.25, Qt::yellow);
    conicalGrad.setColorAt(0.5, Qt::green);
    conicalGrad.setColorAt(0.75, Qt::blue);
    conicalGrad.setColorAt(1, Qt::red);
    painter.setBrush(conicalGrad);
    painter.drawEllipse(290, 200, 60, 60);
    
    painter.restore();
}

void DrawWidget::drawTransformations(QPainter &painter)
{
    painter.save();
    
    // 绘制标题
    painter.setPen(QPen(Qt::black, 2));
    painter.setFont(QFont("Arial", 12, QFont::Bold));
    painter.drawText(20, 310, "变换效果(动画)");
    
    // 保存原始状态
    painter.save();
    
    // 平移到旋转中心
    painter.translate(100, 360);
    
    // 应用旋转(动画)
    painter.rotate(m_animationAngle);
    
    // 绘制旋转的矩形
    painter.setBrush(QBrush(Qt::darkBlue));
    painter.drawRect(-30, -20, 60, 40);
    
    painter.restore();
    
    // 缩放效果
    painter.save();
    painter.translate(220, 360);
    double scale = 1.0 + 0.5 * sin(m_animationAngle * M_PI / 180.0);
    painter.scale(scale, scale);
    painter.setBrush(QBrush(Qt::darkGreen));
    painter.drawEllipse(-30, -30, 60, 60);
    painter.restore();
    
    // 错切效果
    painter.save();
    painter.translate(340, 360);
    painter.shear(0.5 * sin(m_animationAngle * M_PI / 180.0), 0);
    painter.setBrush(QBrush(Qt::darkRed));
    painter.drawRect(-30, -20, 60, 40);
    painter.restore();
    
    painter.restore();
}

void DrawWidget::drawPath(QPainter &painter)
{
    painter.save();
    
    // 绘制标题
    painter.setPen(QPen(Qt::black, 2));
    painter.setFont(QFont("Arial", 12, QFont::Bold));
    painter.drawText(20, 440, "路径绘制");
    
    // 创建复杂路径
    QPainterPath path;
    path.moveTo(50, 460);
    path.lineTo(100, 460);
    path.cubicTo(120, 440, 140, 480, 160, 460);
    path.lineTo(200, 460);
    path.quadTo(220, 480, 240, 460);
    path.arcTo(240, 440, 40, 40, 0, 180);
    
    // 绘制路径
    painter.setPen(QPen(Qt::darkMagenta, 3));
    painter.setBrush(QBrush(Qt::lightGray));
    painter.drawPath(path);
    
    // 创建星形路径
    QPainterPath starPath;
    int starX = 400, starY = 470;
    for (int i = 0; i < 5; ++i) {
        double angle = i * 72 * M_PI / 180.0 - M_PI / 2;
        double x = starX + 30 * cos(angle);
        double y = starY + 30 * sin(angle);
        if (i == 0) {
            starPath.moveTo(x, y);
        } else {
            starPath.lineTo(x, y);
        }
        
        angle = (i * 72 + 36) * M_PI / 180.0 - M_PI / 2;
        x = starX + 15 * cos(angle);
        y = starY + 15 * sin(angle);
        starPath.lineTo(x, y);
    }
    starPath.closeSubpath();
    
    painter.setPen(QPen(Qt::darkYellow, 2));
    painter.setBrush(QBrush(Qt::yellow));
    painter.drawPath(starPath);
    
    painter.restore();
}

void DrawWidget::drawText(QPainter &painter)
{
    painter.save();
    
    // 绘制标题
    painter.setPen(QPen(Qt::black, 2));
    painter.setFont(QFont("Arial", 12, QFont::Bold));
    painter.drawText(20, 540, "文字绘制");
    
    // 普通文字
    painter.setFont(QFont("Times New Roman", 14));
    painter.setPen(Qt::darkBlue);
    painter.drawText(50, 565, "Hello Qt Graphics!");
    
    // 带阴影的文字
    painter.setFont(QFont("Arial", 16, QFont::Bold));
    painter.setPen(Qt::gray);
    painter.drawText(202, 567, "Shadow Text");
    painter.setPen(Qt::black);
    painter.drawText(200, 565, "Shadow Text");
    
    // 旋转文字
    painter.save();
    painter.translate(400, 560);
    painter.rotate(-30);
    painter.setFont(QFont("Courier", 14));
    painter.setPen(Qt::darkGreen);
    painter.drawText(0, 0, "Rotated Text");
    painter.restore();
    
    // 渐变文字
    QLinearGradient textGrad(500, 550, 600, 570);
    textGrad.setColorAt(0, Qt::blue);
    textGrad.setColorAt(1, Qt::red);
    painter.setPen(QPen(QBrush(textGrad), 2));
    painter.setFont(QFont("Arial", 18, QFont::Bold));
    painter.drawText(500, 565, "Gradient");
    
    painter.restore();
}

void DrawWidget::updateAnimation()
{
    m_animationAngle = (m_animationAngle + 5) % 360;
    update(); // 触发重绘
}

2.2 主窗口实现

main.cpp

#include <QApplication>
#include "DrawWidget.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    
    DrawWidget widget;
    widget.show();
    
    return app.exec();
}

三、高级绘制技术

3.1 双缓冲绘制

为了避免闪烁,Qt默认启用双缓冲。也可以手动实现:

void CustomWidget::paintEvent(QPaintEvent *event)
{
    QPixmap pixmap(size());
    pixmap.fill(Qt::white);
    
    QPainter pixmapPainter(&pixmap);
    pixmapPainter.setRenderHint(QPainter::Antialiasing);
    
    // 在pixmap上绘制
    drawContent(pixmapPainter);
    
    // 将pixmap绘制到widget
    QPainter widgetPainter(this);
    widgetPainter.drawPixmap(0, 0, pixmap);
}

3.2 图形项框架 (Graphics View Framework)

对于复杂的图形场景,使用Graphics View框架更合适:

// 场景图形项
class CustomItem : public QGraphicsItem
{
public:
    CustomItem()
    {
        setFlag(ItemIsMovable);
        setFlag(ItemIsSelectable);
    }
    
    QRectF boundingRect() const override
    {
        return QRectF(-50, -50, 100, 100);
    }
    
    void paint(QPainter *painter, 
               const QStyleOptionGraphicsItem *option,
               QWidget *widget) override
    {
        Q_UNUSED(option);
        Q_UNUSED(widget);
        
        painter->setBrush(isSelected() ? Qt::yellow : Qt::lightBlue);
        painter->setPen(QPen(Qt::black, 2));
        painter->drawEllipse(boundingRect());
    }
};

// 使用场景
QGraphicsScene *scene = new QGraphicsScene();
scene->addItem(new CustomItem());

QGraphicsView *view = new QGraphicsView(scene);
view->setRenderHint(QPainter::Antialiasing);
view->show();

3.3 OpenGL集成

Qt支持OpenGL绘制,可以实现高性能3D图形:

class OpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
protected:
    void initializeGL() override
    {
        initializeOpenGLFunctions();
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    }
    
    void paintGL() override
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        // OpenGL绘制代码
    }
    
    void resizeGL(int w, int h) override
    {
        glViewport(0, 0, w, h);
    }
};

四、性能优化技巧

4.1 绘制优化策略

  1. 减少重绘区域
void Widget::updatePartial()
{
    // 只更新特定区域
    update(QRect(10, 10, 100, 100));
}
  1. 使用绘制缓存
class CachedWidget : public QWidget
{
private:
    QPixmap m_cache;
    bool m_cacheValid = false;
    
protected:
    void paintEvent(QPaintEvent *event) override
    {
        if (!m_cacheValid) {
            m_cache = QPixmap(size());
            QPainter cachePainter(&m_cache);
            drawComplexContent(cachePainter);
            m_cacheValid = true;
        }
        
        QPainter painter(this);
        painter.drawPixmap(0, 0, m_cache);
    }
};
  1. 批量绘制操作
// 低效方式
for (int i = 0; i < 1000; ++i) {
    painter.drawPoint(points[i]);
}

// 高效方式
painter.drawPoints(points.data(), 1000);

4.2 渲染提示设置

painter.setRenderHint(QPainter::Antialiasing, true);        // 抗锯齿
painter.setRenderHint(QPainter::TextAntialiasing, true);    // 文字抗锯齿
painter.setRenderHint(QPainter::SmoothPixmapTransform, true); // 平滑变换

五、实战案例:自定义图表控件

5.1 简单饼图实现

class PieChart : public QWidget
{
private:
    QVector<QPair<QString, double>> m_data;
    QVector<QColor> m_colors;
    
public:
    void setData(const QVector<QPair<QString, double>> &data)
    {
        m_data = data;
        generateColors();
        update();
    }
    
protected:
    void paintEvent(QPaintEvent *event) override
    {
        Q_UNUSED(event);
        
        QPainter painter(this);
        painter.setRenderHint(QPainter::Antialiasing);
        
        QRect pieRect = rect().adjusted(20, 20, -20, -20);
        
        double total = 0;
        for (const auto &item : m_data) {
            total += item.second;
        }
        
        int startAngle = 0;
        for (int i = 0; i < m_data.size(); ++i) {
            int spanAngle = static_cast<int>(
                m_data[i].second / total * 360 * 16
            );
            
            painter.setBrush(m_colors[i]);
            painter.drawPie(pieRect, startAngle, spanAngle);
            
            startAngle += spanAngle;
        }
    }
    
private:
    void generateColors()
    {
        m_colors.clear();
        for (int i = 0; i < m_data.size(); ++i) {
            m_colors.append(QColor::fromHsv(
                i * 360 / m_data.size(), 200, 200
            ));
        }
    }
};

六、常见问题与解决方案

6.1 绘制闪烁问题

问题:快速更新时界面闪烁
解决方案

  • 启用双缓冲(Qt默认启用)
  • 使用setAttribute(Qt::WA_OpaquePaintEvent)
  • 避免在paintEvent中进行复杂计算

6.2 坐标系统混淆

问题:绘制位置不正确
解决方案

// 理解Qt坐标系统
// (0,0) 在左上角
// x轴向右增长
// y轴向下增长

// 坐标变换
painter.translate(100, 100);  // 移动原点
painter.scale(2.0, 2.0);      // 缩放
painter.rotate(45);           // 旋转(顺时针)

6.3 性能问题

问题:绘制大量图形时卡顿
解决方案

  • 使用Graphics View框架
  • 实现图形项的LOD(细节层次)
  • 使用OpenGL加速
  • 裁剪不可见区域

七、最佳实践总结

  1. 合理选择绘制方式

    • 简单绘制:重写paintEvent
    • 复杂场景:Graphics View框架
    • 3D/高性能:OpenGL
  2. 性能优化原则

    • 最小化重绘区域
    • 缓存复杂绘制结果
    • 批量处理绘制操作
  3. 代码组织建议

    • 分离绘制逻辑和业务逻辑
    • 使用绘制状态的保存/恢复
    • 合理使用渲染提示

八、项目配置

.pro文件配置

QT += core gui widgets

CONFIG += c++11

TARGET = QtGraphicsDemo
TEMPLATE = app

SOURCES += \
    main.cpp \
    DrawWidget.cpp

HEADERS += \
    DrawWidget.h

# 如果使用OpenGL
# QT += opengl

总结

本文系统介绍了Qt C++图形绘制的核心技术,从基础的QPainter使用到高级的Graphics View框架和OpenGL集成。通过实际代码示例,展示了各种绘制技巧和优化方法。掌握这些技术,可以开发出功能丰富、性能优异的图形应用程序。

Qt的图形系统非常强大和灵活,本文只是涵盖了最常用的部分。建议读者在实际项目中根据具体需求选择合适的技术方案,并持续关注Qt的最新特性和最佳实践。

参考资源


作者: Qt开发者
发布时间: 2024
标签: Qt, C++, 图形绘制, QPainter, Graphics View

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐