1. 背景
Qt 的 2D 渲染体系自底向上大致分为三层:
| 底层渲染后端 | QPainter + QPaintDevice | 直接绘制 2D 图元(线、矩形、椭圆、路径、文字、图像)到任意可绘表面 |
| 中间场景层 | QGraphicsView / QGraphicsScene / QGraphicsItem | 在 QPainter 之上封装场景-视图-图元架构,提供对象化管理 |
| 上层声明式 UI | QML / Qt Quick(Scene Graph) | 声明式语言描述界面,底层用 GPU 加速的场景图渲染 |
为什么还需要 QPainter?
- 自定义控件(QWidget 子类)重写 paintEvent 时,官方推荐方式就是 QPainter;
- 矢量图元、统计图、波形、画板、流程图等"自由画布"场景,QPainter 是最直接、可预测的 API;
- QGraphicsView、QCustomPlot、Qwt 等上层框架,内部最终都落到 QPainter 的 draw 系列调用;
- 离屏渲染(把画面画到 QImage/QPixmap 再复用)是高频性能优化手段。
2. 核心 API 说明
2.1 QPainter 生命周期
// 方式一:栈上构造即 begin(常用)
void Widget::paintEvent(QPaintEvent*) {
QPainter painter(this); // 自动 begin(this)
// … 绘制
} // 析构自动 end()
// 方式二:显式 begin/end(绘制到非 QWidget 表面或中途切换设备时)
QPainter painter;
painter.begin(&image);
// …
painter.end();
关键成员:
| begin(QPaintDevice*) | 开始绘制,返回 bool(设备不支持时 false) |
| end() | 结束绘制;未显式调用时析构函数自动调用 |
| isActive() | 是否处于 begin 之后 end 之前 |
| device() | 当前绘制目标设备 |
| setRenderHint(QPainter::RenderHint, bool) | 设置渲染提示,如 Antialiasing(抗锯齿) |
2.2 QPen(画笔:轮廓)
QPen pen;
pen.setColor(QColor(220, 80, 80));
pen.setWidth(2); // 像素宽度(逻辑单位,受变换影响)
pen.setWidthF(1.5); // 浮点宽度
pen.setStyle(Qt::DashLine); // 线型:SolidLine/DashLine/DotLine/DashDotLine…
pen.setCapStyle(Qt::RoundCap); // 端点帽:FlatCap/SquareCap/RoundCap
pen.setJoinStyle(Qt::RoundJoin); // 连接点:BevelJoin/MiterJoin/RoundJoin
painter.setPen(pen);
painter.setPen(QPen(Qt::blue, 0)); // 宽度 0 = cosmetic 笔(始终 1 像素,不随缩放变粗)
painter.setPen(Qt::NoPen); // 不画轮廓
2.3 QBrush(画刷:填充)
painter.setBrush(QBrush(QColor(100, 200, 150))); // 纯色
painter.setBrush(Qt::BDiagPattern); // 预定义图案
painter.setBrush(QBrush(QPixmap("texture.png"))); // 纹理填充
painter.setBrush(Qt::NoBrush); // 不填充
// 渐变画刷(见 2.6)
QLinearGradient grad(0, 0, 200, 200);
grad.setColorAt(0.0, Qt::white);
grad.setColorAt(1.0, QColor(40, 120, 200));
painter.setBrush(QBrush(grad));
2.4 draw 系列图元 API
| drawPoint / drawPoints | 点 |
| drawLine / drawLines / drawPolyline | 线 / 折线 |
| drawRect / drawRoundedRect | 矩形 / 圆角矩形 |
| drawEllipse / drawArc / drawChord / drawPie | 椭圆 / 弧 / 弓形 / 扇形 |
| drawPolygon | 多边形(自动闭合) |
| drawPath(QPainterPath) | 任意路径(见 2.5) |
| drawText(QRect, int flags, QString) | 文本(见 2.7) |
| drawPixmap / drawImage | 图像(见 2.8) |
| fillRect(QRect, QBrush/QColor) | 直接填充矩形(不走 pen) |
| eraseRect | 擦除(不推荐,用 fillRect 替代) |
所有 draw 均同时受当前 pen(轮廓)与 brush(填充)影响。
2.5 QPainterPath(路径)
QPainterPath path;
path.moveTo(50, 50); // 移动起点
path.lineTo(150, 50); // 直线
path.quadTo(200, 0, 250, 80); // 二次贝塞尔
path.cubicTo(300, 160, 100, 200, 80, 120); // 三次贝塞尔
path.arcTo(QRectF(20, 20, 100, 100), 0, 180); // 圆弧
path.closeSubpath(); // 闭合当前子路径
path.addRect(…); // 添加子路径
path.addEllipse(…);
path.addRoundedRect(…);
path.addText(QPointF, font, "Qt");
painter.drawPath(path);
// 常用谓词
path.isEmpty(); path.contains(QPointF); path.boundingRect();
// 布尔运算(Qt 5.10+ 需要 QtGui 附加模块路径支持)
QPainterPath united = path1.united(path2); // 并集
QPainterPath inter = path1.intersected(path2); // 交集
QPainterPath diff = path1.subtracted(path2); // 差集
2.6 QGradient(渐变)
三种内置渐变 + 一个通用基类:
| QLinearGradient(x1,y1,x2,y2) | 起点/终点定义方向 | 高光、进度条 |
| QRadialGradient(cx,cy,r) | 圆心+半径 | 光晕、球体 |
| QConicalGradient(cx,cy,angle) | 圆心+起始角 | 仪表盘、调色环 |
QLinearGradient g(0, 0, width(), 0); // 水平渐变
g.setColorAt(0.0, QColor(255, 0, 0));
g.setColorAt(0.5, QColor(0, 255, 0));
g.setColorAt(1.0, QColor(0, 0, 255));
g.setSpread(QGradient::RepeatSpread); // 超出范围后的扩散方式
painter.setBrush(g);
2.7 文字绘制
QFont font("Microsoft YaHei", 12); // 注意字体名要存在于系统
font.setBold(true);
painter.setFont(font);
painter.setPen(Qt::black);
painter.drawText(QPoint(10, 30), "左上角基线文本");
painter.drawText(rect, Qt::AlignCenter | Qt::TextWordWrap, "居中换行文本");
// 返回实际绘制到的矩形(可用来测文本尺寸)
QRectF br;
painter.drawText(rect, Qt::AlignCenter, "文本", &br);
QFontMetrics fm(painter.font()); // 字体度量
int w = fm.horizontalAdvance("文本"); // 文本像素宽度(Qt5.11+;旧版用 width())
int h = fm.height(); // 行高
2.8 图像绘制
painter.drawPixmap(x, y, pixmap); // 原尺寸
painter.drawPixmap(QRect(x,y,w,h), pixmap); // 拉伸到目标矩形
painter.drawPixmap(QRect(x,y,w,h), pixmap, QRectF(src)); // 带源区域截取
painter.drawImage(QRect(x,y,w,h), image); // QImage 同理
// 平滑缩放
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
2.9 坐标系统与变换
QPainter 默认坐标系:原点在设备左上角,x 向右,y 向下,单位是逻辑像素。
painter.translate(100, 50); // 平移
painter.rotate(45); // 旋转(度,顺时针)
painter.scale(2.0, 2.0); // 缩放
painter.shear(0.1, 0); // 错切
// 必须成对使用,否则变换叠加污染后续绘制
painter.save();
painter.translate(cx, cy);
painter.rotate(angle);
painter.drawText(QPointF(0, -10), "标签");
painter.restore();
// 世界变换矩阵(Qt 5.14+ 提供更精确的 QTransform 重载)
QTransform t;
t.translate(50, 50).rotate(30).scale(1.5, 1.5);
painter.setWorldTransform(t);
painter.resetTransform(); // 重置为单位矩阵
视图变换(Qt 5.14+):setViewport / setWindow 允许把逻辑窗口映射到设备视口,适合固定逻辑坐标系的绘图(如曲线图 y 轴范围固定):
painter.setWindow(QRect(0, 0, 1000, 500)); // 逻辑窗口
painter.setViewport(QRect(0, 0, width(), height())); // 物理区域
// 此后所有坐标按 1000×500 逻辑空间书写,自动映射
2.10 离屏渲染与缓存
// 常用缓存载体
QPixmap pm(size); // 屏幕优化,绘制在屏幕关联设备上最快(依赖平台)
QImage img(size, QImage::Format_ARGB32_Premultiplied); // 像素可直读,适合 CPU 处理/保存
QPicture pic; // 记录绘制指令(矢量),可重放
// 离屏绘制标准姿势
QPixmap pm(size);
pm.fill(Qt::transparent); // 先填充透明,否则初始内容未定义
QPainter p(&pm);
p.setRenderHint(QPainter::Antialiasing);
// … 绘制
p.end();
// 之后在 paintEvent 中 blit 上屏
painter.drawPixmap(0, 0, pm);
// QImage 逐像素处理示例
QImage img(400, 300, QImage::Format_ARGB32_Premultiplied);
img.fill(Qt::white);
for (int y = 0; y < img.height(); ++y)
for (int x = 0; x < img.width(); ++x)
img.setPixelColor(x, y, QColor(x % 256, y % 256, 128));
img.save("out.png"); // 支持 png/jpg/bmp 等(按后缀选格式)
2.11 组合模式(Composition Mode,Qt 4.6+)
painter.setCompositionMode(QPainter::CompositionMode_SourceOver); // 默认
painter.setCompositionMode(QPainter::CompositionMode_Source); // 直接覆盖
painter.setCompositionMode(QPainter::CompositionMode_Plus); // 叠加增亮
painter.setCompositionMode(QPainter::CompositionMode_DestinationOver);
// … 共 30 种模式,适合实现橡皮擦、发光、遮罩等效果
// 橡皮擦常用:先设置 Source 模式,再用透明色绘制
painter.setCompositionMode(QPainter::CompositionMode_Clear);
painter.fillRect(rect, Qt::transparent);
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
3. 详细使用说明
3.1 最小可运行示例:带渐变与抗锯齿的自定义控件
// widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
class Widget : public QWidget {
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent *event) override;
};
#endif
// widget.cpp
#include "widget.h"
#include <QPainter>
#include <QPainterPath>
#include <QLinearGradient>
Widget::Widget(QWidget *parent) : QWidget(parent) {
setMinimumSize(400, 300);
}
void Widget::paintEvent(QPaintEvent *) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing, true); // 抗锯齿,画曲线必备
// 1) 渐变圆角矩形
QLinearGradient grad(0, 0, width(), height());
grad.setColorAt(0.0, QColor(70, 130, 220));
grad.setColorAt(1.0, QColor(30, 60, 130));
p.setBrush(grad);
p.setPen(QPen(Qt::white, 2));
p.drawRoundedRect(rect().adjusted(10, 10, -10, -10), 12, 12);
// 2) 贝塞尔曲线路径
QPainterPath curve;
curve.moveTo(20, 200);
curve.cubicTo(120, 40, 280, 260, 380, 120);
p.setPen(QPen(QColor(255, 220, 100), 3));
p.setBrush(Qt::NoBrush);
p.drawPath(curve);
// 3) 居中文案
p.setPen(Qt::white);
p.setFont(QFont("Microsoft YaHei", 14, QFont::Bold));
p.drawText(rect(), Qt::AlignCenter, "Hello QPainter");
}
3.2 坐标变换实战:绘制旋转的风扇叶片
void FanWidget::paintEvent(QPaintEvent *) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
const QPointF c = rect().center();
const int n = 3;
for (int i = 0; i < n; ++i) {
p.save(); // 保存未变换状态
p.translate(c); // 平移到中心
p.rotate(angle_ + i * 360.0 / n); // 旋转
p.setBrush(QColor(80, 160, 255, 180));
p.setPen(Qt::NoPen);
p.drawEllipse(QRectF(-90, -20, 180, 40)); // 以原点为中心的叶片
p.restore(); // 恢复,避免旋转叠加
}
}
要点:每次变换前 save、结束后 restore,否则第二次循环的 rotate 会在第一次基础上叠加,叶片会越转越歪。
3.3 离屏渲染 + 缓存:拖动不闪烁的画板
class BoardWidget : public QWidget {
QPixmap canvas_;
QPoint last_;
protected:
void paintEvent(QPaintEvent *) override {
QPainter p(this);
p.drawPixmap(0, 0, canvas_); // 每次重绘只需 blit 缓存
}
void mousePressEvent(QMouseEvent *e) override {
last_ = e->pos();
}
void mouseMoveEvent(QMouseEvent *e) override {
QPainter p(&canvas_); // 直接在缓存上绘制
p.setPen(QPen(Qt::black, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
p.drawLine(last_, e->pos());
last_ = e->pos();
update(); // 触发重绘,paintEvent 中 blit
}
void resizeEvent(QResizeEvent *e) override {
QPixmap next(size());
next.fill(Qt::white);
QPainter p(&next);
p.drawPixmap(0, 0, canvas_); // 保留旧内容
canvas_ = next;
QWidget::resizeEvent(e);
}
};
这是白板/涂鸦类应用的标准架构:交互绘制到离屏缓存,paintEvent 只做 blit,天然防闪烁、性能高。
3.4 简单柱状图(QPainter 手写图表)
void ChartWidget::paintEvent(QPaintEvent *) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
const QVector<double> data{42, 78, 55, 91, 66};
const int n = data.size();
const double bw = width() / double(n) * 0.6; // 柱宽
const double maxV = 100.0;
// 坐标轴
p.setPen(QPen(Qt::gray, 1));
p.drawLine(QPointF(20, height() – 20), QPointF(width() – 10, height() – 20));
p.drawLine(QPointF(20, height() – 20), QPointF(20, 10));
// 柱体(顶部渐变)
for (int i = 0; i < n; ++i) {
double h = data[i] / maxV * (height() – 60);
double x = 30 + i * (width() – 40) / n + (width() – 40) / n * 0.2;
QRectF bar(x, height() – 20 – h, bw, h);
QLinearGradient g(bar.topLeft(), bar.bottomLeft());
g.setColorAt(0, QColor(90, 170, 255));
g.setColorAt(1, QColor(40, 90, 200));
p.setBrush(g);
p.setPen(Qt::NoPen);
p.drawRoundedRect(bar, 3, 3);
// 数值标签
p.setPen(Qt::black);
p.drawText(bar.adjusted(0, -22, 0, -6), Qt::AlignCenter, QString::number(data[i]));
}
}
4. 常错点 / 坑(高发清单)
4.1 重复 begin 导致警告或崩溃
用 QPainter p(this) 后又在函数里调用 p.begin(…),或对已 begin 的 painter 再次 begin,会输出 QPainter::begin: Painter already active 且返回 false。规则:栈上构造即 begin,之后只准 end 一次(可省略);需要切换设备时用无参构造 + 显式 begin。
4.2 在 paintEvent 之外绘制 QWidget 不生效
QWidget 的内容绘制只能发生在 paintEvent 中(系统随时会擦除重画)。想主动重画调用 update()(合并请求)或 repaint()(立即重画,阻塞,慎用)。非 paintEvent 中画 widget 的正确做法:画到离屏 QPixmap/QImage,再在 paintEvent 里 blit。
4.3 变换未 save/restore 导致污染
translate/rotate/scale 是有状态的,会影响其后的所有绘制。忘记 restore 的典型症状:第二次刷新画面位置越偏越远、图形越来越小/大。每个变换块前后必须 save()/restore() 配对。
4.4 高 DPI / 缩放模糊
- 未启用高 DPI 支持时,高分屏上 QPainter 画的文字/线条发虚:程序入口加 QApplication::setHighDpiScaleFactorRoundingPolicy(…),并在 main 最前面 QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling)(Qt 5;Qt 6 默认开启)。
- 图片拉伸模糊:需要 SmoothPixmapTransform 渲染提示。
- 线条随缩放变粗:想要恒 1 像素用宽度 0(cosmetic pen)。
4.5 中文乱码 / 字体缺失
- 明确设置中文字体:QFont("Microsoft YaHei") 或 "SimHei",不要依赖默认字体;
- 系统无该字体时 Qt 会回退,可能显示豆腐块;
- 画布控件设置字体后记得 painter.setFont(font),painter 不会自动继承 widget 字体。
4.6 性能陷阱
- 在 paintEvent 中创建渐变/字体/大对象:每次重绘都重建。应在构造函数缓存为成员变量。
- 抗锯齿全开:圆角+渐变+阴影叠加时 CPU 开销显著,只有曲线/斜线需要时开启,矩形/文字可不开。
- 频繁 update() 造成连续重绘:合并为定时器节流(如 30ms 一次)。
- 大 QPixmap 反复绘制:用 drawTiledPixmap 平铺或缩小目标区域。
- QImage 逐像素 setPixelColor 很慢:批量用 setPixelColor 循环 1000×1000 约需几十 ms 级;需要高性能图像处理优先操作 bits()/scanLine() 指针或改用 GPU 方案。
4.7 QPixmap 未填充就绘制
QPixmap pm(size) 初始内容未定义(取决于平台,可能是垃圾数据)。离屏绘制前必须先 pm.fill(Qt::transparent) 或填充底色,否则出现花屏/黑边。
4.8 QImage 格式选择错误
- Format_ARGB32 是不预乘 alpha,Format_ARGB32_Premultiplied 是预乘,二者混用会导致颜色发暗/发亮;
- 默认 Format_ARGB32(Qt 5 中 QImage 默认就是它)在部分绘制操作上比预乘慢;需要 alpha 合成建议用 Premultiplied;
- Format_RGB32 无 alpha,pixelColor 读 alpha 恒为 255。
4.9 线程绘制安全
QPainter 绘制 QImage 是线程安全的(只要该 QImage 不被其它线程同时读写),但绘制 QPixmap / QWidget 不是。后台线程画图请用 QImage,完成后通过信号槽把结果交给 GUI 线程再转 QPixmap 上屏。
4.10 drawText 对不齐 / 测量误差
- drawText(QPoint, …) 的 y 是基线(baseline)坐标,不是文字顶部,第一眼觉得"偏上了"是正常的;
- 想要精确居中用 drawText(QRect, Qt::AlignCenter, …);
- 宽度测量用 QFontMetrics::horizontalAdvance(不要用废弃的 width(),多字节字符会偏小)。
4.11 组合模式状态泄漏
设置了 CompositionMode_Clear 或 Source 后忘记恢复 SourceOver,后续所有绘制都会变成覆盖/擦除效果。改组合模式同样建议 save/restore 包裹。
4.12 resizeEvent 中未保留画板内容
画板类控件 resize 后 QPixmap 尺寸变化,直接 new 一块空的会把旧内容清掉;应按 3.3 示例把旧画布内容复制到新画布。
5. 总结
QPainter 是 Qt 2D 绘制的根:自定义控件、离屏渲染、图表、画板、甚至上层 Graphics View / QCustomPlot / QML 场景图,底层都离不开它。掌握它的关键是理解三件事:
日常使用记住 8 条避坑铁律:begin 只一次、绘制只在 paintEvent、变换必配对 save/restore、QPixmap 先 fill、抗锯齿按需开、中文显式设字体、QImage 预乘格式、线程画 QImage 不画 QPixmap。
FAQ 速查表
| 画笔宽度为 0 是什么意思? | cosmetic 笔,恒为 1 像素,不随 scale 变粗 |
| 想画透明背景的缓存图? | pm.fill(Qt::transparent) 且使用带 alpha 的格式 |
| 画出来的圆有锯齿? | setRenderHint(QPainter::Antialiasing, true) |
| 重绘闪烁怎么办? | 离屏 QPixmap 绘制 + paintEvent 只 blit |
| 如何清空画板? | canvas.fill(Qt::white) 后 update() |
| 后台线程能绘制吗? | 只能画 QImage,画完转 QPixmap 交 GUI 线程 |
| 文字垂直不对齐? | drawText(QPoint) 的 y 是基线;居中用 QRect 重载 |
| 图片放大模糊? | SmoothPixmapTransform 渲染提示 |
| 高 DPI 下模糊? | Qt6 默认开启;Qt5 需 AA_EnableHighDpiScaling |
| 如何保存绘制结果? | QImage::save(path);矢量保存用 QPicture 或导出 SVG(QSvgGenerator) |
网硕互联帮助中心


评论前必须登录!
注册