Qt技巧笔记(十):QPainter 详解与实践指南
Qt技巧笔记(十):QPainter 详解与实践指南
1. QPainter 概述
QPainter是Qt框架中用于二维图形绘制的核心类,其提供了一套完整、强大的API来在各种绘制设备上进行图形操作。无论是简单的线条绘制还是复杂的图形变换,广泛用于自定义控件、绘制背景、图形编辑器等场景。由QPainter、QPaintDevice 、QPaintEngine三类共同构成了Qt强大的二维绘图系统。
QPainter用于执行绘图操作,其提供的API在GUI或QImage、QOpenGLPaintDevice、QWidget和QPaintDevice显示图形(线、形状、渐变等),文本和图形。QPaintDevice不直接绘制物理显示画图,而利用逻辑界面的中间媒介。例如,绘制矩形图形时,为了将对象绘制到QWidget、QGLPixelBuffer、QImage、QPixmap、QPicture等多种界面中间,必须使用QPaintDevice。QPaintEngine提供了一些接口,可用于QPainter在不同的设备上进行绘制。
绘图系统由 QPainter 完成具体的绘制操作,QPainter 类提供了大量高度优化的函数来完成 GUI 编程所需要的大部分绘制工作。它可以绘制一切想要的图形,从最简单的一条直线到其他任何复杂的图形,例如:点、线、矩形、弧形、饼状图、多边形、贝塞尔弧线等。此外,QPainter 也支持一些高级特性,例如反走样(针对文字和图形边缘)、像素混合、渐变填充和矢量路径等,QPainter 也支持线性变换,例如平移、旋转、缩放。
2. QPainter 绘制图形的常用方法
QPainter 的常用方法按照功能分类进行总结,方便你快速查阅和使用。
| 类别 | API接口 | 参数含义 | 功能说明 |
|---|---|---|---|
| 基本类型 | drawPoint(int x, int y) |
x: 点的X坐标;y: 点的Y坐标 |
绘制单个点 |
void drawPoint(const QPoint &p) |
p: 包含X和Y坐标的点对象 |
绘制单个点 | |
void drawLine(int x1, int y1, int x2, int y2) |
x1,y1: 起点坐标;x2,y2: 终点坐标 |
绘制直线 | |
void drawLine(const QPoint &p1, const QPoint &p2) |
p1: 起点;p2: 终点 |
绘制直线 | |
void drawLine(const QLine &line) |
line: 包含起点和终点的直线对象 |
绘制直线 | |
void drawRect(int x, int y, int w, int h) |
x,y: 左上角坐标;w: 宽度;h: 高度 |
绘制矩形 | |
void drawRoundedRect(const QRectF &rect, qreal xRadius, qreal yRadius, Qt::SizeMode mode = Qt::AbsoluteSize) |
rect: 矩形区域;xRadius: X轴圆角半径;yRadius: Y轴圆角半径;mode: 半径单位模式(绝对尺寸或相对尺寸) |
绘制圆角矩形 | |
void drawEllipse(const QRectF &rect) |
center: 椭圆中心;rx: X轴半径;ry: Y轴半径 |
绘制椭圆 | |
void drawEllipse(const QRectF &rect) |
rect: 椭圆的外接矩形 |
绘制椭圆 | |
void drawEllipse(const QRect &rect) |
rect: 椭圆的外接矩形 |
绘制椭圆 | |
void drawEllipse(int x, int y, int w, int h) |
x,y,w,h: 外接矩形的参数 |
绘制椭圆 | |
void drawArc(const QRectF &rect, int startAngle, int spanAngle) |
ct: 弧所在椭圆的外接矩形;startAngle: 起始角度(1/16度);spanAngle: 跨越角度(1/16度) |
绘制弧线 | |
void drawPie(const QRectF &rect, int startAngle, int spanAngle) |
参数同上 | 绘制扇形(包含圆心) | |
void drawChord(const QRectF &rect, int startAngle, int spanAngle) |
rect: 弧所在椭圆的外接矩形;startAngle: 起始角度(1/16度);spanAngle: 跨越角度(1/16度) |
绘制弦(弧两端连线封闭) | |
void drawChord(int x, int y, int w, int h, int startAngle, int spanAngle) |
同上 | 绘制弦 | |
void drawPolygon(const QPolygonF &polygon, Qt::FillRule fillRule = Qt::OddEvenFill) |
polygon: 多边形顶点列表;fillRule: 填充规则(奇偶填充或弯曲填充) |
绘制多边形 | |
void drawPolygon(const QPolygon &polygon, Qt::FillRule fillRule = Qt::OddEvenFill) |
同上 | 绘制多边形 | |
void drawConvexPolygon(const QPolygonF &polygon) |
polygon: 凸多边形顶点列表 |
绘制凸多边形(效率更高) | |
void drawConvexPolygon(const QPolygon &polygon) |
同上 | 绘制凸多边形 | |
| 文本 | void drawText(const QPointF &pos, const QString &text) |
pos: 文本基线左端点;text: 要绘制的文本 |
绘制文本 |
void drawText(int x, int y, const QString &text) |
x,y: 文本基线左端点坐标;text: 文本 |
绘制文本 | |
void drawText(const QRectF &rect, int flags, const QString &text, QRectF *boundingRect = nullptr) |
rect: 文本绘制区域;flags: 对齐标志(如Qt::AlignCenter);text: 文本;boundingRect: 返回实际文本边界 |
在矩形内绘制文本 | |
void drawText(const QRect &rect, int flags, const QString &text, QRect *boundingRect = nullptr) |
同上,使用整数矩形 | 在矩形内绘制文本 | |
void drawText(int x, int y, int w, int h, int flags, const QString &text, QRect *boundingRect = nullptr) |
x,y,w,h: 矩形参数;其余同上 |
在矩形内绘制文本 | |
QRectF boundingRect(const QRectF &rect, int flags, const QString &text) |
参数同上,无boundingRect输出参数;返回计算出的文本边界矩形 |
计算文本边界 | |
QRect boundingRect(const QRect &rect, int flags, const QString &text) |
同上 | 计算文本边界 | |
| 图像与像素图 | void drawImage(const QPointF &point, const QImage &image) |
point: 图像左上角放置位置;image: 要绘制的图像 |
绘制QImage的指定区域到目标区域 |
void drawImage(const QRectF &target, const QImage &image, const QRectF &source, Qt::ImageConversionFlags flags = Qt::AutoColor) |
target: 目标区域(可能缩放); image: 源图像;source: 源图像中要绘制的子区域;flags: 颜色转换选项 |
绘制QPixmap |
|
void drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source) |
target: 目标区域;pixmap: 源像素图;source: 源图像中要绘制的子区域 |
绘制QPixmap指定区域到目标区域 |
|
void drawTiledPixmap(const QRectF &rect, const QPixmap &pixmap, const QPointF &offset = QPointF()) |
rect: 要平铺填充的目标区域;pixmap: 平铺用的像素图;offset: 平铺起始偏移 |
平铺绘制像素图 | |
| 复杂路径 | void drawPath(const QPainterPath &path) |
path: 包含直线、曲线等子路径的路径对象 |
绘制复杂路径 |
| 填充与擦除 | void fillRect(const QRectF &rect, const QBrush &brush) |
rect: 要填充的矩形;brush: 画刷(定义颜色、样式、渐变等) |
填充矩形 |
void fillRect(int x, int y, int w, int h, const QColor &color) |
x,y,w,h: 矩形参数;color: 填充颜色 |
填充矩形 | |
void fillRect(const QRect &rect, Qt::GlobalColor color) |
rect: 矩形;color: 全局颜色枚举(如Qt::red) |
填充矩形 | |
void eraseRect(const QRectF &rect) |
rect: 要擦除的矩形区域 |
擦除矩形(用背景色填充) | |
void eraseRect(int x, int y, int w, int h) |
x,y,w,h: 矩形参数 |
擦除矩形 | |
| 状态与变换 | void save() |
无参数 | 保存当前画家状态(画笔、画刷、变换等) |
void restore() |
无参数 | 恢复上次保存的画家状态 | |
void translate(qreal dx, qreal dy) |
dx: X轴偏移量;dy: Y轴偏移量 |
平移坐标系 | |
void translate(const QPointF &offset) |
offset: 包含dx和dy的偏移向量 |
平移坐标系 | |
void rotate(qreal angle) |
angle: 旋转角度(度,顺时针为正) |
旋转坐标系 | |
void scale(qreal sx, qreal sy) |
sx: X轴缩放因子;sy: Y轴缩放因子 |
缩放坐标系 | |
void shear(qreal sh, qreal sv) |
sh: 水平剪切因子;sv: 垂直剪切因子 |
剪切坐标系 | |
void setRenderHint(RenderHint hint, bool on = true) |
hint: 渲染提示枚举(如Antialiasing);on: 启用或禁用 |
设置渲染选项 |
注意:在默认情况下,QPainter 使用的坐标系非常简单直接:
- 原点 (Origin):位于绘图设备(如
QWidget窗口、QPixmap等)的左上角(0, 0)。 - X轴方向:水平向右,数值递增。
- Y轴方向:垂直向下,数值递增。
- 单位 (Unit):在基于像素的设备(如屏幕)上,1个单位代表 1个像素;在打印机上,1个单位代表1个点(1/72 英寸)。
这种设计符合屏幕显示的常规认知。
3. QPainter 的基本使用详解
3.1 初始化与资源管理
QPainter 必须在绘制设备(QPaintDevice 的子类,如 QWidget、QPixmap、QImage 等)上工作。初始化即建立 QPainter 与设备的关联。
// 在QPixmap上绘制
QPixmap pixmap(200, 200);
pixmap.fill(Qt::white);
{
QPainter painter(&pixmap); // 自动开始绘制
// 绘制操作
} // 自动结束绘制,资源释放
// 在QWidget的paintEvent中
void MyWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this); // this指向当前widget
// 绘制操作
}
3.2 通过Qpen 画笔设置样式的例子
在 Qt 中,QPen 定义了绘制线条和轮廓线的样式。通过 QPen 可以设置线的颜色、宽度、线型(实线、虚线等)、笔帽样式(端点形状)以及连接样式(折线连接处的形状)。下面通过完整的示例展示如何使用 QPen 设置各种样式。
QPen pen;
pen.setColor(QColor(255, 0, 0)); // 红色
pen.setWidth(3); // 3像素宽度
pen.setStyle(Qt::DashLine); // 虚线样式
pen.setCapStyle(Qt::RoundCap); // 线端圆角
painter.setPen(pen);
完整案例:绘制不同笔样式的线段
#include <QPainter>
#include <QWidget>
#include <QApplication>
class PenDemoWidget : public QWidget
{
protected:
void paintEvent(QPaintEvent *event) override
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing); // 抗锯齿让线条平滑
// 设置坐标和间隔
int y = 30;
int step = 30;
// 1. 默认实线
QPen pen1;
painter.setPen(pen1);
painter.drawLine(20, y, 300, y);
painter.drawText(310, y+5, "SolidLine (default)");
y += step;
// 2. 红色虚线,宽度2
QPen pen2(Qt::red, 2, Qt::DashLine);
painter.setPen(pen2);
painter.drawLine(20, y, 300, y);
painter.drawText(310, y+5, "DashLine");
y += step;
// 3. 蓝色点线,宽度3,圆帽
QPen pen3;
pen3.setColor(Qt::blue);
pen3.setWidth(3);
pen3.setStyle(Qt::DotLine);
pen3.setCapStyle(Qt::RoundCap);
painter.setPen(pen3);
painter.drawLine(20, y, 300, y);
painter.drawText(310, y+5, "DotLine + RoundCap");
y += step;
// 4. 绿色点划线,宽度2,方形帽
QPen pen4(QBrush(Qt::green), 2, Qt::DashDotLine);
pen4.setCapStyle(Qt::SquareCap);
painter.setPen(pen4);
painter.drawLine(20, y, 300, y);
painter.drawText(310, y+5, "DashDotLine + SquareCap");
y += step;
// 5. 自定义虚线模式
QPen pen5;
pen5.setColor(Qt::magenta);
pen5.setWidth(2);
QVector<qreal> dashes;
dashes << 5 << 2 << 10 << 2; // 画5空2画10空2
pen5.setDashPattern(dashes);
pen5.setStyle(Qt::CustomDashLine);
painter.setPen(pen5);
painter.drawLine(20, y, 300, y);
painter.drawText(310, y+5, "CustomDash (5,2,10,2)");
y += step;
// 6. 连接样式演示:绘制折线
y += 30;
painter.setPen(QPen(Qt::darkYellow, 8, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
drawPoly(&painter, y, "MiterJoin"); y += 80;
painter.setPen(QPen(Qt::darkYellow, 8, Qt::SolidLine, Qt::FlatCap, Qt::BevelJoin));
drawPoly(&painter, y, "BevelJoin"); y += 80;
painter.setPen(QPen(Qt::darkYellow, 8, Qt::SolidLine, Qt::FlatCap, Qt::RoundJoin));
drawPoly(&painter, y, "RoundJoin");
}
void drawPoly(QPainter *painter, int yOffset, const QString &label)
{
QPolygon poly;
poly << QPoint(50, yOffset) << QPoint(150, yOffset+30) << QPoint(250, yOffset-20);
painter->drawPolyline(poly);
painter->drawText(260, yOffset+5, label);
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
PenDemoWidget w;
w.resize(500, 500);
w.show();
return app.exec();
}
3.3 通过 QBrush 画笔设置样式的例子
QBrush 是 Qt 中用于定义填充样式的类,它决定了封闭图形(如矩形、椭圆、多边形)内部如何着色。与 QPen(负责轮廓线)不同,QBrush 主要设置填充的颜色、图案、渐变或纹理。下面通过完整示例演示 QBrush 的各种用法。下面QBrush 的主要属性:
| 属性 | 设置方法 | 说明 |
|---|---|---|
| 颜色 | setColor(const QColor &) |
填充颜色(对纯色和某些图案有效) |
| 样式 | setStyle(Qt::BrushStyle) |
填充样式,如实心、各种密度图案、渐变等 |
| 纹理 | setTexture(const QPixmap &) |
用像素图作为填充纹理 |
| 渐变 | setGradient(const QGradient &) |
设置渐变填充(线性、径向、圆锥) |
| 变换 | setMatrix(const QMatrix &) setTransform(const QTransform &) |
对纹理或渐变应用坐标变换 |
其中的Qt预定义的画刷样式Qt::BrushStyle:
| 枚举值 | 效果 |
|---|---|
Qt::NoBrush |
无填充 |
Qt::SolidPattern |
实心填充 |
Qt::Dense1Pattern ~ Dense7Pattern |
不同密度的点状图案 |
Qt::HorPattern |
水平线 |
Qt::VerPattern |
垂直线 |
Qt::CrossPattern |
交叉线 |
Qt::BDiagPattern |
左斜线 |
Qt::FDiagPattern |
右斜线 |
Qt::DiagCrossPattern |
斜交叉线 |
Qt::LinearGradientPattern |
线性渐变(需配合 QGradient) |
Qt::RadialGradientPattern |
径向渐变 |
Qt::ConicalGradientPattern |
圆锥渐变 |
Qt::TexturePattern |
纹理填充 |
// 纯色填充
painter.setBrush(QBrush(Qt::blue));
// 渐变填充
QLinearGradient gradient(0, 0, 100, 100);
gradient.setColorAt(0, Qt::white);
gradient.setColorAt(1, Qt::black);
painter.setBrush(QBrush(gradient));
// 纹理填充
painter.setBrush(QBrush(QPixmap("texture.png")));
完整案例:
#include <QPainter>
#include <QWidget>
#include <QApplication>
#include <QLinearGradient>
#include <QRadialGradient>
#include <QConicalGradient>
class BrushDemoWidget : public QWidget
{
protected:
void paintEvent(QPaintEvent *) override
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
int x = 20, y = 20, w = 80, h = 60, spacing = 100;
// 1. 纯色填充
painter.setBrush(QBrush(Qt::red));
painter.drawRect(x, y, w, h);
painter.drawText(x, y + h + 15, "Solid (red)");
// 2. 预定义图案填充
painter.setBrush(QBrush(Qt::blue, Qt::Dense4Pattern));
painter.drawRect(x + spacing, y, w, h);
painter.drawText(x + spacing, y + h + 15, "Dense4Pattern");
painter.setBrush(QBrush(Qt::green, Qt::CrossPattern));
painter.drawRect(x + 2*spacing, y, w, h);
painter.drawText(x + 2*spacing, y + h + 15, "CrossPattern");
// 3. 纹理填充(使用内置QPixmap)
QPixmap texture(16, 16);
texture.fill(Qt::transparent);
QPainter p(&texture);
p.fillRect(0, 0, 8, 8, Qt::gray);
p.fillRect(8, 8, 8, 8, Qt::gray);
p.end();
painter.setBrush(QBrush(texture));
painter.drawRect(x + 3*spacing, y, w, h);
painter.drawText(x + 3*spacing, y + h + 15, "Texture");
// 4. 线性渐变
QLinearGradient linearGrad(QPointF(0, 0), QPointF(w, h));
linearGrad.setColorAt(0.0, Qt::yellow);
linearGrad.setColorAt(0.5, Qt::red);
linearGrad.setColorAt(1.0, Qt::green);
painter.setBrush(QBrush(linearGrad));
painter.drawRect(x, y + spacing, w, h);
painter.drawText(x, y + spacing + h + 15, "LinearGradient");
// 5. 径向渐变
QRadialGradient radialGrad(QPointF(x + w/2, y + spacing + h/2), w/2, QPointF(x + w/3, y + spacing + h/3));
radialGrad.setColorAt(0.0, Qt::cyan);
radialGrad.setColorAt(0.5, Qt::magenta);
radialGrad.setColorAt(1.0, Qt::yellow);
painter.setBrush(QBrush(radialGrad));
painter.drawEllipse(x + spacing, y + spacing, w, h);
painter.drawText(x + spacing, y + spacing + h + 15, "RadialGradient");
// 6. 圆锥渐变
QConicalGradient conicalGrad(QPointF(x + 2*spacing + w/2, y + spacing + h/2), 45);
conicalGrad.setColorAt(0.0, Qt::red);
conicalGrad.setColorAt(0.2, Qt::green);
conicalGrad.setColorAt(0.4, Qt::blue);
conicalGrad.setColorAt(0.6, Qt::yellow);
conicalGrad.setColorAt(0.8, Qt::magenta);
conicalGrad.setColorAt(1.0, Qt::red);
painter.setBrush(QBrush(conicalGrad));
painter.drawEllipse(x + 2*spacing, y + spacing, w, h);
painter.drawText(x + 2*spacing, y + spacing + h + 15, "ConicalGradient");
// 7. 半透明填充
painter.setBrush(QBrush(QColor(255, 0, 0, 100))); // 红色半透明
painter.setPen(Qt::NoPen);
painter.drawEllipse(x + 3*spacing + 20, y + spacing - 10, w, h);
painter.setPen(Qt::black);
painter.drawText(x + 3*spacing, y + spacing + h + 15, "Translucent");
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
BrushDemoWidget w;
w.resize(500, 300);
w.show();
return app.exec();
}
3.4 基本类型绘制操作—几何图形绘制
// 线条和简单形状
painter.drawLine(10, 10, 100, 100); // 线段
painter.drawRect(50, 50, 100, 80); // 矩形
painter.drawRoundedRect(50, 50, 100, 80, 10, 10); // 圆角矩形
painter.drawEllipse(100, 100, 80, 60); // 椭圆
painter.drawArc(150, 150, 100, 100, 30*16, 120*16); // 圆弧
// 多边形
QPolygon polygon;
polygon << QPoint(10, 10) << QPoint(100, 30)
<< QPoint(80, 100) << QPoint(20, 80);
painter.drawPolygon(polygon);
3.5 基本类型绘制—文本和图像绘制
// 文本绘制
painter.setFont(QFont("微软雅黑", 12, QFont::Bold));
painter.drawText(10, 30, "静态文本");
painter.drawText(QRect(10, 50, 200, 50),
Qt::AlignCenter | Qt::TextWordWrap,
"多行对齐文本");
// 图像绘制
QPixmap image(":/icon.png");
painter.drawPixmap(10, 100, 64, 64, image);
4.高级特性深度探索
4.1 坐标系变换
坐标系变换是 QPainter 中非常强大的功能,它允许我们通过移动、旋转、缩放、扭曲绘图坐标系,从而简化复杂图形的绘制,实现图形与窗口大小的自适应,以及制作动画效果。下面详细介绍 QPainter 坐标系变换的原理、函数用法、实践示例以及注意事项。坐标系变换是QPainter最强大的功能之一,可以实现复杂的图形效果。其基本变换操作的案例:
// 保存当前状态
painter.save();
// 平移变换
painter.translate(100, 50);
// 旋转变换(以当前原点为中心)
painter.rotate(45);
// 缩放变换
painter.scale(1.5, 0.8);
// 此时绘制会应用所有变换
painter.drawRect(0, 0, 50, 50);
// 恢复之前的状态
painter.restore();
其复杂变换的组合案例:
// 围绕特定点旋转
painter.save();
painter.translate(centerPoint); // 移动到旋转中心
painter.rotate(angle); // 旋转
painter.translate(-centerPoint); // 移回原位置
painter.drawRect(targetRect); // 绘制
painter.restore();
4.2 抗锯齿与渲染优化
抗锯齿(Antialiasing)与渲染优化是 Qt 绘图中两个紧密相关的话题。开启抗锯齿可以显著提升图形质量,但也会带来一定的性能开销。因此,理解其原理及如何优化渲染过程,对于开发流畅且美观的应用程序至关重要。当你调用 painter.setRenderHint(QPainter::Antialiasing) 后,规则变得直观很多:像素会被对称地渲染在数学定义点的两侧。这会让图形边缘看起来更平滑,但可能会显得稍微有些模糊。QPainter 通过 setRenderHint 支持以下几种与抗锯齿相关的渲染提示:
| 渲染提示 | 作用 |
|---|---|
QPainter::Antialiasing |
对基本图形(如直线、曲线、多边形)启用抗锯齿。 |
QPainter::TextAntialiasing |
对文本启用抗锯齿。通常默认开启,但可以强制关闭以提升速度。 |
QPainter::SmoothPixmapTransform |
当对像素图进行变换(如缩放、旋转)时,使用平滑滤波算法,避免马赛克。 |
// 开启抗锯齿(提高质量,降低性能)
painter.setRenderHint(QPainter::Antialiasing);
// 开启文本抗锯齿
painter.setRenderHint(QPainter::TextAntialiasing);
// 平滑像素图变换
painter.setRenderHint(QPainter::SmoothPixmapTransform);
// 高性能模式(需要时关闭高质量渲染)
painter.setRenderHint(QPainter::Antialiasing, false);
4.3 双缓冲技术实践
双缓冲(Double Buffering)的核心思想是:在内存中创建一个与屏幕显示区域兼容的后台缓冲区(Off‑screen Buffer),所有的绘制操作先在后台缓冲区完成,绘制完成后,再将整个缓冲区的内容一次性拷贝到屏幕上(前台缓冲区)。
- 前台缓冲区:屏幕当前显示的内容。
- 后台缓冲区:内存中的一张画布(例如
QPixmap或QImage),所有绘图指令都先在这里执行。
这样做可以避免直接在屏幕上逐像素更新造成的闪烁,因为屏幕的更新是一次性完成的,而不是一部分一部分地呈现。双缓冲的作用:
- 消除闪烁:如果直接在
paintEvent中绘图,每次重绘时屏幕会先擦除背景,再绘制前景,这种频繁的清屏与绘制过程肉眼可见,导致闪烁。双缓冲通过一次像素块拷贝避免了这个过程。 - 提升性能:对于复杂图形,后台缓冲区的绘制可以脱离屏幕刷新率的限制,绘制完成后一次提交。
- 支持局部更新:配合
update()的矩形区域,可以减少不必要的绘制开销。
从 Qt 4 开始,许多窗口部件(如 QWidget)默认已经启用了双缓冲。当你在 paintEvent 中使用 QPainter 绘制时,实际上已经是在一个后台像素图上操作,最后这个像素图会被自动合成到屏幕上。因此,对于大多数简单的自定义控件,不需要手动实现双缓冲。可以通过 setAutoFillBackground(false) 并结合 Qt::WA_OpaquePaintEvent 属性进一步优化,但这不是本节的重点。
标准双缓冲实现:
1.头文件(Widget.h)
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QPixmap>
#include <QPoint>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
protected:
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
private:
QPixmap m_pixmap; // 后台缓冲区
QPoint m_lastPoint;// 上一个鼠标点
bool m_drawing; // 是否正在绘制
};
#endif // WIDGET_H
2.源文件(Widget.cpp)
#include "Widget.h"
#include <QPainter>
#include <QMouseEvent>
#include <QResizeEvent>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, m_drawing(false)
{
setAttribute(Qt::WA_OpaquePaintEvent); // 告诉 Qt 我们不希望自动擦除背景
resize(400, 300);
// 初始化后台缓冲区,与当前窗口大小一致
m_pixmap = QPixmap(size());
m_pixmap.fill(Qt::white); // 填充白色背景
}
Widget::~Widget()
{
}
void Widget::paintEvent(QPaintEvent *event)
{
// 直接将后台缓冲区的内容绘制到窗口
QPainter painter(this);
painter.drawPixmap(0, 0, m_pixmap);
}
void Widget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
m_lastPoint = event->pos();
m_drawing = true;
}
}
void Widget::mouseMoveEvent(QMouseEvent *event)
{
if ((event->buttons() & Qt::LeftButton) && m_drawing) {
QPoint currentPoint = event->pos();
// 在后台缓冲区上绘制线段
QPainter painter(&m_pixmap);
painter.setPen(QPen(Qt::blue, 2, Qt::SolidLine, Qt::RoundCap));
painter.drawLine(m_lastPoint, currentPoint);
// 更新当前点
m_lastPoint = currentPoint;
// 触发窗口重绘,将更新后的缓冲区显示出来
update();
}
}
void Widget::resizeEvent(QResizeEvent *event)
{
// 当窗口大小改变时,调整后台缓冲区大小并保留原有内容
QPixmap newPixmap(event->size());
newPixmap.fill(Qt::white);
QPainter painter(&newPixmap);
// 将旧缓冲区的内容绘制到新缓冲区的左上角(也可以根据需求缩放)
painter.drawPixmap(0, 0, m_pixmap);
m_pixmap = newPixmap;
}
5.完整示例代码
下面是一个综合性的 QPainter 演示程序,涵盖了 基本图形绘制、笔样式、画刷样式、坐标系变换、双缓冲绘图 以及 抗锯齿 的全局控制。程序使用 QTabWidget 将不同类别的示例分页展示,每个页面独立实现其绘制逻辑,并包含一个全局的“抗锯齿”复选框,方便对比效果。
#include <QApplication>
#include <QWidget>
#include <QTabWidget>
#include <QVBoxLayout>
#include <QCheckBox>
#include <QPainter>
#include <QPainterPath>
#include <QLinearGradient>
#include <QRadialGradient>
#include <QConicalGradient>
#include <QMouseEvent>
#include <QPixmap>
#include <QDebug>
// ==================== 全局抗锯齿控制 ====================
bool g_antialias = true; // 默认开启
// ==================== 页面1:基本图形绘制 ====================
class BasicDrawingPage : public QWidget
{
protected:
void paintEvent(QPaintEvent *) override
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, g_antialias);
int x = 20, y = 20, w = 80, h = 60, spacing = 100;
// 点
painter.setPen(QPen(Qt::red, 5));
painter.drawPoint(30, 30);
// 线
painter.setPen(QPen(Qt::blue, 2));
painter.drawLine(50, 20, 150, 80);
// 矩形
painter.setPen(Qt::black);
painter.setBrush(Qt::yellow);
painter.drawRect(x, y, w, h);
// 圆角矩形
painter.setBrush(Qt::green);
painter.drawRoundedRect(x + spacing, y, w, h, 15, 15);
// 椭圆
painter.setBrush(Qt::cyan);
painter.drawEllipse(x + 2*spacing, y, w, h);
// 弧
painter.setBrush(Qt::NoBrush);
painter.setPen(QPen(Qt::red, 2));
painter.drawArc(x + 3*spacing, y, w, h, 30*16, 120*16);
// 弦
painter.setPen(QPen(Qt::blue, 2));
painter.drawChord(x, y + spacing, w, h, 30*16, 120*16);
// 扇形
painter.setBrush(Qt::magenta);
painter.setPen(Qt::darkMagenta);
painter.drawPie(x + spacing, y + spacing, w, h, 30*16, 120*16);
// 多边形
QPolygon polygon;
polygon << QPoint(x + 2*spacing + 20, y + spacing)
<< QPoint(x + 2*spacing + 60, y + spacing + 30)
<< QPoint(x + 2*spacing + 10, y + spacing + 50);
painter.setBrush(QColor(255,165,0)); // 橙色
painter.drawPolygon(polygon);
}
};
// ==================== 页面2:笔样式演示 ====================
class PenStylePage : public QWidget
{
protected:
void paintEvent(QPaintEvent *) override
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, g_antialias);
int y = 20;
int step = 30;
// 默认实线
painter.setPen(QPen(Qt::black, 2));
painter.drawLine(20, y, 300, y);
painter.drawText(310, y+5, "SolidLine");
y += step;
// 虚线
painter.setPen(QPen(Qt::red, 2, Qt::DashLine));
painter.drawLine(20, y, 300, y);
painter.drawText(310, y+5, "DashLine");
y += step;
// 点线 + 圆帽
QPen pen(Qt::blue, 4);
pen.setStyle(Qt::DotLine);
pen.setCapStyle(Qt::RoundCap);
painter.setPen(pen);
painter.drawLine(20, y, 300, y);
painter.drawText(310, y+5, "DotLine + RoundCap");
y += step;
// 点划线 + 方帽
pen.setColor(Qt::darkGreen);
pen.setStyle(Qt::DashDotLine);
pen.setCapStyle(Qt::SquareCap);
pen.setWidth(3);
painter.setPen(pen);
painter.drawLine(20, y, 300, y);
painter.drawText(310, y+5, "DashDotLine + SquareCap");
y += step;
// 自定义虚线模式
QVector<qreal> dashes;
dashes << 5 << 2 << 10 << 2;
pen.setColor(Qt::magenta);
pen.setStyle(Qt::CustomDashLine);
pen.setDashPattern(dashes);
painter.setPen(pen);
painter.drawLine(20, y, 300, y);
painter.drawText(310, y+5, "CustomDash (5,2,10,2)");
y += 50;
// 连接样式演示
drawPolyWithJoin(&painter, y, Qt::MiterJoin, "MiterJoin");
y += 80;
drawPolyWithJoin(&painter, y, Qt::BevelJoin, "BevelJoin");
y += 80;
drawPolyWithJoin(&painter, y, Qt::RoundJoin, "RoundJoin");
}
void drawPolyWithJoin(QPainter *painter, int yOffset, Qt::PenJoinStyle join, const QString &label)
{
QPolygon poly;
poly << QPoint(50, yOffset) << QPoint(150, yOffset+30) << QPoint(250, yOffset-20);
QPen pen(Qt::darkYellow, 8, Qt::SolidLine, Qt::FlatCap, join);
painter->setPen(pen);
painter->drawPolyline(poly);
painter->drawText(260, yOffset+5, label);
}
};
// ==================== 页面3:画刷样式演示 ====================
class BrushStylePage : public QWidget
{
protected:
void paintEvent(QPaintEvent *) override
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, g_antialias);
int x = 20, y = 20, w = 80, h = 60, spacing = 100;
// 纯色填充
painter.setBrush(QBrush(Qt::red));
painter.drawRect(x, y, w, h);
painter.drawText(x, y + h + 15, "Solid");
// 图案填充
painter.setBrush(QBrush(Qt::blue, Qt::Dense4Pattern));
painter.drawRect(x + spacing, y, w, h);
painter.drawText(x + spacing, y + h + 15, "Dense4");
painter.setBrush(QBrush(Qt::green, Qt::CrossPattern));
painter.drawRect(x + 2*spacing, y, w, h);
painter.drawText(x + 2*spacing, y + h + 15, "Cross");
// 纹理填充(简单棋盘格)
QPixmap texture(16, 16);
texture.fill(Qt::transparent);
QPainter p(&texture);
p.fillRect(0, 0, 8, 8, Qt::gray);
p.fillRect(8, 8, 8, 8, Qt::gray);
p.end();
painter.setBrush(QBrush(texture));
painter.drawRect(x + 3*spacing, y, w, h);
painter.drawText(x + 3*spacing, y + h + 15, "Texture");
// 线性渐变
QLinearGradient linearGrad(QPointF(x, y+spacing), QPointF(x+w, y+spacing+h));
linearGrad.setColorAt(0, Qt::yellow);
linearGrad.setColorAt(0.5, Qt::red);
linearGrad.setColorAt(1, Qt::green);
painter.setBrush(linearGrad);
painter.drawRect(x, y + spacing, w, h);
painter.drawText(x, y + spacing + h + 15, "LinearGrad");
// 径向渐变
QRadialGradient radialGrad(QPointF(x+spacing+w/2, y+spacing+h/2), w/2);
radialGrad.setColorAt(0, Qt::cyan);
radialGrad.setColorAt(0.5, Qt::magenta);
radialGrad.setColorAt(1, Qt::yellow);
painter.setBrush(radialGrad);
painter.drawEllipse(x + spacing, y + spacing, w, h);
painter.drawText(x + spacing, y + spacing + h + 15, "RadialGrad");
// 圆锥渐变
QConicalGradient conicalGrad(QPointF(x+2*spacing+w/2, y+spacing+h/2), 45);
conicalGrad.setColorAt(0, Qt::red);
conicalGrad.setColorAt(0.2, Qt::green);
conicalGrad.setColorAt(0.4, Qt::blue);
conicalGrad.setColorAt(0.6, Qt::yellow);
conicalGrad.setColorAt(0.8, Qt::magenta);
conicalGrad.setColorAt(1, Qt::red);
painter.setBrush(conicalGrad);
painter.drawEllipse(x + 2*spacing, y + spacing, w, h);
painter.drawText(x + 2*spacing, y + spacing + h + 15, "ConicalGrad");
// 半透明
painter.setBrush(QColor(255, 0, 0, 100));
painter.setPen(Qt::NoPen);
painter.drawEllipse(x + 3*spacing + 20, y + spacing - 10, w, h);
painter.setPen(Qt::black);
painter.drawText(x + 3*spacing, y + spacing + h + 15, "Translucent");
}
};
// ==================== 页面4:坐标系变换演示 ====================
class TransformPage : public QWidget
{
protected:
void paintEvent(QPaintEvent *) override
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, g_antialias);
int cx = width() / 2;
int cy = height() / 2;
// 绘制坐标轴(灰色)
painter.setPen(QPen(Qt::lightGray, 1, Qt::DashLine));
painter.drawLine(0, cy, width(), cy);
painter.drawLine(cx, 0, cx, height());
// 示例1:平移
painter.save();
painter.translate(100, 50);
painter.setPen(QPen(Qt::red, 2));
painter.drawRect(0, 0, 80, 50);
painter.drawText(0, -5, "translate(100,50)");
painter.restore();
// 示例2:旋转(绕原点旋转30度)
painter.save();
painter.translate(300, 80);
painter.rotate(30);
painter.setPen(QPen(Qt::blue, 2));
painter.drawRect(0, 0, 80, 50);
painter.drawText(0, -5, "rotate(30)");
painter.restore();
// 示例3:缩放
painter.save();
painter.translate(120, 200);
painter.scale(1.5, 0.8);
painter.setPen(QPen(Qt::darkGreen, 2));
painter.drawRect(0, 0, 80, 50);
painter.drawText(0, -5, "scale(1.5,0.8)");
painter.restore();
// 示例4:组合变换(先平移后旋转,使矩形绕自身中心旋转)
painter.save();
painter.translate(350, 200);
painter.rotate(45);
painter.setPen(QPen(Qt::magenta, 2));
painter.drawRect(-40, -25, 80, 50); // 中心在原点
painter.drawText(-40, -30, "rotate around center");
painter.restore();
// 示例5:窗口-视口变换(始终保持一个200x200的正方形区域映射到窗口中央)
painter.save();
int side = qMin(width(), height()) / 2;
painter.setWindow(-100, -100, 200, 200); // 逻辑坐标范围 [-100,100]
painter.setViewport(cx - side/2, cy - side/2, side, side); // 物理正方形区域
painter.fillRect(-50, -50, 100, 100, QColor(255,255,0,100));
painter.setPen(Qt::black);
painter.drawRect(-50, -50, 100, 100);
painter.drawText(-50, -60, "Window/Viewport");
painter.restore();
}
};
// ==================== 页面5:双缓冲绘图画板 ====================
class DoubleBufferPage : public QWidget
{
Q_OBJECT
public:
DoubleBufferPage(QWidget *parent = nullptr) : QWidget(parent)
{
setAttribute(Qt::WA_OpaquePaintEvent); // 避免自动擦除背景,减少闪烁
m_pixmap = QPixmap(size());
m_pixmap.fill(Qt::white);
m_drawing = false;
}
protected:
void paintEvent(QPaintEvent *) override
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, g_antialias);
painter.drawPixmap(0, 0, m_pixmap); // 直接显示后台缓冲区
}
void resizeEvent(QResizeEvent *event) override
{
// 调整缓冲区大小,保留原有内容
QPixmap newPixmap(event->size());
newPixmap.fill(Qt::white);
QPainter painter(&newPixmap);
painter.drawPixmap(0, 0, m_pixmap);
m_pixmap = newPixmap;
}
void mousePressEvent(QMouseEvent *event) override
{
if (event->button() == Qt::LeftButton) {
m_lastPoint = event->pos();
m_drawing = true;
}
}
void mouseMoveEvent(QMouseEvent *event) override
{
if ((event->buttons() & Qt::LeftButton) && m_drawing) {
QPoint currentPoint = event->pos();
// 在后台缓冲区上绘制
QPainter painter(&m_pixmap);
painter.setRenderHint(QPainter::Antialiasing, g_antialias);
painter.setPen(QPen(Qt::blue, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
painter.drawLine(m_lastPoint, currentPoint);
m_lastPoint = currentPoint;
update(); // 触发刷新显示
}
}
void mouseReleaseEvent(QMouseEvent *) override
{
m_drawing = false;
}
private:
QPixmap m_pixmap;
QPoint m_lastPoint;
bool m_drawing;
};
// ==================== 主窗口 ====================
class MainWindow : public QWidget
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr) : QWidget(parent)
{
setWindowTitle("QPainter 完整演示");
resize(800, 600);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
// 抗锯齿复选框
QCheckBox *aaCheck = new QCheckBox("启用抗锯齿 (Antialiasing)");
aaCheck->setChecked(g_antialias);
connect(aaCheck, &QCheckBox::toggled, [this](bool checked){
g_antialias = checked;
// 刷新所有标签页(通过触发每个页面的重绘)
for (int i = 0; i < tabWidget->count(); ++i) {
tabWidget->widget(i)->update();
}
});
mainLayout->addWidget(aaCheck);
// 标签页
tabWidget = new QTabWidget;
tabWidget->addTab(new BasicDrawingPage, "基本图形");
tabWidget->addTab(new PenStylePage, "笔样式");
tabWidget->addTab(new BrushStylePage, "画刷样式");
tabWidget->addTab(new TransformPage, "坐标系变换");
tabWidget->addTab(new DoubleBufferPage, "双缓冲画板");
mainLayout->addWidget(tabWidget);
}
private:
QTabWidget *tabWidget;
};
// ==================== main ====================
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow w;
w.show();
return app.exec();
}
#include "main.moc" // 如果使用 qmake,需要包含 moc 文件;若使用 CMake 自动处理,可忽略此行
6. QPainter 总结
QPainter作为Qt图形系统的核心,提供了强大而灵活的二维图形绘制能力。通过掌握其基本用法和高级特性,开发者可以实现从简单的UI元素到复杂的数据可视化等各种图形需求。关键在于理解其状态机模型和坐标系变换机制,结合合理的优化策略,就能创造出既美观又高效的图形应用程序。无论是桌面应用、嵌入式界面还是数据可视化项目,QPainter都是Qt开发者不可或缺的强大工具。希望本文能帮助读者深入理解并有效运用这一重要的图形绘制框架。

浙公网安备 33010602011771号