QPainter绘制线宽大于1的曲线时耗时骤增如何优化
问题现象
我以绘制正弦波作为测试场景,分别使用QPainter::drawPolyline()与QPainter::drawPath()接口统计曲线绘制耗时。测试发现无论采用哪种绘制接口,只要将画笔宽度设置为大于1,正弦波的绘制耗时就会增长10倍以上。
测试代码如下:
Widget::Widget(QWidget* parent) : QWidget(parent) { setAutoFillBackground(true); setAttribute(Qt::WA_NoSystemBackground, true); // 生成正弦波数据 for(qsizetype ii = 0; ii < 1000000; ++ii) sineWave << (1.0 + sin(2 * M_PI * 3 * ii / 1000000)); } void Widget::paintEvent(QPaintEvent* evt) { QPainter painter(this); painter.fillRect(rect(), QColor(Qt::white)); painter.setRenderHint(QPainter::Antialiasing); QPen pen; pen.setWidthF(2); // 设置画笔宽度 painter.setPen(pen); QElapsedTimer timer; timer.start(); qDebug() << "-> Paint Event\tPen Width:" << pen.widthF() << "\tWidget WxH:" << width() << "x" << height(); QList<QPointF> pixPoints; for(qsizetype ii = 0; ii < sineWave.length(); ++ii) { double yPos = height() * (sineWave[ii] / 2.0); double xPos = width() * (ii / 1000000.0); pixPoints << QPointF(xPos, yPos); } qDebug() << "--> 1: Time [ms]:" << (timer.nsecsElapsed() / 1000000.0); painter.drawPolyline(pixPoints); qDebug() << "--> 2: Time [ms]:" << (timer.nsecsElapsed() / 1000000.0); }
- 笔宽设置为2时的运行输出:
-> Paint Event Pen Width: 2 Widget WxH: 722 x 494 --> 1: Time [ms]: 109.352 --> 2: Time [ms]: 5887.08
- 笔宽设置为1时的运行输出:
-> Paint Event Pen Width: 1 Widget WxH: 741 x 535 --> 1: Time [ms]: 105.882 --> 2: Time [ms]: 233.165
两种测试场景下,创建QList<QPointF> pixPoints的耗时基本相近(日志标记为1:的阶段),但调用QPainter::drawPolyline()执行曲线绘制的阶段(日志标记为2:的阶段),笔宽为2时的绘制耗时远高于笔宽为1的场景。
原因说明
这个性能差距是Qt默认光栅绘制引擎的固有机制导致的,和调用哪个绘制接口无关:
- 笔宽为1时,引擎走专门优化的单像素线段快速绘制路径,只需要沿路径逐像素填色,不需要额外计算线条轮廓
- 笔宽大于1时,引擎会把每一段折线转换成对应宽度的闭合多边形,再做光栅化填充;开启抗锯齿后还要额外计算边缘的透明度混合。测试用例中用了100万个点生成折线,相当于要处理近100万个多边形的生成与填充,耗时自然会暴涨十几倍。
优化方案
按优化收益从高到低排序:
- 点抽稀(收益最高):测试中控件宽度仅700余像素,100万个点意味着平均每个X像素位置堆叠1400多个点,绝大多数点对最终显示效果没有任何贡献。按照控件实际像素宽度抽稀,每像素保留1-2个点即可,700宽度的控件最多留1500个点,绘制耗时会直接降到几毫秒级别,肉眼完全看不出和100万点绘制效果的差异。
- 静态内容缓存:正弦波属于固定不变的静态内容,不需要每次触发paintEvent都重新绘制。首次绘制时将整个曲线渲染到和控件等尺寸的
QPixmap缓存上,后续paintEvent直接将缓存的pixmap绘制到界面即可,无论线宽多少,后续刷新耗时都在毫秒级。 - 移出paintEvent中的重复计算:当前代码每次paintEvent都循环100万次计算点坐标,这部分本身就占了100ms以上耗时。将坐标计算逻辑移到初始化函数、或者
resizeEvent中,控件尺寸不变时直接复用预计算好的点列表,避免无意义的重复计算。 - 细节调优:
- 若对绘制精度要求不高,关闭
QPainter::Antialiasing抗锯齿选项,宽线绘制速度可提升3倍以上 - 将画笔端点样式设为
Qt::FlatCap、连接样式设为Qt::MiterJoin,相比圆角端点、圆角连接样式能减少不少轮廓生成的计算量 - 若需要绘制高频刷新的动态曲线,可切换到
QOpenGLWidget使用GPU绘制,线宽带来的性能差距会被GPU并行计算能力大幅缩小。
- 若对绘制精度要求不高,关闭
内容的提问来源于stack exchange,提问作者Chaitanya
相关产品推荐
相关产品推荐

