Qt 2D 绘制系统核心原理深度解析

引言

Qt 的 2D 绘制系统是其 GUI 框架的基石,支撑着从简单按钮到复杂图表的一切视觉呈现。理解这套系统的核心原理,对于写出高性能、自定义程度高的 UI 代码至关重要。本文从源码级别剖析 Qt 2D 绘制的三层架构:QPaintDevice、QPainter、QPaintEngine,揭示绘制引擎如何实现跨平台抽象,以及关键函数的底层实现。


1. 绘制系统的三层架构

Qt 的 2D 绘制系统由三个核心类构成,形成了一个经典的门面(Facade)模式:

复制代码
QPainter(门面) → QPaintEngine(引擎抽象) → QPaintDevice(绘制目标)
  • QPainter:提供绘制 API,是用户的操作入口
  • QPaintEngine:抽象出不同后端绘制引擎的接口
  • QPaintDevice:抽象出各种绘制目标(窗口、图片、打印设备)

1.1 QPaintDevice 源码解析

QPaintDevice 是所有绘制目标的基类。它定义了"在哪里画"这个问题。

cpp 复制代码
// qtbase/src/gui/painting/qpaintdevice.h
class Q_GUI_EXPORT QPaintDevice
{
public:
    virtual ~QPaintDevice();
    virtual QPaintEngine *paintEngine() const = 0;

    int width() const { return d_ptr->width; }
    int height() const { return d_ptr->height; }
    int widthMM() const { return d_ptr->width * 25.4 / logicalDpiX(); }
    int heightMM() const { return d_ptr->height * 25.4 / logicalDpiY(); }

    qreal devicePixelRatio() const;
    QPainter::RenderHints renderHints() const;

    // 设备坐标系到物理坐标系的转换
    int logicalDpiX() const;
    int logicalDpiY() const;
    int physicalDpiX() const;
    int physicalDpiY() const;

    QPaintDevice &operator=(const QPaintDevice &) = delete;

protected:
    QPaintDevice();
    QPaintDevice(const QPaintDevice &);

private:
    QScopedPointer<QPaintDevicePrivate> d_ptr;
    friend class QPainter;
    friend class QPaintEngine;
    friend class QCoreApplication;
};

关键点:

  • paintEngine() 是纯虚函数,每个子类必须实现,返回对应的绘制引擎
  • devicePixelRatio() 支持高分屏,每个逻辑像素对应多个物理像素
  • logicalDpiX/YphysicalDpiX/Y 的区别用于区分逻辑分辨率和物理分辨率

1.2 继承体系

QPaintDevice 的子类层次如下:

cpp 复制代码
// QWidget    --- 在窗口上绘制,QWidget 持有 QPaintEngine
// QImage     --- 内存图像,可直接像素操作
// QPixmap    --- 平台优化的图像,依赖平台后端(Windows GDI/Direct2D, macOS CG, X11 Cairo)
// QPicture   --- 记录绘制命令,用于重放
// QPrinter   --- 打印输出
// QOpenGLPaintDevice --- OpenGL 帧缓冲绘制

Qt 5.10 之后引入了 QOpenGLPaintDevice 和 QSvgGenerator,进一步扩展了绘制目标类型。


2. QPaintEngine 抽象引擎

QPaintEngine 是 Qt 实现跨平台绘制的核心抽象------每种绘制后端(软件渲染、GDI、GDI+、OpenGL、SVG、PDF)都实现一个 QPaintEngine 子类。

cpp 复制代码
// qtbase/src/gui/painting/qpaintengine.h
class Q_GUI_EXPORT QPaintEngine
{
public:
    virtual ~QPaintEngine();

    enum PaintEngineFeatures {
        PrimitiveTransform    = 0x00000001,
        PatternTransform      = 0x00000002,
        PixmapTransform       = 0x00000004,
        PatternBrush         = 0x00000008,
        LinearGradientFill    = 0x00000010,
        RadialGradientFill    = 0x00000020,
        ConicalGradientFill   = 0x00000040,
        AlphaBlend           = 0x00000080,
        PorterDuff           = 0x00000100,
        // ... 更多特性
    };

