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 的前提。

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

相关推荐
用户805533698037 小时前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner7 小时前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz5 天前
QML Hello World 入门示例
qt
xcyxiner8 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner9 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner9 天前
DicomViewer (添加模型类)3
qt
xcyxiner10 天前
DicomViewer (目录调整) 2
qt
xcyxiner10 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00612 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术12 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript