第十六章:QT绘图——QPainter、QPen、QBrush与图形绘制
绘图,说白了就是让程序能「画东西」。在QT里,这件事主要由三个类搞定:QPainter(画家)、QPen(笔)和QBrush(刷子)。
我刚开始学QT绘图时,总觉得这玩意儿不就是画个圆、画个方块吗?后来做项目才发现,绘图的水很深——从简单的控件自绘,到复杂的图表、游戏界面,都离不开它。今天我们就把它彻底搞明白。
16.1 QPainter:你的画板
QPainter是QT绘图的核心。它就像你手里的画笔,可以在任何「画布」(QPaintDevice)上作画。常见的画布有QWidget、QPixmap、QImage等。
使用QPainter有个固定套路:
void MyWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this); // 在当前的widget上画
painter.setPen(Qt::blue);
painter.drawText(20, 20, "Hello, QT!");
}
嗯,这里要注意:绘图代码必须写在paintEvent()里。你不能随便找个函数就开始画,系统会在需要重绘时自动调用paintEvent()。如果你强行在其他地方画,画完一刷新就没了——我当年踩过这个坑。
16.2 QPen:线条的灵魂
QPen控制线条的样式:颜色、宽度、线型(实线、虚线、点线等)。
QPen pen;
pen.setColor(QColor(255, 0, 0)); // 红色
pen.setWidth(3); // 3像素宽
pen.setStyle(Qt::DashLine); // 虚线
painter.setPen(pen);
我在项目中遇到过一个问题:画出来的线总是模糊的。后来发现是坐标没对齐——奇数宽度的线,坐标要偏移0.5像素,否则线条边缘会模糊。你想想看,像素是整数格子,线宽3像素时,如果坐标在整数位置,线条会跨越两个像素格子,导致抗锯齿效果变差。
| 线型枚举 | 说明 | 效果 |
|---|---|---|
| Qt::SolidLine | 实线 | —————— |
| Qt::DashLine | 虚线 | - - - - - - |
| Qt::DotLine | 点线 | · · · · · · |
| Qt::DashDotLine | 点划线 | -·-·-·-· |
16.3 QBrush:填充的艺术
QBrush负责填充封闭图形的内部。可以是纯色、渐变色,甚至是纹理图案。
QBrush brush;
brush.setColor(Qt::green);
brush.setStyle(Qt::Dense7Pattern); // 密集的斜线纹理
painter.setBrush(brush);
painter.drawRect(50, 50, 100, 80);
常用的填充样式有:
- Qt::SolidPattern:纯色填充
- Qt::HorPattern:水平线纹理
- Qt::VerPattern:垂直线纹理
- Qt::CrossPattern:交叉线纹理
- Qt::Dense1Pattern ~ Dense7Pattern:不同密度的点阵纹理
16.4 绘制基本图形
QT提供了丰富的绘图API,常用的有:
// 画直线
painter.drawLine(QPoint(10, 10), QPoint(100, 100));
// 画矩形
painter.drawRect(20, 20, 80, 60);
// 画圆角矩形
painter.drawRoundedRect(20, 20, 80, 60, 10, 10);
// 画椭圆(圆是椭圆的特例)
painter.drawEllipse(QPoint(100, 100), 50, 30);
// 画圆弧
painter.drawArc(QRect(20, 20, 100, 100), 0, 90 * 16);
// 画多边形
QPolygon polygon;
polygon << QPoint(10, 10) << QPoint(100, 10) << QPoint(50, 80);
painter.drawPolygon(polygon);
我个人习惯把常用图形的绘制封装成工具函数,比如:
void drawStar(QPainter &painter, QPoint center, int radius)
{
QPolygon star;
for (int i = 0; i < 5; ++i) {
double angle = -M_PI / 2 + i * 2 * M_PI / 5;
star << QPoint(center.x() + radius * cos(angle),
center.y() + radius * sin(angle));
angle += M_PI / 5;
star << QPoint(center.x() + radius * 0.4 * cos(angle),
center.y() + radius * 0.4 * sin(angle));
}
painter.drawPolygon(star);
}
这样在paintEvent里直接调用就行,代码清爽很多。
16.5 渐变与变换
渐变让图形看起来更立体、更专业。QT支持三种渐变:
- QLinearGradient:线性渐变,从一点到另一点颜色逐渐变化
- QRadialGradient:径向渐变,从中心向外扩散
- QConicalGradient:锥形渐变,围绕中心旋转
// 线性渐变
QLinearGradient gradient(0, 0, 100, 100);
gradient.setColorAt(0.0, Qt::red);
gradient.setColorAt(0.5, Qt::yellow);
gradient.setColorAt(1.0, Qt::green);
painter.setBrush(gradient);
painter.drawRect(0, 0, 100, 100);
变换则是对整个坐标系进行操作。常用的有平移、旋转、缩放:
painter.save(); // 保存当前状态
painter.translate(100, 50); // 平移
painter.rotate(45); // 旋转45度
painter.scale(1.5, 1.5); // 缩放1.5倍
painter.drawRect(0, 0, 50, 50);
painter.restore(); // 恢复状态
16.6 双缓冲绘图
双缓冲是解决绘图闪烁问题的标准方案。原理很简单:先在内存里画好,再一次性贴到屏幕上。
为什么会闪烁?因为直接在屏幕上画时,你画一条线,屏幕就刷新一次。画100条线,屏幕就闪100次。双缓冲相当于先在后台画完,然后「啪」一下贴上去,用户只看到最终结果。
QT里实现双缓冲有两种方式:
方式一:使用QPixmap
void MyWidget::paintEvent(QPaintEvent *)
{
QPixmap buffer(size());
buffer.fill(Qt::white);
QPainter bufferPainter(&buffer);
// 在缓冲区画各种图形
bufferPainter.drawLine(...);
bufferPainter.drawRect(...);
// 最后一次性贴到屏幕上
QPainter painter(this);
painter.drawPixmap(0, 0, buffer);
}
方式二:使用QWidget的autoFillBackground
其实QT的QWidget默认就支持双缓冲。你只要在paintEvent里正常画就行,QT内部会自动处理。但如果你自己用QPainter直接画在屏幕上(比如在鼠标事件里画),那就需要手动实现双缓冲。
知识体系总览
下面这张图帮你理清本章的核心逻辑:
这张图把本章的核心知识点串起来了。你从QPainter入手,配合QPen和QBrush,就能画出各种基本图形。再加上渐变和变换,图形会更有层次感。最后用双缓冲解决性能问题——一套组合拳下来,QT绘图你就拿下了。
公众号:蓝海资料掘金营,微信deep3321