news 2026/5/14 9:50:54

全景扫描瀑布图实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
全景扫描瀑布图实现

1. 全景扫描瀑布图

1.AUIMSCANPluginSystemControl/Waterfall/waterfall.cppWaterfall

这是普通 QWidget,核心不在 OpenGL,而是QPixmap+QImage+paintEvent拼出来

Render(数据入口)

void Waterfall::Render(float* fData, int iDataCount) { if(!_fData) { return ; } memcpy(_fData, fData, iDataCount * sizeof(float)); _dataCnt = iDataCount; this->Replot(); _frame++; }

Replot:先画「底图」,再在_dataImage里叠一层「瀑布」

像素与颜色(DrawDataLayer)——按列聚合 + 纵向滚动 + 伪彩色

  1. 横轴像素列数colCount = _dataImage.width()频谱点数_dataCnt
  2. 每列对应一段 bin,取该段最大值(类似 max-hold 降采样):
double dRgbFactor = (double)_dataCnt / colCount; //颜色因子 static double dstRgb[4096]; for(int i = 0; i < colCount; ++i) { int iBeinIdx = i * dRgbFactor; int iEndIdx = i * dRgbFactor + dRgbFactor - 1; iEndIdx = std::max(iBeinIdx, iEndIdx); dstRgb[i] = *std::max_element(_fData + iBeinIdx, _fData + iEndIdx); }
  1. 时间方向滚动:把整个_dataImage的像素向下挪一行memmove),再在第一行写新颜色:
uchar* pDataSrc = _dataImage.bits(); // pointer first pixel data memmove(pDataSrc + _dataImage.bytesPerLine(), pDataSrc, (rowCount - 1) * _dataImage.bytesPerLine()); _gradient->colorize(dstRgb, mDataRange, (QRgb*)pDataSrc, colCount);
  1. colorize如何把「电平 → 颜色」**(线性映射到调色板下标):
    posToIndexFactor = (mLevelCount-1) / range.size(),对每个i
\[ \text{index} = \text{clamp}\bigl(\lfloor (\text{data}_i - \text{range.lower}) \cdot \text{posToIndexFactor} \rfloor\bigr) \] 然后 `scanLine[i] = mColorBuffer[index]`:

mDataRange用的是SetYRange设的_amplitudeBegin/_amplitudeEnd(例如 -20~120)。渐变预设为QCPColorGradient::gpSpectrum(构造函数里)。

paintEvent:多图层混合显示

void Waterfall::paintEvent(QPaintEvent* event) { Q_UNUSED(event) QPainter painter(this); _mutex.lock(); painter.drawPixmap(0, 0, _backgroundPixmap); painter.drawImage(QRect(_margin.left() + _dataImage.width() + 10, _margin.top(), _colorRangeImage.width(), _colorRangeImage.height()), _colorRangeImage); if(0 == _dataCnt) { _dataImage.fill(_backgroundColor); } painter.drawImage(QRect(_margin.left(), _margin.top(), _dataImage.width(), _dataImage.height()), _dataImage);

顺序:全图背景QPixmap右侧色标条_colorRangeImageDrawBackgroundLayer里按纵向扫渐变填的)→瀑布主体_dataImage→ 再在没有锁住的 painter上画游标、时间文字等(DrawCenterLineDrawTime…)。

这就是一种典型的混合绘制离屏缓冲(QImage)里用 CPU 写像素paintEvent里用QPainter叠图和矢量 UI


1.B 扫描/单频里更常见的单频测向里waterfallCtrl用的是这套

这里没有单独的paintEvent写瀑布像素,而是QGraphicsView+WaterfallItem::drawCurve

数据线程WaterfallDataManager::run:把频谱重采样到「屏幕宽度」列(点多则按列max,点少则线性插值),再drawCurve()WaterfallItem::handleData

颜色(WaterfallItem::handleData

  • 同样memmove整幅图向下滚一行
  • 对每个水平位置id,电平data[id]映射到100 档颜色COLOR_COUNT):
int colorId = 0; for(int id = 0; id < m_iPointCount; ++id) { if(data[id] < m_fYStart) { continue; } colorId = COLOR_COUNT * (data[id] - m_fYStart) / (m_fYEnd - m_fYStart); if(colorId < 0) { colorId = 0; } else if(colorId >= COLOR_COUNT) { colorId = COLOR_COUNT - 1; } pPixColor[id] = (*m_pMapColor)[colorId]; }