    virtual void updateState(const QPaintEngineState &state) = 0;
    virtual void draw(const QPaintEngineState *state) = 0;
    virtual void drawPixmap(const QPaintEngineState *state, ...) = 0;
    virtual void drawTextItem(const QPaintEngineState *state, ...) = 0;
    virtual void drawPolygon(const QPaintEngineState *state, ...) = 0;
    // ...
};

2.1 软件渲染引擎的入口

Qt 的默认软件渲染引擎是 QRasterPaintEngine,路径在:

复制代码
qtbase/src/gui/painting/qpaintengine_raster.cpp

QRasterPaintEngine::draw() 中,所有的绘制原语最终都落入光栅化(rasterization)处理:

cpp 复制代码
// qtbase/src/gui/painting/qpaintengine_raster.cpp
void QRasterPaintEngine::draw(const QPaintEngineState *state)
{
    // 根据当前绘制状态的类型分发到具体处理函数
    switch (state->type()) {
    case QPaintEngine::Ellipse:
        // 椭圆绘制
        break;
    case QPaintEngine::Rect:
        // 矩形绘制
        break;
    case QPaintEngine::Path:
        // 路径绘制(最通用)
        drawPath(state->strokePath().isEmpty()
                 ? state->fillPath()
                 : state->strokePath());
        break;
    case QPaintEngine::Points:
    case QPaintEngine::Lines:
    case QPaintEngine::Polygons:
        // 点、线、多边形
        break;
    default:
        break;
    }
}

3. QPainter 绘制器核心

3.1 构造函数与状态管理

QPainter 的构造过程决定了它的绘制目标:

cpp 复制代码
// qtbase/src/gui/painting/qpainter.cpp
QPainter::QPainter()
    : d_ptr(new QPainterPrivate)
{
    // 默认构造的 painter 不绑定任何设备
}

QPainter::QPainter(QPaintDevice *pd)
    : d_ptr(new QPainterPrivate)
{
    begin(pd);  // 自动调用 begin()
}

begin() 的核心逻辑:

cpp 复制代码
bool QPainter::begin(QPaintDevice *pd)
{
    Q_D(QPainter);

    // 检查设备有效性
    if (!pd || pd->paintEngine() == nullptr) {
        qWarning("QPainter::begin: Paint device returned null engine");
        return false;
    }

    // 切换绘制引擎
    d->engine = pd->paintEngine();
    d->device = pd;

    // 初始化状态(画笔、画刷、字体、变换矩阵)
    d->state = new QPainterState();
    d->state->pen = QPen();
    d->state->brush = QBrush();
    d->state->font = QFont();
    d->state->opacity = 1.0;
    d->state->compositionMode = QPainter::CompositionMode_SourceOver;

    // 设置默认抗锯齿
    d->engine->setAntialiasing(true);
    d->engine->setAlphaCorrection(true);

    d->engine->setState(d->state);
    return true;
}

3.2 save/restore 状态栈

QPainter 维护一个状态栈,save() 将当前状态压栈,restore() 弹出恢复。这是绘制复杂图形时的常用技巧。

cpp 复制代码
// qtbase/src/gui/painting/qpainter.cpp
void QPainter::save()
{
    Q_D(QPainter);
    d->stateStack.append(d->state);
    // 创建当前状态的深拷贝
    d->state = new QPainterState(*d->state);
}

void QPainter::restore()
{
    Q_D(QPainter);
    if (d->stateStack.isEmpty())
        return;
    // 删除当前状态
    delete d->state;
    // 弹出栈顶
    d->state = d->stateStack.takeLast();
    // 更新引擎状态
    d->engine->updateState(*d->state);
}

注意:save/restore 必须成对使用。建议使用 RAII 风格的 QPainterStateSaver 类避免遗漏:

cpp 复制代码
void drawComplexGraphic(QPainter &painter)
{
    QPainterStateSaver saver(painter); // 自动 save
    // ... 复杂绘制操作 ...
    // 函数结束时自动 restore
}

3.3 坐标系统变换

QPainter 支持丰富的坐标变换:

cpp 复制代码
// 平移
void translate(qreal dx, qreal dy);

// 旋转(绕原点)
void rotate(qreal angle);

// 缩放
void scale(qreal sx, qreal sy);

// 剪切
void shear(qreal sh, qreal sv);

// 矩阵变换(最通用)
void setMatrix(const QMatrix &matrix, bool combine = false);
void setTransform(const QTransform &transform, bool combine = false);

内部使用 QTransform(3x3 仿射变换矩阵)实现:

cpp 复制代码
// qtbase/src/gui/math3d/qtransform.cpp
// QTransform 内部结构
/*
 *  m11  m12  m13   (m13 为 0 表示平面变换)
 *  m21  m22  m23   (m23 为 0 表示平面变换)
 *  m31  m32  m33   (m33 为 1)
 *
 *  | x' |   | m11 m12 0 | | x |
 *  | y' | = | m21 m22 0 | | y |
 *  | w' |   | m31 m32 1 | | 1 |
 */

4. QPen 与 QBrush 源码解析

4.1 QPen 画笔

QPen 定义了"如何画线"------颜色、宽度、线型、线帽、连接方式。

cpp 复制代码
// qtbase/src/gui/painting/qpen.h
class Q_GUI_EXPORT QPen
{
public:
    enum PenStyle { NoPen, SolidPen, DashPen, DotPen, DashDotPen, DashDotDotPen, CustomDashPen };
    enum PenCapStyle { FlatCap, SquareCap, RoundCap };
    enum PenJoinStyle { MiterJoin, BevelJoin, RoundJoin, SvgMiterJoin };

    QPen() : d(new QPenPrivate) {}
    QPen(const QBrush &brush, qreal width, Qt::PenStyle style = Qt::SolidLine);

    QBrush brush() const;
    qreal width() const;
    qreal widthF() const;
    PenStyle style() const;
    PenCapStyle capStyle() const;
    PenJoinStyle joinStyle() const;

    // 虚函数允许子类自定义
    virtual void setBrush(const QBrush &b);
};

关键原理:虚线样式(DashPen)是通过设置虚线模式(DashPattern)实现的:

cpp 复制代码
// 虚线模式:交替的 [画长, 空长]
// 例如 [5, 3] 表示画5像素空3像素,循环
void setDashPattern(const QVector<qreal> &dashPattern);
// 预设
setStyle(Qt::DashLine);      // dashPattern = [4, 2]
setStyle(Qt::DotLine);       // dashPattern = [1, 2]
setStyle(Qt::DashDotLine);   // dashPattern = [4, 2, 1, 2]

4.2 QBrush 画刷

QBrush 定义了"如何填充"。支持纯色、渐变、纹理三种模式:

cpp 复制代码
// qtbase/src/gui/painting/qbrush.h
class Q_GUI_EXPORT QBrush
{
public:
    enum Style {
        NoBrush,
        SolidPattern,
        Dense1Pattern, Dense2Pattern, ..., Dense7Pattern,
        LinearGradientPattern,
        RadialGradientPattern,
        ConicalGradientPattern,
        TexturePattern  // 使用 QPixmap 作为纹理
    };

    QBrush() : d(new QBrushPrivate), style(SolidPattern) {}
    QBrush(const QColor &color);
    QBrush(Qt::GlobalColor color);
    QBrush(const QBrush &other);
    QBrush(const QPixmap &pixmap);   // 纹理
    QBrush(const QImage &image);     // 纹理
    QBrush(const QGradient &gradient); // 渐变

    QGradient *gradient() const;
    Style style() const;
};

5. 渐变填充源码剖析

