4、图形绘制引擎:QPainter基础、绘制基本图元(点、线、矩形、圆)、画笔与画刷设置、抗锯齿与渲染优化

好,咱们今天聊聊EPS软件里最核心、也最直观的部分——图形绘制引擎。说白了,就是怎么在屏幕上画出那些点、线、面。你想想看,一个绘图软件,如果连基本图形都画不漂亮,那用户肯定扭头就走。我当年刚入行时,就吃过这个亏,画出来的线总是有毛刺,被老大骂了一顿。从那以后,我就把QPainter摸得透透的。

4.1 QPainter:你的绘图画笔

QPainter是Qt里负责绘制的核心类。你可以把它想象成一支真正的画笔。它本身不存储图形,它只负责“画”这个动作。你需要给它一个“画布”,也就是一个QPaintDevice,比如QWidget、QImage或者QPixmap。

我个人习惯,在paintEvent事件里创建QPainter对象。为什么?因为这是Qt推荐的官方做法,能保证绘制操作在正确的上下文中执行。代码大概长这样:

void MyWidget::paintEvent(QPaintEvent *) {
    QPainter painter(this);
    // 开始画吧!
    painter.drawLine(10, 10, 100, 100);
}

嗯,这里要注意:QPainter对象一定要在栈上创建,别用new。它会在析构时自动做一些清理工作,比如结束绘制状态。我曾经见过有人用new QPainter,结果忘了delete,导致内存泄漏,画面还卡顿。这坑我踩过,你们就别踩了。

4.2 绘制基本图元:点、线、矩形、圆

掌握了画笔,咱们就开始画点东西。QPainter提供了丰富的绘制函数,咱们挑最常用的几个说说。

4.2.1 点

画点最简单,用drawPoint()。但注意,点的大小由画笔的宽度决定。如果你画了一个宽度为1的画笔,点就是一个像素;宽度为5,点就是一个5x5的方块。

painter.drawPoint(50, 50); // 画一个点

4.2.2 线

画线用drawLine()。可以传两个点,也可以传四个坐标值。我个人喜欢传四个整数,代码更紧凑。

painter.drawLine(10, 10, 200, 10); // 画一条水平线

4.2.3 矩形

画矩形用drawRect()。它接受一个QRect对象,或者直接传x, y, width, height。如果你想要圆角矩形,用drawRoundedRect()

painter.drawRect(50, 50, 100, 80); // 画一个矩形

4.2.4 圆(椭圆)

画圆其实是用drawEllipse()。它画的是椭圆,如果你传的宽高相等,那就是正圆。

painter.drawEllipse(100, 100, 50, 50); // 画一个半径为50的圆
小技巧: 如果你需要画一个精确的圆,记得让宽高相等。我在项目中遇到过,有人传了50和51,结果画出来是个扁圆,排查了半天才发现是参数写错了。

4.3 画笔与画刷设置

光画出来还不够,你得控制线条的颜色、粗细、样式,以及填充区域的纹理。这就轮到QPen和QBrush登场了。

4.3.1 QPen:控制线条

QPen控制线条的样式。你可以设置颜色、宽度、线型(实线、虚线、点线等)。

QPen pen;
pen.setColor(Qt::red);
pen.setWidth(3);
pen.setStyle(Qt::DashLine); // 虚线
painter.setPen(pen);

我个人习惯,在绘制前先设置好画笔,然后一口气画完。别画一笔改一次,那样性能会下降。你想想看,每次setPen都会触发内部状态更新,频繁调用肯定慢。

4.3.2 QBrush:控制填充

QBrush控制填充区域的颜色和纹理。可以是纯色、渐变、或者图片纹理。

QBrush brush;
brush.setColor(Qt::blue);
brush.setStyle(Qt::SolidPattern); // 纯色填充
painter.setBrush(brush);

如果你想要渐变填充,可以用QLinearGradient或QRadialGradient。这个在画一些高级图形时特别有用,比如模拟光照效果。

避坑指南: 我曾经在画一个复杂图形时,忘了设置画笔,结果画出来的矩形没有边框。后来才发现,QPainter默认的画笔是黑色实线,但如果你之前设置过其他画笔,一定要记得重置。我的习惯是:每次paintEvent开始,先调用painter.setPen(Qt::NoPen)painter.setPen(QPen())来确保状态干净。

4.4 抗锯齿与渲染优化

好了,图形能画了,颜色也能控制了。但你会发现,画出来的线条边缘有锯齿,尤其是斜线或圆。这就是所谓的“锯齿效应”。怎么解决?抗锯齿。

4.4.1 开启抗锯齿

QPainter提供了setRenderHint()方法。你只需要在绘制前调用一下:

painter.setRenderHint(QPainter::Antialiasing, true);

就这么简单。开启后,线条边缘会变得平滑。但代价是性能会下降一点点。为什么?因为抗锯齿需要计算每个像素的透明度,计算量大了。

我建议:只在需要高质量输出的地方开启抗锯齿。比如画圆、画斜线时。画水平或垂直线时,其实没必要,因为本身就没有锯齿。

4.4.2 其他渲染优化

除了抗锯齿,还有几个优化点值得注意:

  • 减少绘制调用次数: 能用drawPoints()一次画多个点,就别用drawPoint()循环画。QPainter内部对批量绘制有优化。
  • 使用QPixmap缓存: 如果某个图形不经常变化,可以把它画到一个QPixmap上,然后在paintEvent里直接贴图。这比每次都重新绘制快得多。
  • 避免在paintEvent里做复杂计算: paintEvent应该只负责绘制,别在里面做排序、查找等操作。把这些计算放到外面,绘制时直接用结果。
警告: 抗锯齿不是万能的。如果你把抗锯齿开到最大,同时画了几千个图形,帧率会掉得很厉害。我在做地图渲染时遇到过,画了5000个点,开启抗锯齿后帧率从60帧掉到15帧。后来我改用QPainter::HighQualityAntialiasing(Qt 5.0之后已废弃,用QPainter::Antialiasing即可),并配合setRenderHint(QPainter::SmoothPixmapTransform, false)来平衡质量与性能。

4.5 实战小例子:画一个带边框的圆

说了这么多,咱们来个小例子。画一个红色的圆,蓝色边框,开启抗锯齿。

void MyWidget::paintEvent(QPaintEvent *) {
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);

    // 设置画笔:蓝色,宽度2,实线
    QPen pen(Qt::blue, 2, Qt::SolidLine);
    painter.setPen(pen);

    // 设置画刷:红色,纯色填充
    QBrush brush(Qt::red, Qt::SolidPattern);
    painter.setBrush(brush);

    // 画圆
    painter.drawEllipse(50, 50, 100, 100);
}

这段代码跑起来,你会看到一个漂亮的红色圆,边缘平滑,没有锯齿。嗯,这就是QPainter的基本功。

好了,这一章就到这里。下一章咱们聊聊更高级的图形变换,比如旋转、缩放、平移。这些在EPS软件里经常用到,比如旋转一个图形元素。到时候见!