m_pMapColorWaterfallView构造时由分段线性 RGB(蓝→绿→黄→红)生成:

QRgb WaterfallView::getColor(float fFactory) { int iR, iG, iB; for(int i = 0; i < s_lstColor.size() - 1; ++i) { if(fFactory >= s_lstColor[i].fPos && fFactory <= s_lstColor[i + 1].fPos) { fFactory = (fFactory - s_lstColor[i].fPos) / (s_lstColor[i + 1].fPos - s_lstColor[i].fPos); iR = s_lstColor[i].color.red() + (s_lstColor[i + 1].color.red() - s_lstColor[i].color.red()) * fFactory; // ...

显示drawCurvepainter->drawImage(rect(), m_image)——仍是 Scene 的 item 绘制路径,和 View 的drawForeground等可再叠鼠标十字线。

「混合」子线程算每帧_maxData并调handleDataQImage主线程updateUi()QGraphicsView刷新Scene(典型生产者/消费者 + 离屏位图)。


2. Level Flow(LevelStream

控件在 UI 里是levelstreamCtrl,数据链路例如:Render(level)SignalHandleDataSlotRender

void LevelStream::Render(float level) { emit SignalHandleData(level); }

SlotRender:不是直接画,而是更新时间序列 + 屏幕上的折线点

  • 横轴:每个新点xleftMargin + 样本序号,满一行后整列左移(所有点x -= 1,删掉队列头),新点接在右端。
  • 纵轴GetLevelYScale(level)把 dBuV/dBm 映射到像素行。

Y 像素(线性电压刻度)

int LevelStream::GetLevelYScale(float level) { int maxY = _maxY; int minY = _minY; int gridHeight = this->height() - _ctrlMargin.top() - _ctrlMargin.bottom() + 1; float ampStep = (maxY - minY) / (gridHeight * 1.0); int scaleY = (maxY - level) / ampStep + _ctrlMargin.top(); return scaleY; }

即:电平高 →maxY - level小 → y 靠上(与常见示波图一致)。

DrawLevel(在工作线程里画到_drawLayer这张QImage

  • 默认Curve:对_useLevelStreamPointsgenerateSmoothCurve(样条),描一条青色_levelEnvelop曲线;
  • 可选火柴棒Match梯度填充Cube实填SolidFill
  • _useSmoothing时再画一条平滑曲线_smoothPen)。

颜色不是按电平映射伪彩色,而是固定笔颜色 + 可选QLinearGradient填充(Cube 模式里蓝→绿→黄→红)。

paintEvent:主线程只叠图 + 交互层

void LevelStream::paintEvent(QPaintEvent* evt) { QWidget::paintEvent(evt); QPainter painter(this); painter.setRenderHint(QPainter::TextAntialiasing); painter.setBrush(QBrush(QColor(Qt::black))); painter.drawRect(this->rect()); painter.drawPixmap(0, 0, QPixmap::fromImage(_bottomLayer)); painter.drawPixmap(0, 0, QPixmap::fromImage(_drawLayer)); DrawTimeMarkers(painter); if(_isMouseHover) { DrawMouseMove(painter); } DrawClickLine(&painter); }

工作线程DataThread::run:约每 100ms在持锁情况下对当前_drawLayerDrawLayer(内部DrawLevel等),再emit SignalCompleteDraw回主线程换缓存图:

if(_isDrawLayer && (_handleWidget != nullptr)) { QMutexLocker locker(&DataMutex); _layerMutex.lock(); QImage levelLayer = _drawLayer; _handleWidget->DrawLayer(levelLayer); _layerMutex.unlock(); emit SignalCompleteDraw(levelLayer); } msleep(100);

混合绘制在这里也很明确:

  • 主线程:收电平、维护_levelStream/ 点坐标;paintEvent黑底 + 两张大位图 + 游标/时间/点击线
  • 子线程:把曲线、网格、图例烤到_drawLayer(以及SlotDrawLayer里还会重画 bottom/grid/legend,见DataThread::SlotDrawLayer)。

3. 总结

问题答案要点
像素怎么来瀑布:memmove纵移 +首行写新颜色(每列一个标量);Level Flow:维护(x,y) 点列y = GetLevelYScale(电平)
颜色怎么算WaterfallQCPColorGradient::colorize,按Y 轴量程线性映射到调色板WaterfallcolorId = 100 * (dB - ymin)/(ymax-ymin)查 RGB 表。Level Flow:画笔/渐变,不是按像素读 palette。
Render vs paintEventRender/handleData喂数据 + 改离屏图paintEvent把已有位图和矢量 UI 合成到屏幕
「混动/混合」统一特征是:CPU 在QImage(或 Item)里改像素+QPainter/Graphics 框架负责合成;不少路径里再加独立线程降低 UI 阻塞(WaterfallDataManagerLevelStream::DataThread)。

**LevelStream(曲线层):电平在主线程的 SlotRender 里进队列、改点坐标;绘制主要靠 DataThread 约每 100ms 调一次 DrawLayer,把曲线画进 _drawLayer,再 emit 回主线程 SlotGetLayer → update()。是 「数据高频进、位图低频重画」。
瀑布图:SystemControl::Waterfall:Render → Replot 每次来数据都会 DrawDataLayer(memmove + colorize),再 PaintUiSignal → update(),刷新与数据帧基本同频。

WaterfallView:数据在 子线程里算每行像素、handleData 里对 QImage 做 memmove + 填色,UI 用 定时器约 50ms 汇总 updateUi() 刷新视图。
相同点:都可能在 离屏 QImage/位图 上改像素,最后用 update/paint 显示。
不同点:LevelStream 是 1D 时域折线 + 固定周期合成;瀑布图是 2D 频谱热力滚屏,且 UIMSCAN 的 Waterfall 与 Waterfall 的线程/节流策略也和 LevelStream 不一致。**

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/14 9:49:19

3分钟掌握音乐文件解密:NCM加密格式终极转换指南

3分钟掌握音乐文件解密&#xff1a;NCM加密格式终极转换指南 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 你是否曾在网易云音乐下载了心爱的歌曲&#xff0c;却发现只能在官方App中播放&#xff1f;那些神秘的NCM格式文件就像被锁…

作者头像 李华
网站建设 2026/5/14 9:46:05

图计算加速器内存瓶颈与Piccolo架构创新

1. 图计算加速器的内存瓶颈本质现代图计算应用面临的核心矛盾在于&#xff1a;图数据天然的稀疏性与传统DRAM架构的访问特性之间存在根本性不匹配。这种不匹配主要体现在三个维度&#xff1a;访问粒度差异&#xff1a;典型图算法&#xff08;如BFS、PageRank&#xff09;每次操…

作者头像 李华
网站建设 2026/5/14 9:42:04

DoubleQoL:重新定义《工业队长》的游戏体验优化

DoubleQoL&#xff1a;重新定义《工业队长》的游戏体验优化 【免费下载链接】DoubleQoLMod-zh 项目地址: https://gitcode.com/gh_mirrors/do/DoubleQoLMod-zh 在策略模拟类游戏中&#xff0c;时间管理与操作效率往往是制约玩家体验的关键瓶颈。《工业队长》作为一款深…

作者头像 李华
网站建设 2026/5/14 9:41:06

做减法的外语进阶:停掉繁杂打卡,专注沉浸式文本输入

曾经有一段时间&#xff0c;我在外语提升上陷入了“伪勤奋”的怪圈。手机里塞满了各种学习类APP&#xff0c;通勤时刷词汇、午休时听电台、睡前还在死磕语法点。表面上看每天的日程排得很满&#xff0c;可一旦要在工作中查阅全英文的行业报告&#xff0c;或是去海外论坛翻找资料…

作者头像 李华
网站建设 2026/5/14 9:36:05

知识库接入还能这么玩?Tablestore 四种方式实战揭秘

本文接《你的企业知识库&#xff0c;何必自己折腾&#xff1f;Tablestore 知识库服务帮你一站式搞定》。在了解知识库服务的背景、核心能力与典型应用场景的基础上&#xff0c;以下内容将详细介绍 API 接口设计、多种接入方式的操作步骤&#xff0c;以及基于真实数据集的评测结…

作者头像 李华
网站建设 2026/5/14 9:35:10

魔兽争霸3游戏优化终极指南:3步解决帧率限制与界面显示问题

魔兽争霸3游戏优化终极指南&#xff1a;3步解决帧率限制与界面显示问题 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 还在为魔兽争霸3的卡顿画面和界…

作者头像 李华