Qt 支持三种渐变:线性渐变、径向渐变、圆锥渐变,全部通过 QGradient 实现。

cpp 复制代码
// qtbase/src/gui/painting/qgradient.cpp
QLinearGradient::QLinearGradient(const QPointF &start, const QPointF &stop)
{
    d = new QLinearGradientPrivate(start, stop);
    setSpread(QGradient::PadSpread);
}

// 渐变停止点
void QGradient::setColorAt(qreal pos, const QColor &color)
{
    d_ptr->stops.append(QGradientStop(pos, color));
}

使用示例:

cpp 复制代码
QLinearGradient gradient(0, 0, width(), height());
gradient.setColorAt(0.0, Qt::red);
gradient.setColorAt(0.5, Qt::yellow);
gradient.setColorAt(1.0, Qt::blue);
gradient.setSpread(QGradient::RepeatSpread); // 重复平铺

QPainter painter(this);
painter.setBrush(gradient);
painter.drawRect(rect());

6. QPainterPath 贝塞尔路径

QPainterPath 是 Qt 2D 绘制的瑞士军刀------它表示一个任意复杂的路径,可以复用、存储、检测命中。

cpp 复制代码
// qtbase/src/gui/painting/qpainterpath.h
class Q_GUI_EXPORT QPainterPath
{
public:
    void moveTo(const QPointF &p);
    void lineTo(const QPointF &p);
    void cubicTo(const QPointF &c1, const QPointF &c2, const QPointF &end);
    void quadTo(const QPointF &c, const QPointF &end);
    void closeSubpath();

    bool contains(const QPointF &pt) const;  // 点是否在路径内
    QRectF boundingRect() const;
    void addEllipse(const QRectF &rect);
    void addRect(const QRectF &rect);
    void addText(const QString &text, const QFont &font);
    void addPath(const QPainterPath &path);

    // 布尔运算
    QPainterPath united(const QPainterPath &other) const;
    QPainterPath intersected(const QPainterPath &other) const;
    QPainterPath subtracted(const QPainterPath &other) const;
};

6.1 贝塞尔曲线的实现

Qt 中的 cubicTo() 使用德卡斯特里奥(de Casteljau)算法实现三阶贝塞尔曲线:

cpp 复制代码
// qtbase/src/gui/painting/qpainterpath.cpp
void QPainterPath::cubicTo(const QPointF &c1, const QPointF &c2,
                           const QPointF &endPoint)
{
    // 贝塞尔曲线参数方程:
    // B(t) = (1-t)³P₀ + 3(1-t)²tP₁ + 3(1-t)t²P₂ + t³P₃
    // 其中 t ∈ [0, 1], P₀=起点, P₁=c1, P₂=c2, P₃=终点
    Element e;
    e.type = MoveToElement;  // 先 moveTo 到起点
    // 贝塞尔曲线转换为多段线(t 分成 N 段),存入 path
    // N 的大小由曲率决定,保证精度
}

7. 绘制顺序与合成模式

7.1 合成模式(Composition Mode)

Qt 支持 Porter-Duff 合成模式,通过 setCompositionMode() 设置:

cpp 复制代码
QPainter painter(this);

// 默认:SourceOver(源像素覆盖目标像素)
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);

// 叠加:源像素加上目标像素
painter.setCompositionMode(QPainter::CompositionMode_Plus);

// 异或
painter.setCompositionMode(QPainter::CompositionMode_Xor);

// 只绘制在透明区域
painter.setCompositionMode(QPainter::CompositionMode_SourceAtop);

7.2 绘制顺序的重要性

在 Qt 中,绘制顺序决定最终效果。先画的被后画的覆盖:

cpp 复制代码
void PaintWidget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);

    // 1. 先画背景
    painter.fillRect(rect(), Qt::white);

    // 2. 再画内容(内容在上层)
    painter.setPen(Qt::blue);
    painter.drawText(rect(), Qt::AlignCenter, "Hello Qt");

    // 3. 最后画遮罩(遮罩在最上层)
    painter.fillRect(0, 0, 100, 100, QColor(255, 0, 0, 128)); // 50% 透明红色
}

