Qt技巧笔记(十):QPainter 详解与实践指南

Qt技巧笔记(十):QPainter 详解与实践指南

1. QPainter 概述

QPainter是Qt框架中用于二维图形绘制的核心类,其提供了一套完整、强大的API来在各种绘制设备上进行图形操作。无论是简单的线条绘制还是复杂的图形变换,广泛用于自定义控件、绘制背景、图形编辑器等场景。由QPainterQPaintDevice QPaintEngine三类共同构成了Qt强大的二维绘图系统。

  • QPainter 用于执行绘图操作,其提供的API在GUI或QImageQOpenGLPaintDeviceQWidgetQPaintDevice 显示图形(线、形状、渐变等),文本和图形。
  • QPaintDevice 不直接绘制物理显示画图,而利用逻辑界面的中间媒介。例如,绘制矩形图形时,为了将对象绘制到QWidgetQGLPixelBufferQImageQPixmapQPicture等多种界面中间,必须使用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: 包含dxdy的偏移向量 平移坐标系
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 的子类,如 QWidgetQPixmapQImage 等)上工作。初始化即建立 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),所有的绘制操作先在后台缓冲区完成,绘制完成后,再将整个缓冲区的内容一次性拷贝到屏幕上(前台缓冲区)。

  • 前台缓冲区:屏幕当前显示的内容。
  • 后台缓冲区:内存中的一张画布(例如 QPixmapQImage),所有绘图指令都先在这里执行。

这样做可以避免直接在屏幕上逐像素更新造成的闪烁,因为屏幕的更新是一次性完成的,而不是一部分一部分地呈现。双缓冲的作用:

  • 消除闪烁:如果直接在 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开发者不可或缺的强大工具。希望本文能帮助读者深入理解并有效运用这一重要的图形绘制框架。

posted @ 2026-02-27 17:01  GeoFXR  阅读(0)  评论(0)    收藏  举报