一种高效绘制余晖波形的方法Qt/C++

一、什么是"余晖波形"?

"余晖波形"(afterglow / persistence waveform)是指在实时波形显示中保留上一帧或多帧的轨迹并使其逐渐衰退的视觉效果。类似示波器上"痕迹逐渐消失"的效果:

  • 能看到最新信号的同时,也能看见短期历史(轨迹),比单纯把历史点清掉再画更直观。

  • 常见于音频可视化、示波器、频谱可视化、监控仪表盘等场景。

实现上通常有两种思路:把历史数据一直保留并每帧重绘(昂贵),或者在画布上"叠加透明层"使旧像素逐帧衰减(高效且视觉平滑)。

二、QCustomPlot 的性能瓶颈

在一个包含 4 个通道、每个通道 5000 点(总计 20,000 点)的场景中,如果每帧都把数据传给 QCustomPlot 并调用完整重绘,会遇到以下问题:

  1. 整体重绘开销大:每次刷新都触发全窗口的完整绘制,哪怕只有部分数据更新,也会重新计算和渲染全部曲线。

  2. 频繁的内存分配和拷贝:每帧都要构造并传递大量点数据,增加了内存管理开销。

  3. 高层抽象带来的额外消耗:QCustomPlot 提供了丰富的数据和绘图管理功能,但这些便利在高频实时刷新时会成为性能负担。

结果就是 CPU 占用率高、界面刷新不流畅、容易出现掉帧。而且对于"余晖"这种需要保留历史痕迹的效果,每次都重绘所有历史数据本身就是多余的。

三、使用离屏缓冲 + alpha 淡化 + 增量绘制实现

3.1 本方法的主要优点

1)性能高效:避免每帧重建和绘制全部点数据,显著降低 CPU 与内存开销。

2)视觉流畅:通过淡化处理保留历史轨迹,波形变化连续自然,观察体验更接近示波器。

3)实现简洁:基于缓冲区叠加和局部更新即可实现,无需引入复杂的数据结构。

3.2 关键实现原理

1) 离屏缓冲

绘制波形时,并不是直接在控件上重绘所有历史数据,而是使用一张缓冲图像(离屏画布)来保存已经绘制过的波形。这样,每一帧只需要把缓冲图像整体"贴"到窗口上即可,开销非常小。

2) 淡化实现

为了产生"余晖"效果,每一帧在缓冲图像上覆盖一层半透明的颜色,让旧的轨迹逐渐变浅。这样既能保留短时间的历史痕迹,又不会无限叠加造成画面污染。

3) 增量更新

每个波形通道都只生成和绘制最新的数据点,而不是每次都把整个通道的所有点重新绘制一遍。这样可以大幅减少绘制调用次数,提高帧率。

3.3 关键代码展示

cpp 复制代码
void WaveformPlot::paintEvent(QPaintEvent* event) {

  QPainter p(this);
  p.setRenderHint(QPainter::Antialiasing);

  // 绘制背景
  p.fillRect(rect(), Qt::white);

  // 绘制缓冲图像中的波形
  p.drawImage(0, 0, m_buffer);

  // 绘制坐标轴
  drawAxes(p);
}

void WaveformPlot::updateWaveform() {

  // 对旧波形做淡化
  QPainter fadePainter(&m_buffer);

  // alpha 衰减
  fadePainter.fillRect(m_buffer.rect(), QColor(255, 255, 255, 30));
  fadePainter.end();

  QColor baseColors[4] = {
      QColor(0, 0, 255),   // 蓝
      QColor(255, 0, 0),   // 红
      QColor(0, 255, 0),   // 绿
      QColor(255, 128, 0)  // 橙
  };

  double newYMin = m_yMin;
  double newYMax = m_yMax;

  for (int i = 0; i < m_layers.size(); ++i) {
    auto wf = m_layers[i].waveform;

    // 生成最新波形数据
    wf->generate(m_layers[i].index, i);

    // 获取 Y 范围
    wf->getminmax(newYMin, newYMax);

    m_layers[i].index = (m_layers[i].index + 1) % m_maxCount;
  }

  // 如果数据超出原范围,就调整 Y 轴
  if (newYMin < m_yMin || newYMax > m_yMax) {
    m_yMin = newYMin;
    m_yMax = newYMax;

    // ==== 计算扩展后的 yMin/yMax ====
    double yRange = m_yMax - m_yMin;
    m_yMinExt = m_yMin - 0.2 * yRange;  // 下扩展 20%
    m_yMaxExt = m_yMax + 0.2 * yRange;  // 上扩展 20%
  }

  for (int i = 0; i < m_layers.size(); ++i) {
    auto wf = m_layers[i].waveform;
    // 绘制最新波形
    wf->draw(m_buffer, this, baseColors[i]);
  }

  // 触发 paintEvent
  update();
}

3.4 资源占用展示

绘制余晖波形过程中,资源占用情况如下图所示

四、总结

本文介绍的余晖波形绘制方法,最突出的优点如下:

1、性能高效:避免每帧重建和绘制全部点数据,显著降低 CPU 与内存开销。

2、视觉流畅:通过淡化处理保留历史轨迹,波形变化连续自然。

但是也有一些缺点,比如:

1、完全依靠painter绘制,交互性不如QCustomPlot

2、由于历史波形数据通过离屏缓冲 + alpha 淡化实现,窗口大小变化时历史波形无法自适应变化,适合窗口大小固定的场景。

相关推荐
一叶飘零_sweeeet5 小时前
从 0 到 1 攻克订单表分表分库:亿级流量下的数据库架构实战指南
java·数据库·mysql·数据库架构·分库分表
xianyinsuifeng5 小时前
Oracle 10g → Oracle 19c 升级后问题解决方案(Pro*C 项目)
c语言·数据库·oracle
深耕AI5 小时前
【MFC文档与视图结构:数据“仓库”与“橱窗”的梦幻联动 + 初始化“黑箱”大揭秘!】
c++·mfc
TDengine (老段)5 小时前
TDengine 选择函数 First 用户手册
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据
励志不掉头发的内向程序员6 小时前
STL库——二叉搜索树
开发语言·c++·学习
dreams_dream6 小时前
企业级 Django 日志配置示例
数据库·django·sqlite
tan180°6 小时前
Boost搜索引擎 查找并去重(3)
linux·c++·后端·搜索引擎
络76 小时前
Redis 非缓存核心场景及实例说明
数据库·redis·缓存
追烽少年x7 小时前
QProxyStyle类中drawControl和drawComplexControl函数的区别是什么
qt