8. 抗锯齿与渲染提示

通过 setRenderHint() 控制渲染质量:

cpp 复制代码
QPainter painter(this);

// 抗锯齿(最重要)
painter.setRenderHint(QPainter::Antialiasing);

// 文字抗锯齿
painter.setRenderHint(QPainter::TextAntialiasing);

// 平滑变换(缩小/旋转时保持平滑)
painter.setRenderHint(QPainter::SmoothPixmapTransform);

// 高质量反锯齿(Qt 5.12+,使用 FreeType 或 CoreText 的高级反锯齿)
painter.setRenderHint(QPainter::HighQualityAntialiasing);

9. Qt 6 的绘制系统变化

Qt 6 对 2D 绘制系统做了重大改进:

9.1 QPainter 后端重写

Qt 6.0 开始,默认软件渲染引擎从 QRasterPaintEngine 升级为 QPaintEngineEx(代号 Rasterizer2):

  • SIMD 优化:利用 SSE2/NEON 向量指令加速光栅化
  • 新的光栅化器:基于 tessellation(曲面细分)的多边形光栅化,支持非凸多边形的高效填充
  • 批处理优化:相同属性的绘制命令合并处理,减少状态切换开销

9.2 硬件加速

Qt 6 在 macOS 和 Windows 上默认使用 Core Graphics 和 Direct2D 硬件加速:

cpp 复制代码
// qtbase/src/gui/painting/qpainter.cpp
void QPainter::begin(QPaintDevice *pd)
{
    // Qt 6 会自动选择最佳后端
    // QOpenGLPaintDevice 或 QSvgPaintDevice 或 QRasterPaintEngine
}

10. 性能调优清单

技巧 说明
使用 QPicture 缓存绘制命令 QPicture pic; QPainter p(&pic); ...draw...; 之后只需 painter.drawPicture(0, 0, pic);
QImage vs QPixmap 线程内像素操作用 QImage;显示用 QPixmap
减少状态切换 相同属性的绘制连续执行,减少 save/restore
使用 QPainterPath::addPath 合并路径 减少引擎调用次数
避免每帧创建新 QPainter 在 paintEvent 中复用或使用缓存
使用 LogicalDpi vs DeviceDpi 根据精度需求选择

总结

Qt 的 2D 绘制系统通过 QPainter(门面)、QPaintEngine(引擎抽象)、QPaintDevice(目标抽象)三层架构,实现了"写一份代码,跑遍所有平台"的能力。QPainterPath 提供了强大的路径操作能力,QGradient 提供了丰富的渐变效果,QPen/QBrush 则控制着"如何画"与"如何填"。理解这些底层机制,是写出高性能 Qt UI 的前提。

注:若有发现问题欢迎大家提出来纠正

相关推荐
xyq20242 小时前
PostgreSQL LIMIT 指令详解
开发语言
csbysj20202 小时前
Kotlin 数据类与密封类
开发语言
玖別ԅ(¯﹃¯ԅ)2 小时前
C++ Qt + OpenCV 实现本地人脸识别系统:摄像头采集、ONNX模型加载、人脸库比对完整流程
c++·qt
iwS2o90XT2 小时前
Kotlin标准库:实用函数
android·开发语言·kotlin
csbysj20202 小时前
C# 命名空间(Namespace)
开发语言
深蓝海拓2 小时前
Qt的HSL色彩系统
笔记·python·qt·学习
永远睡不够的入2 小时前
C++11新特性(3):lambda不是玄学:从编译器生成的仿函数类彻底搞懂 C++ 匿名函数
开发语言·c++
SilentSamsara2 小时前
综合实战:用 Python 做一个待办事项管理器(CLI 版)
开发语言·python·青少年编程·pycharm