引言
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/Y与physicalDpiX/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 的前提。
注:若有发现问题欢迎大家提出来纠正