一种高效绘制余晖波形的方法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 淡化实现,窗口大小变化时历史波形无法自适应变化,适合窗口大小固定的场景。

相关推荐
绵绵细雨中的乡音18 小时前
MySQL 常用函数实操指南:从基础到实战案例
数据库·mysql
给大佬递杯卡布奇诺18 小时前
FFmpeg 基本API av_seek_frame函数内部调用流程分析
c++·ffmpeg·音视频
uxiang_blog18 小时前
C++进阶:重载类型转换
linux·开发语言·c++
凉栀お_19 小时前
MySQL相关知识查询表中内容(第二次作业)
数据库·mysql
moringlightyn19 小时前
c++11可变模版参数 emplace接口 新的类功能 lambda 包装器
开发语言·c++·笔记·其他·c++11·lambda·包装器
郝学胜-神的一滴19 小时前
使用Linux系统函数递归遍历指定目录
linux·运维·服务器·开发语言·c++·软件工程
ss27319 小时前
手写Spring第7弹:Spring IoC容器深度解析:XML配置的完整指南
java·前端·数据库
PFinal社区_南丞20 小时前
PostgreSQL-10个鲜为人知的强大功能
数据库·后端
misty youth20 小时前
配置openguass 教程(自存)
数据库·ubuntu·华为·openguass
会开花的二叉树20 小时前
C++微服务 UserServer 设计与实现
开发语言·c++·微服务