一、QPainter绘图:从基础到进阶
绘图,说白了就是把数据变成画面。在Qt里,QPainter就是那个拿画笔的人。我刚开始学Qt时,觉得绘图很神秘,后来才发现,它其实就是一套固定的流程——你告诉QPainter画什么、怎么画,它就帮你搞定。
这一章,我会带你从零开始,把QPainter的底裤都扒干净。嗯,准备好了吗?
1.1 绘图基础:QPainter的启动与关闭
QPainter不能单独存在,它必须依附在一个“画布”上。这个画布通常是QWidget、QPixmap或者QImage。我习惯把QPainter想象成一个临时工——你把它叫来,它画完就走,不能赖着不走。
基本流程就三步:
- 创建QPainter对象,指定画布
- 调用绘图函数(画线、画圆、画文字等)
- 销毁QPainter对象(或调用end())
看个最简单的例子:
void MyWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this); // 把画家请到当前窗口上
painter.drawLine(10, 10, 100, 100); // 画一条从(10,10)到(100,100)的线
// painter 离开作用域时自动析构,相当于自动收工
}
这里有个坑,我曾经踩过:千万别在paintEvent之外调用QPainter的绘图函数。为什么?因为Qt的绘图系统是事件驱动的,你强行在别处画,要么画不上,要么程序崩。记住,paintEvent才是画家的主场。
QPainter painter(this);一次性创建,然后连续调用drawXXX。不要画一条线就创建一次QPainter,那样效率低,而且容易出问题。
1.2 画笔与画刷:线条与填充的灵魂
QPainter有两大法宝:画笔(QPen) 和 画刷(QBrush)。画笔管线条(轮廓),画刷管填充(内部)。我刚开始时总搞混,后来记住一句话:“笔是线,刷是面”。
画笔可以设置颜色、宽度、样式(实线、虚线、点线等)。画刷可以设置颜色、样式(纯色、渐变、纹理等)。
看个例子:
void MyWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
// 设置画笔:红色,3像素宽,实线
QPen pen(Qt::red, 3, Qt::SolidLine);
painter.setPen(pen);
// 设置画刷:蓝色,纯色填充
QBrush brush(Qt::blue, Qt::SolidPattern);
painter.setBrush(brush);
// 画一个矩形,轮廓红色,内部蓝色
painter.drawRect(50, 50, 200, 100);
}
你想想看,如果只设置画笔不设置画刷,那图形就只有轮廓,内部是透明的。反过来,只设置画刷不设置画笔,图形就只有填充,没有轮廓。两者配合,才能画出完整的图形。
1.3 绘制基本图形:点、线、矩形、椭圆、圆弧
QPainter提供了丰富的drawXXX函数,覆盖了几乎所有基本图形。我列个表,方便你查阅:
| 函数 | 作用 | 示例 |
|---|---|---|
| drawPoint(x, y) | 画一个点 | painter.drawPoint(10, 20); |
| drawLine(x1, y1, x2, y2) | 画一条直线 | painter.drawLine(0, 0, 100, 100); |
| drawRect(x, y, w, h) | 画矩形 | painter.drawRect(10, 10, 80, 60); |
| drawEllipse(x, y, w, h) | 画椭圆(w=h时是圆) | painter.drawEllipse(10, 10, 50, 50); |
| drawArc(x, y, w, h, startAngle, spanAngle) | 画圆弧(角度单位是1/16度) | painter.drawArc(10, 10, 100, 100, 0, 90*16); |
| drawChord(x, y, w, h, startAngle, spanAngle) | 画弦(圆弧+连接两端点的线) | painter.drawChord(10, 10, 100, 100, 0, 90*16); |
| drawPie(x, y, w, h, startAngle, spanAngle) | 画扇形(圆弧+圆心连线) | painter.drawPie(10, 10, 100, 100, 0, 90*16); |
注意:drawArc、drawChord、drawPie的角度单位是1/16度。也就是说,90度要写成90*16。这个设计有点反人类,但Qt就是这么定的,习惯就好。我刚开始时总忘记乘以16,画出来的圆弧永远不对。
另外,drawEllipse的宽高参数是外接矩形的宽高。如果你想画一个圆心在(50,50)、半径为30的圆,应该这样:
painter.drawEllipse(QPointF(50, 50), 30, 30);
这个重载版本更直观,我推荐你用这个。
1.4 渐变与填充:让图形活起来
纯色填充太单调了。Qt提供了三种渐变:线性渐变(QLinearGradient)、径向渐变(QRadialGradient)、锥形渐变(QConicalGradient)。说白了,就是让颜色从A平滑过渡到B。
我项目中用得最多的是线性渐变,比如做一个按钮的悬停效果:
void MyWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
// 线性渐变:从(0,0)到(100,0),即从左到右
QLinearGradient gradient(0, 0, 100, 0);
gradient.setColorAt(0.0, Qt::red); // 起点红色
gradient.setColorAt(0.5, Qt::green); // 中间绿色
gradient.setColorAt(1.0, Qt::blue); // 终点蓝色
painter.setBrush(gradient);
painter.drawRect(10, 10, 200, 100);
}
setColorAt的第一个参数是位置(0.0到1.0),第二个参数是颜色。你可以设置任意多个颜色节点,Qt会自动插值。
径向渐变是从一个点向外扩散:
QRadialGradient gradient(50, 50, 50); // 圆心(50,50),半径50
gradient.setColorAt(0.0, Qt::white);
gradient.setColorAt(1.0, Qt::black);
锥形渐变是围绕一个点旋转:
QConicalGradient gradient(50, 50, 0); // 中心(50,50),起始角度0度
gradient.setColorAt(0.0, Qt::red);
gradient.setColorAt(1.0, Qt::blue);
1.5 抗锯齿与渲染:让边缘不再“狗啃”
默认情况下,Qt绘图是不开抗锯齿的。你画一条斜线,边缘会有明显的锯齿,像楼梯一样。我刚开始做UI时,客户总说“这个边缘怎么毛毛的”,其实就是没开抗锯齿。
开启抗锯齿很简单:
painter.setRenderHint(QPainter::Antialiasing, true);
就这么一行,画出来的线条就平滑多了。代价是性能稍微下降一点,但现代硬件完全扛得住。
除了抗锯齿,还有几个渲染提示:
- QPainter::Antialiasing:抗锯齿,最常用
- QPainter::TextAntialiasing:文字抗锯齿,默认开启
- QPainter::SmoothPixmapTransform:图片缩放时平滑处理
- QPainter::HighQualityAntialiasing:高质量抗锯齿(Qt5.14+)
我一般这样写:
painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform, true);
一次性开启多个。注意,渲染提示要在绘图之前设置,否则可能不生效。
1.6 知识体系总览
下面这张图,是我为你梳理的本章知识结构。你可以把它当作一个地图,随时回来查看:
这张图把本章的五个知识点串起来了。你可以看到,所有操作都围绕paintEvent展开,画笔和画刷是基础,基本图形是输出,渐变让图形更丰富,抗锯齿让输出更漂亮。
1.7 综合示例:画一个带渐变的圆角矩形
最后,我写一个综合示例,把今天讲的东西都用上:
void MyWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
// 线性渐变
QLinearGradient gradient(0, 0, 0, 100);
gradient.setColorAt(0.0, QColor(100, 200, 255));
gradient.setColorAt(1.0, QColor(30, 80, 160));
// 画笔:白色,2像素宽
QPen pen(Qt::white, 2);
painter.setPen(pen);
painter.setBrush(gradient);
// 画圆角矩形
painter.drawRoundedRect(50, 50, 200, 100, 20, 20);
// 画文字
painter.setPen(Qt::white);
painter.setFont(QFont("微软雅黑", 16));
painter.drawText(QRect(50, 50, 200, 100), Qt::AlignCenter, "Hello Qt!");
}
这段代码画了一个带渐变填充的圆角矩形,中间写了白色文字。你运行一下,效果应该很漂亮。嗯,这就是QPainter的魅力——几行代码,就能画出专业的界面。
好了,这一章就到这里。记住,绘图是UI的基础,也是Qt最有趣的部分之一。下一章,我们会深入更高级的绘图技巧,比如坐标变换和路径绘制。但今天,先把这些基础打牢。