diff --git a/Components/Application/Inc/Application.hpp b/Components/Application/Inc/Application.hpp index b556421..201eb34 100644 --- a/Components/Application/Inc/Application.hpp +++ b/Components/Application/Inc/Application.hpp @@ -241,8 +241,8 @@ namespace jdi /** * @brief 应用绘图区提交的「实验性功能 → 构建为新信号」请求。 * - * 为指定网格内的每条被勾选信号各构建一条派生信号(多次迭代均值滤波或弱峰增强), - * 命名为 `<原信号名称> (N点均值…)` / `<原信号名称> (弱峰增强 M%)`, + * 为指定网格内的每条被勾选信号各构建一条派生信号(多次迭代均值滤波或弱峰调整), + * 命名为 `<原信号名称> (N点均值…)` / `<原信号名称> (弱峰调整 M%)`, * 插入到原信号所在组的原信号位置**下方**,并把新信号所在的网格移动到原信号网格的下方。 * * @return 实际构建了新信号返回 true。 @@ -250,7 +250,7 @@ namespace jdi bool ApplyPendingDerivedBuild(); /** - * @brief 派生信号构建请求(多次迭代均值滤波 / 弱峰增强共用同一套插入流程)。 + * @brief 派生信号构建请求(多次迭代均值滤波 / 弱峰调整共用同一套插入流程)。 */ struct DerivedSignalRequest { @@ -258,12 +258,12 @@ namespace jdi int signalIndex = -1; /**< 原信号下标。 */ int points = 5; /**< 多次迭代均值滤波:均值点数 X(滑平半径)。 */ int iterations = 1; /**< 多次迭代均值滤波:迭代次数 N(至少 1)。 */ - int levelPercent = 150; /**< 弱峰增强:增强等级(0–200)。 */ - bool weakPeak = false; /**< true = 弱峰增强,false = 多次迭代均值滤波。 */ + int amountPercent = 150; /**< 弱峰调整:增强等级(0–200)。 */ + bool weakPeak = false; /**< true = 弱峰调整,false = 多次迭代均值滤波。 */ }; /** - * @brief 把一条派生信号(多次迭代均值滤波 / 弱峰增强)插入数据层并同步各视图。 + * @brief 把一条派生信号(多次迭代均值滤波 / 弱峰调整)插入数据层并同步各视图。 * @param [IN] request 构建请求。 * @param [IN] makeVisible 是否在当前标签页中勾选新信号。 * @return 构建并插入成功返回 true。 diff --git a/Components/Application/Src/Application.cpp b/Components/Application/Src/Application.cpp index 7b0f616..459194b 100644 --- a/Components/Application/Src/Application.cpp +++ b/Components/Application/Src/Application.cpp @@ -1481,14 +1481,14 @@ namespace jdi // 1. 计算派生采样值(与网格内的预览曲线共用同一实现,因此逐位一致)。 // 多次迭代均值滤波形如「<原信号名称> (N点均值 M次迭代)」, - // 弱峰增强形如「<原信号名称> (弱峰增强 M% N次迭代)」。 + // 弱峰调整形如「<原信号名称> (弱峰调整 M% N次迭代)」。 std::vector derived_values; SignalData derived; if (request.weakPeak) { - ComputeWeakPeakBoost(source.values, static_cast(request.levelPercent) / 100.0, + ComputeWeakPeakAdjust(source.values, static_cast(request.amountPercent) / 100.0, request.iterations, derived_values); - derived.name = baseName + L" (弱峰增强 " + std::to_wstring(request.levelPercent) + L"% " + + derived.name = baseName + L" (弱峰调整 " + std::to_wstring(request.amountPercent) + L"% " + std::to_wstring(request.iterations) + L"次迭代)"; } else @@ -1538,7 +1538,7 @@ namespace jdi bool Application::ApplyPendingDerivedBuild() { - // 两类「构建为新信号」请求(均值滤波 / 弱峰增强)机制完全一致, + // 两类「构建为新信号」请求(均值滤波 / 弱峰调整)机制完全一致, // 统一整理成同一份构建清单后按下标降序处理。 std::vector requests; { @@ -1568,7 +1568,7 @@ namespace jdi DerivedSignalRequest request; request.fileIndex = item.fileIndex; request.signalIndex = item.signalIndex; - request.levelPercent = item.levelPercent; + request.amountPercent = item.amountPercent; request.iterations = item.iterations; request.weakPeak = true; requests.push_back(request); @@ -1612,7 +1612,7 @@ namespace jdi std::wstring text = L"已构建 " + std::to_wstring(built) + L" 条 "; if (last.weakPeak) { - text += L"弱峰增强 " + std::to_wstring(last.levelPercent) + L"% " + + text += L"弱峰调整 " + std::to_wstring(last.amountPercent) + L"% " + std::to_wstring(last.iterations) + L"次迭代信号。"; } else @@ -1754,7 +1754,7 @@ namespace jdi experimental.submenu.push_back(meanFilter); MenuItem weakPeak; - weakPeak.label = L"弱峰增强"; + weakPeak.label = L"弱峰调整"; weakPeak.id = PLOT_CMD_LANE_WEAK_PEAK; weakPeak.checked = m_plot.LaneWeakPeak(laneIndex); experimental.submenu.push_back(weakPeak); @@ -1873,7 +1873,7 @@ namespace jdi m_plot.ToggleLaneMeanFilter(laneIndex); break; case PLOT_CMD_LANE_WEAK_PEAK: - // 「实验性功能 → 弱峰增强」:切换弱峰增强曲线与参数面板。 + // 「实验性功能 → 弱峰调整」:切换弱峰调整曲线与参数面板。 m_plot.ToggleLaneWeakPeak(laneIndex); break; case PLOT_CMD_LANE_SPLIT: @@ -2378,7 +2378,7 @@ namespace jdi { // 绘图区内的点击也可能提交图例改名,需要立刻落到数据层。 ApplyPendingRename(); - // 「构建为新信号」按钮(均值滤波 / 弱峰增强):立即构建新信号并同步各视图。 + // 「构建为新信号」按钮(均值滤波 / 弱峰调整):立即构建新信号并同步各视图。 ApplyPendingDerivedBuild(); InvalidateRect(hwnd, nullptr, FALSE); } diff --git a/Components/PlotView/Inc/PlotView.hpp b/Components/PlotView/Inc/PlotView.hpp index 1916c22..415fa9d 100644 --- a/Components/PlotView/Inc/PlotView.hpp +++ b/Components/PlotView/Inc/PlotView.hpp @@ -43,8 +43,8 @@ namespace jdi MOVE_SIGNAL, /**< 拖拽信号曲线到其它网格。 */ EDGE_MOVE, /**< 拖动「边沿计数」的横向光标(上下移动幅值)。 */ EDGE_PANEL_MOVE, /**< 拖动边沿计数浮动面板。 */ - PEAK_LEVEL, /**< 拖动「弱峰增强」某信号的等级滑块(0–200%)。 */ - PEAK_ITERATIONS, /**< 拖动「弱峰增强」某信号的迭代次数滑块(1 … 该信号自定义上限)。 */ + PEAK_LEVEL, /**< 拖动「弱峰调整」某信号的等级滑块(0–200%)。 */ + PEAK_ITERATIONS, /**< 拖动「弱峰调整」某信号的迭代次数滑块(1 … 该信号自定义上限)。 */ LANE_REORDER /**< 拖动网格图例中的 GX 编号徽标:上下调整网格次序。 */ }; @@ -73,7 +73,7 @@ namespace jdi EDGE_CENTER_ITERATIONS, /**< 编辑「中心轴边沿计数」的迭代次数 N。 */ MEAN_FILTER_POINTS, /**< 编辑「均值滤波」的均值点数 X。 */ MEAN_FILTER_ITERATIONS, /**< 编辑「均值滤波」的迭代次数 N。 */ - PEAK_ITERATIONS_MAX /**< 编辑「弱峰增强」某信号「迭代次数 N」滑块的**上限**(正整数)。 */ + PEAK_ITERATIONS_MAX /**< 编辑「弱峰调整」某信号「迭代次数 N」滑块的**上限**(正整数)。 */ }; /** @@ -91,7 +91,7 @@ namespace jdi PLOT_CMD_LANE_EDGE_CENTER = 8, /**< 切换本网格的「实验性功能 → 边沿计数(中心轴)」开关。 */ PLOT_CMD_LANE_SPLIT = 9, /**< 拆分当前网格:把网格内信号按个拆成独立网格并顺次下移编号。 */ PLOT_CMD_LANE_MEAN_FILTER = 10, /**< 切换本网格的「实验性功能 → 均值滤波」开关。 */ - PLOT_CMD_LANE_WEAK_PEAK = 11 /**< 切换本网格的「实验性功能 → 弱峰增强」开关。 */ + PLOT_CMD_LANE_WEAK_PEAK = 11 /**< 切换本网格的「实验性功能 → 弱峰调整」开关。 */ }; /** @@ -102,7 +102,7 @@ namespace jdi COUNT = 0, /**< 边沿计数。 */ CENTER = 1, /**< 边沿计数(中心轴)。 */ MEAN = 2, /**< 均值滤波。 */ - PEAK = 3 /**< 弱峰增强。 */ + PEAK = 3 /**< 弱峰调整。 */ }; /** @@ -113,7 +113,7 @@ namespace jdi enum class DerivedCurveKind : int { MEAN_FILTER = 0, /**< 多次迭代均值滤波。 */ - WEAK_PEAK = 1 /**< 弱峰增强。 */ + WEAK_PEAK = 1 /**< 弱峰调整。 */ }; /** @@ -570,19 +570,19 @@ namespace jdi bool TakePendingMeanFilterBuild(int& laneIndex, std::vector& items); /** - * @brief 「实验性功能 → 弱峰增强」的「构建为新信号」单个请求 + * @brief 「实验性功能 → 弱峰调整」的「构建为新信号」单个请求 * (一个被勾选的信号 + **它自己的**增强等级与迭代次数,与均值滤波同构)。 */ struct WeakPeakBuild { int fileIndex = -1; /**< 信号所属文件下标。 */ int signalIndex = -1; /**< 文件内信号下标。 */ - int levelPercent = 150; /**< 弱峰增强等级(0–200)。 */ + int amountPercent = 150; /**< 弱峰调整等级(0–200)。 */ int iterations = 1; /**< 迭代次数 N(1–10)。 */ }; /** - * @brief 取出「弱峰增强 → 构建为新信号」请求(由面板底部按钮触发)。 + * @brief 取出「弱峰调整 → 构建为新信号」请求(由面板底部按钮触发)。 * @param [OUT] laneIndex 触发该请求的网格下标。 * @param [OUT] items 需要构建的信号及其参数(按信号在网格内的顺序)。 * @return 存在待处理请求返回 true(取出后清空)。 @@ -590,7 +590,7 @@ namespace jdi bool TakePendingWeakPeakBuild(int& laneIndex, std::vector& items); /** - * @brief 切换某网格的「弱峰增强」开关。 + * @brief 切换某网格的「弱峰调整」开关。 * @param [IN] laneIndex 网格下标。 * @param [IN] enabled 是否开启。 * @return 状态发生变化返回 true。 @@ -598,44 +598,44 @@ namespace jdi bool SetLaneWeakPeak(int laneIndex, bool enabled); /** - * @brief 查询某网格的「弱峰增强」开关。 + * @brief 查询某网格的「弱峰调整」开关。 * @param [IN] laneIndex 网格下标。 * @return 已开启返回 true。 */ bool LaneWeakPeak(int laneIndex) const; /** - * @brief 切换某网格的「弱峰增强」开关。 + * @brief 切换某网格的「弱峰调整」开关。 * @param [IN] laneIndex 网格下标。 * @return 切换后的状态。 */ bool ToggleLaneWeakPeak(int laneIndex); /** - * @brief 获取弱峰增强曲线(按「信号 + 增强等级 + 迭代次数」缓存)。 + * @brief 获取弱峰调整曲线(按「信号 + 增强等级 + 迭代次数」缓存)。 * @param [IN] signal 目标信号。 - * @param [IN] levelPercent 弱峰增强等级(0–200,整数百分比)。 + * @param [IN] amountPercent 弱峰调整等级(0–200,整数百分比)。 * @param [IN] iterations 迭代次数 N(1–10)。 * @return 曲线数值数组(长度与 `signal.values` 相同;缺失处为 NaN)。 */ - const std::vector& WeakPeakCurveFor(const SignalData& signal, int levelPercent, + const std::vector& WeakPeakCurveFor(const SignalData& signal, int amountPercent, int iterations) const; /** - * @brief 取某个时刻的弱峰增强曲线取值(用于纵向光标的取值标注)。 + * @brief 取某个时刻的弱峰调整曲线取值(用于纵向光标的取值标注)。 * @param [IN] file 信号所属文件。 * @param [IN] signal 目标信号。 - * @param [IN] levelPercent 弱峰增强等级(0–200)。 + * @param [IN] amountPercent 弱峰调整等级(0–200)。 * @param [IN] iterations 迭代次数 N(1–10)。 * @param [IN] time 采样时刻。 * @param [OUT] value 输出取值。 * @return 取到有效值返回 true。 */ - bool WeakPeakValueAt(const DataFile& file, const SignalData& signal, int levelPercent, int iterations, + bool WeakPeakValueAt(const DataFile& file, const SignalData& signal, int amountPercent, int iterations, double time, double& value) const; /** - * @brief 切换某个信号在「弱峰增强」面板中的预览勾选状态。 + * @brief 切换某个信号在「弱峰调整」面板中的预览勾选状态。 * @param [IN] laneIndex 网格下标。 * @param [IN] ref 目标信号。 * @return 切换后的勾选状态。 @@ -643,7 +643,7 @@ namespace jdi bool ToggleLaneWeakPeakSignal(int laneIndex, const SignalRef& ref); /** - * @brief 查询某个信号在「弱峰增强」面板中的预览勾选状态。 + * @brief 查询某个信号在「弱峰调整」面板中的预览勾选状态。 * @param [IN] laneIndex 网格下标。 * @param [IN] ref 目标信号。 * @return 已勾选返回 true。 @@ -1072,7 +1072,7 @@ namespace jdi const std::vector& timeTicks); /** - * @brief 绘制某网格的全部**派生预览曲线**(均值滤波 / 中心轴 / 弱峰增强),叠加在原信号之上。 + * @brief 绘制某网格的全部**派生预览曲线**(均值滤波 / 中心轴 / 弱峰调整),叠加在原信号之上。 * * 从 `PaintLane` 抽出,供「宽缓冲区」前台路径复用:宽缓冲只存"基底"(网格/波形), * 派生曲线每帧作为叠加层单独绘制,因此拖派生参数滑块时无需重画波形。 @@ -1155,7 +1155,7 @@ namespace jdi std::vector rowIterations; std::vector rowStats; /** - * @brief 「弱峰增强」每个被勾选信号自己的两行**滑块**(与 `rowRefs` 同序)。 + * @brief 「弱峰调整」每个被勾选信号自己的两行**滑块**(与 `rowRefs` 同序)。 * * `rowLevelTracks/rowLevelValues` = 增强等级滑块与它右侧的数值文本; * `rowIterTracks/rowIterValues` = 迭代次数滑块与它右侧的数值文本 @@ -1295,20 +1295,20 @@ namespace jdi * @brief 取得某个采样区间内的派生曲线(带缓存)。 * * ⚠️ **性能关键**:派生曲线只用于“看得见的那一段”,而整条信号可能有数千万个采样点 - * (VERY_BIG_DATA 为 2576 万)。若每次都重算整条曲线(实测弱峰增强 ~800ms/条), + * (VERY_BIG_DATA 为 2576 万)。若每次都重算整条曲线(实测弱峰调整 ~800ms/条), * 拖动等级/迭代次数滑块时每帧要花几秒钟,交互完全卡死。 * 本函数只计算“所需区间 ± 保护带”,并按(信号 + 参数 + 对齐后的区间)缓存: * 实测单条降到 1~3ms,滑块拖动与平移因此恢复流畅。 * * 保护带保证可见区间内的取值与“整条曲线”一致:均值滤波的核是有限宽度, - * 弱峰增强的修正沿相邻极值向内传播(每迭代一次传一个极值), + * 弱峰调整的修正沿相邻极值向内传播(每迭代一次传一个极值), * 因此只要求保护带足够宽(见 `kDerivedCurveGuard` 系列常量)。 * * @param [IN] kind 曲线种类。 * @param [IN] signal 目标信号。 * @param [IN] points 均值点数 X(仅 `MEAN_FILTER` 使用)。 * @param [IN] iterations 迭代次数 N。 - * @param [IN] levelPercent 弱峰增强等级(仅 `WEAK_PEAK` 使用,0–200)。 + * @param [IN] amountPercent 弱峰调整等级(仅 `WEAK_PEAK` 使用,0–200)。 * @param [IN] begin 需要的采样区间起点(含)。 * @param [IN] end 需要的采样区间终点(不含)。 * @param [OUT] spanBegin 返回数组对应的首个采样下标。 @@ -1319,7 +1319,7 @@ namespace jdi * @return 曲线数组指针(覆盖 `[spanBegin, spanBegin + size())`);暂无可显示曲线时返回 nullptr。 */ const std::vector* CurveSpanFor(DerivedCurveKind kind, const SignalData& signal, - int points, int iterations, int levelPercent, + int points, int iterations, int amountPercent, std::size_t begin, std::size_t end, std::size_t& spanBegin, bool allowAsyncCompute = false) const; @@ -1337,12 +1337,12 @@ namespace jdi int kind = 0; /**< `DerivedCurveKind`。 */ int points = 5; /**< 均值点数 X。 */ int iterations = 1; /**< 迭代次数 N。 */ - int levelPercent = 0; /**< 弱峰增强等级(0–200)。 */ + int amountPercent = 0; /**< 弱峰调整等级(0–200)。 */ bool operator==(const CurveKey& other) const { return signal == other.signal && kind == other.kind && points == other.points && - iterations == other.iterations && levelPercent == other.levelPercent; + iterations == other.iterations && amountPercent == other.amountPercent; } }; @@ -1470,15 +1470,15 @@ namespace jdi int points, int iterations) const; /** - * @brief 绘制某条信号的弱峰增强曲线(与均值滤波曲线同一配色派生规则)。 + * @brief 绘制某条信号的弱峰调整曲线(与均值滤波曲线同一配色派生规则)。 * @param [IN,OUT] g 绘图上下文。 * @param [IN] resolved 信号视图。 * @param [IN] lane 网格绘制信息。 - * @param [IN] levelPercent 弱峰增强等级(0–200)。 + * @param [IN] amountPercent 弱峰调整等级(0–200)。 * @param [IN] iterations 迭代次数 N(1–10)。 */ void PaintWeakPeakCurve(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane, - int levelPercent, int iterations) const; + int amountPercent, int iterations) const; /** * @brief 数据点标记的位置与"越界方向"(用于决定画圆点还是贴边三角)。 @@ -1694,9 +1694,9 @@ namespace jdi bool m_pendingRename = false; /**< 是否存在待应用的重命名请求。 */ int m_pendingMeanFilterLane = -1; /**< 待构建的均值滤波信号所属网格下标(-1 表示无请求)。 */ - int m_pendingWeakPeakLane = -1; /**< 待构建的弱峰增强信号所属网格下标(-1 表示无请求)。 */ + int m_pendingWeakPeakLane = -1; /**< 待构建的弱峰调整信号所属网格下标(-1 表示无请求)。 */ std::vector m_pendingMeanFilterItems; /**< 待构建的信号及其参数。 */ - std::vector m_pendingWeakPeakItems; /**< 待构建的弱峰增强信号及其参数。 */ + std::vector m_pendingWeakPeakItems; /**< 待构建的弱峰调整信号及其参数。 */ bool m_mouseOverPanel = false; /**< 上一帧鼠标是否落在某个实验性功能面板上。 */ int m_peakDragLane = -1; /**< 正在拖动弱峰滑块的面板所属网格。 */ SignalRef m_peakDragRef; /**< 正在拖动弱峰滑块的**信号**(每个信号一套参数)。 */ @@ -1769,18 +1769,18 @@ namespace jdi }; mutable std::deque m_meanCurveCache; - /** 弱峰增强曲线缓存项(按「信号指针 + 增强等级百分比 + 迭代次数」标识)。 */ + /** 弱峰调整曲线缓存项(按「信号指针 + 增强等级百分比 + 迭代次数」标识)。 */ struct WeakPeakCurveEntry { const SignalData* signal = nullptr; - int levelPercent = 150; /**< 弱峰增强等级(0–200)。 */ + int amountPercent = 150; /**< 弱峰调整等级(0–200)。 */ int iterations = 1; /**< 迭代次数 N(1–10)。 */ std::vector values; /**< 与 signal->values 等长;缺失处为 NaN。 */ }; mutable std::deque m_weakPeakCurveCache; // ---- 异步派生曲线计算(后台线程 + 双缓冲)---- - // 设计:预览曲线(均值滤波 / 弱峰增强)在窗口很大时**不在绘制线程里计算**,而是交给后台 + // 设计:预览曲线(均值滤波 / 弱峰调整)在窗口很大时**不在绘制线程里计算**,而是交给后台 // 线程;绘制线程本帧先用上一条已算好的曲线顶替(双缓冲),后台算完后通过 // `kMessagePlotDerivedReady` 通知宿主重绘,从而切换为最新曲线。这样滑块 / 光标 / 视界 / // 缩放等交互的反馈始终跟手,不会被计算拖住,同时把重计算放到后台充分利用多核。 @@ -1979,7 +1979,7 @@ namespace jdi /** * @brief 装饰层(图例 + Y 轴刻度)的指纹:含外观样式,但**不含派生曲线参数**。 * - * 拖弱峰增强等级/迭代次数滑块时,图例与 Y 轴刻度不变,仅网格内预览曲线变化; + * 拖弱峰调整等级/迭代次数滑块时,图例与 Y 轴刻度不变,仅网格内预览曲线变化; * 因此装饰层缓存不应因参数变化而失效(否则拖滑块每帧都要重绘图例与刻度)。 * * @return 指纹值。 @@ -2009,14 +2009,14 @@ namespace jdi * * 它**只影响网格内容的绘制**(不影响网格结构/视图几何),因此不进入 * `SceneGeometryHash`,而是与外观样式一起作为“局部重画”的判据: - * 拖动弱峰增强的等级/迭代次数滑块时,只需重画受影响的网格, + * 拖动弱峰调整的等级/迭代次数滑块时,只需重画受影响的网格, * 不必整幅重绘(也不会把静态场景缓存整体作废)。 */ struct SceneDerivedEntry { int edgeCenter = 0; /**< 中心轴边沿计数开关。 */ int meanFilter = 0; /**< 均值滤波开关。 */ - int weakPeak = 0; /**< 弱峰增强开关。 */ + int weakPeak = 0; /**< 弱峰调整开关。 */ /** 各条目展开为 [文件, 信号, 参数…],按原顺序(稳定)。 */ std::vector edgeCenterItems; std::vector meanFilterItems; @@ -2313,14 +2313,16 @@ namespace jdi * @param [IN] laneIndex 网格下标。 * @param [IN,OUT] layer 目标图层(会被写入尺寸 / 时间映射 / 指纹)。 * @param [IN] stripX 只渲染位图的这一段 x 区间(滚动时补画细条用)。 - * @param [IN] stripW 该区间的宽度;传 0 表示渲染整块。 + * @param [IN] stripW 该区间的宽度;传 0 表示横向整块。 + * @param [IN] stripY 只渲染位图的这一段 y 区间(纵向滚动补条用)。 + * @param [IN] stripH 该区间的高度;传 0 表示纵向整块。 * @param [IN] wideMin 位图 `x=0` 对应的时间。 * **必须与调用方的意图一致**:整块重画传 `LayerDefaultWideMin()`(以当前视界居中); * 滚动补细条则传 `layer.wideMin`(与该图层已有像素同一映射,否则会留下接缝)。 * @return 渲染成功返回 true。 */ - bool RenderLaneLayerRegion(int laneIndex, LaneLayer& layer, int stripX, int stripW, - double wideMin); + bool RenderLaneLayerRegion(int laneIndex, LaneLayer& layer, int stripX, int stripW, int stripY, + int stripH, double wideMin); /** * @brief 前台补画各网格的**分界线**(网格顶部那条较重的横线)。 * diff --git a/Components/PlotView/Src/PlotView.cpp b/Components/PlotView/Src/PlotView.cpp index e1aefd1..080fce4 100644 --- a/Components/PlotView/Src/PlotView.cpp +++ b/Components/PlotView/Src/PlotView.cpp @@ -25,6 +25,14 @@ namespace jdi const size_t kMaxPolylinePoints = 20000; /**< 单条折线最大顶点数(上限保护)。 */ const size_t kPolylinePointsPerPixel = 2; /**< 每个像素列允许的折线顶点数(上/下包络两顶点)。 */ const size_t kMinPolylinePoints = 512; /**< 折线最少顶点数(窗口很窄时仍保留细节)。 */ + /** + * @brief 图层**纵向滚动**的量化阈值(位图像素)。 + * + * 幅值平移不足该值时**不滚动**图层,交给贴图时的纵向偏移(`srcY`)处理; + * 超过才滚动一次并补画新暴露的一整条。这样画布映射在拖动过程中保持稳定 → + * 折线缓存(键含幅值映射)能命中,避免"每帧滚 1 行 → 每帧重算折线"的性能陷阱。 + */ + const int kLayerRollQuantum = 16; const float kEnvelopeMinPixelSpan = 0.5f; /**< 包络纵向跨度小于该像素值时合并为单个顶点。 */ const float kCursorHandleReserve = 9.0f; /**< 光标顶部三角把手占用的高度(数据点贴边时需避让)。 */ const float kMarkerEdgeInset = 7.0f; /**< 数据点越界贴边时距网格可见边界的间距。 */ @@ -68,18 +76,20 @@ namespace jdi const float kEdgePanelMeanEditHeight = 24.0f; /**< 逐条目面板中每条信号的参数行高度。 */ const float kEdgePanelMeanRowGap = 2.0f; /**< 名称行与参数行之间的间距。 */ const float kEdgePanelMeanCheckSize = 15.0f; /**< 逐条目面板中条目复选框尺寸。 */ - const float kWeakPeakSliderRowHeight = 26.0f; /**< 弱峰增强面板中每个滑块行的高度。 */ - const float kWeakPeakTrackHeight = 6.0f; /**< 弱峰增强滑块的轨道高度。 */ - const float kWeakPeakTrackMinWidth = 120.0f; /**< 弱峰增强滑块轨道的最小宽度。 */ + const float kWeakPeakSliderRowHeight = 26.0f; /**< 弱峰调整面板中每个滑块行的高度。 */ + const float kWeakPeakTrackHeight = 6.0f; /**< 弱峰调整滑块的轨道高度。 */ + const float kWeakPeakTrackMinWidth = 120.0f; /**< 弱峰调整滑块轨道的最小宽度。 */ const int kWeakPeakIterationsMin = 1; /**< 迭代次数 N 的下限。 */ /** 迭代次数滑块「自定义上限」的允许范围(与算法内部的阶数夹取保持一致)。 */ const int kWeakPeakIterationsMaxLimit = 1000; /** 迭代次数滑块上限的默认值(与 `Lane::WeakPeakItem::maxIterations` 一致)。 */ const int kWeakPeakIterationsDefaultMax = 10; - /** 弱峰增强等级上限(面板上显示为 200%)。 */ - const double kWeakPeakLevelMax = 2.0; - /** 弱峰增强等级百分比上限(用于构建新信号 / 曲线缓存键)。 */ - const int kWeakPeakPercentMax = 200; + /** 弱峰调整的幅度上限/下限(面板上显示为 +200% / -200%)。 */ + const double kWeakPeakAmountScale = 2.0; + /** 弱峰调整幅度百分比上限(用于构建新信号 / 曲线缓存键)。 */ + const int kWeakPeakAmountMax = 200; + /** 弱峰调整幅度百分比下限(负值 = 削弱)。 */ + const int kWeakPeakAmountMin = -200; /** * @brief 派生曲线「区间窗口」缓存的保护带上限(采样点数)。 * @@ -96,19 +106,19 @@ namespace jdi * @brief 派生(预览)曲线走「后台异步计算」的窗口采样数门槛。 * * 拖动滑块 / 移动视界 / 缩放每帧都会请求预览曲线。窗口不超过本门槛时同步重算足够快 - * (弱峰增强 ≈0.9ms/6.5 万采样),预览曲线能**实时跟随控件**;一旦窗口超过门槛 - * (大时间跨度、整条信号),同步重算会阻塞交互(弱峰增强整条信号单条 ≈368ms), + * (弱峰调整 ≈0.9ms/6.5 万采样),预览曲线能**实时跟随控件**;一旦窗口超过门槛 + * (大时间跨度、整条信号),同步重算会阻塞交互(弱峰调整整条信号单条 ≈368ms), * 此时改为交后台线程计算、本帧先用旧曲线顶替(双缓冲),保证控件反馈始终跟手。 */ const std::size_t kDerivedAsyncWindowThreshold = 65536; - const wchar_t* const kWeakPeakLevelLabel = L"增强等级 %"; /**< 逐条目面板里「等级」滑块行的标签。 */ + const wchar_t* const kWeakPeakLevelLabel = L"调整幅度 %"; /**< 逐条目面板里「幅度」滑块行的标签。 */ const wchar_t* const kWeakPeakIterationsLabel = L"迭代次数 N"; /**< 逐条目面板里「迭代次数」滑块行的标签。 */ - const wchar_t* const kWeakPeakTitle = L"弱峰增强"; /**< 弱峰增强面板标题。 */ - const wchar_t* const kBuildSignalLabel = L"构建为新信号"; /**< 逐条目面板底部按钮文案(均值滤波 / 弱峰增强共用)。 */ + const wchar_t* const kWeakPeakTitle = L"弱峰调整"; /**< 弱峰调整面板标题。 */ + const wchar_t* const kBuildSignalLabel = L"构建为新信号"; /**< 逐条目面板底部按钮文案(均值滤波 / 弱峰调整共用)。 */ const std::size_t kEdgeCountCacheLimit = 48; /**< 边沿统计缓存条目上限(超出时淘汰最早的一条)。 */ /** - * @brief 判断坐标是否命中「弱峰增强」的某条滑块轨道。 + * @brief 判断坐标是否命中「弱峰调整」的某条滑块轨道。 * * 轨道本身只有几个像素高,直接按轨道矩形命中很难抓住,因此把命中区在纵向上下各放宽 * (至少一个轨道高度),与样式区滑块的交互一致。 @@ -133,18 +143,18 @@ namespace jdi } /** - * @brief 「弱峰增强」滑块行右侧**数值列**的宽度(布局与面板宽度共用同一算式)。 + * @brief 「弱峰调整」滑块行右侧**数值列**的宽度(布局与面板宽度共用同一算式)。 * - * 取「200%」与「10000」两者较宽者:前者是等级滑块的最大显示文本, + * 取「-200%」与「10000」两者较宽者:前者是幅度滑块的最小显示文本(带负号), * 后者为迭代次数上限留出足够位宽(上限可被用户改大)。 */ float WeakPeakValueColumnWidth(HFONT monoSmall) { - return std::max(UiMeasureTextWidth(monoSmall, L"200%"), + return std::max(UiMeasureTextWidth(monoSmall, L"-200%"), UiMeasureTextWidth(monoSmall, L"10000")); } - /** @brief 「弱峰增强」滑块行左侧**标签列**的宽度(两条标签取较宽者,保证轨道左对齐)。 */ + /** @brief 「弱峰调整」滑块行左侧**标签列**的宽度(两条标签取较宽者,保证轨道左对齐)。 */ float WeakPeakLabelColumnWidth(HFONT uiSmall) { return std::max(UiMeasureTextWidth(uiSmall, kWeakPeakLevelLabel), @@ -325,12 +335,12 @@ namespace jdi * @param [IN] kind `DerivedCurveKind` 的整型值。 * @param [IN] points 均值点数 X。 * @param [IN] iterations 迭代次数 N。 - * @param [IN] levelPercent 弱峰增强等级(0–200)。 + * @param [IN] amountPercent 弱峰调整等级(0–200)。 * @return 曲线数值(长度 = 有效窗口长度)。 */ std::vector ComputeDerivedWindow(const SignalData& signal, std::size_t windowBegin, std::size_t windowEnd, int kind, int points, - int iterations, int levelPercent) + int iterations, int amountPercent) { std::vector out; const std::vector& values = signal.values; @@ -344,7 +354,7 @@ namespace jdi values.begin() + static_cast(hi)); if (kind == static_cast(DerivedCurveKind::WEAK_PEAK)) { - ComputeWeakPeakBoost(window, static_cast(levelPercent) / 100.0, iterations, out); + ComputeWeakPeakAdjust(window, static_cast(amountPercent) / 100.0, iterations, out); } else { @@ -2158,7 +2168,7 @@ namespace jdi } const std::vector* PlotView::CurveSpanFor(DerivedCurveKind kind, const SignalData& signal, - int points, int iterations, int levelPercent, + int points, int iterations, int amountPercent, std::size_t begin, std::size_t end, std::size_t& spanBegin, bool allowAsyncCompute) const @@ -2179,7 +2189,7 @@ namespace jdi const int kindValue = static_cast(kind); const int safePoints = std::max(0, std::min(points, 1000000)); const int safeIterations = std::max(1, std::min(iterations, 1000)); - const int safeLevel = std::max(0, std::min(kWeakPeakPercentMax, levelPercent)); + const int safeLevel = std::max(kWeakPeakAmountMin, std::min(kWeakPeakAmountMax, amountPercent)); // 对齐到粗网格:轻微平移/缩放时可直接复用同一条窗口,避免每帧重算。 const std::size_t grid = std::max(4096, (end - begin) / 4); @@ -2191,7 +2201,7 @@ namespace jdi key.kind = kindValue; key.points = safePoints; key.iterations = safeIterations; - key.levelPercent = safeLevel; + key.amountPercent = safeLevel; // 1) 已有覆盖请求区间的最新条目 → 直接复用(钉住,防止本帧被回收)。 if (const CurveEntry* hit = AcquireCoveringCurve(key, begin, end, spanBegin)) @@ -2231,7 +2241,7 @@ namespace jdi std::size_t alignedEnd, int points, int iterations, std::size_t count, std::size_t& begin, std::size_t& end) const { - // 保护带取窗口长度的 1/8(夹在 4096~65536):弱峰增强的修正沿相邻极值向内传播, + // 保护带取窗口长度的 1/8(夹在 4096~65536):弱峰调整的修正沿相邻极值向内传播, // 1/8 个可见宽度足以覆盖真实信号的极值间距。 // 均值滤波必须额外**覆盖滤波半径**:N 阶共向外传播 X*N 个采样,否则窗口边界取值 // 会与「整条曲线」不同(边界上可用采样数更少)。 @@ -2345,7 +2355,7 @@ namespace jdi { std::lock_guard lock(m_curveMutex); // 同一「信号 + 曲线种类」只保留**一个**待算任务,并以**最新参数**为准: - // 拖弱峰增强等级/迭代次数滑块时每帧参数都变,若每次都入队会把队列灌满整段曲线任务, + // 拖弱峰调整等级/迭代次数滑块时每帧参数都变,若每次都入队会把队列灌满整段曲线任务, // 后台线程池被淹没 → 与 UI 抢 CPU → 拖动反而卡顿。这里把同 (signal, kind) 的旧待算任务 // 全部移除(正在计算的那个由工作线程另行负责,其结果是旧参数,会被后续条目取代)。 bool identicalPending = false; @@ -2449,7 +2459,7 @@ namespace jdi //(结构变化前宿主会先 `PauseDerivedAsync`)。 std::vector values = ComputeDerivedWindow(*job.key.signal, job.windowBegin, job.windowEnd, job.key.kind, - job.key.points, job.key.iterations, job.key.levelPercent); + job.key.points, job.key.iterations, job.key.amountPercent); { std::lock_guard lock(m_curveMutex); @@ -2546,13 +2556,13 @@ namespace jdi } const std::vector& PlotView::WeakPeakCurveFor(const SignalData& signal, - int levelPercent, int iterations) const + int amountPercent, int iterations) const { - const int level = std::max(0, std::min(kWeakPeakPercentMax, levelPercent)); + const int level = std::max(kWeakPeakAmountMin, std::min(kWeakPeakAmountMax, amountPercent)); const int stages = std::max(kWeakPeakIterationsMin, std::min(kWeakPeakIterationsMaxLimit, iterations)); for (const WeakPeakCurveEntry& entry : m_weakPeakCurveCache) { - if (entry.signal == &signal && entry.levelPercent == level && entry.iterations == stages) + if (entry.signal == &signal && entry.amountPercent == level && entry.iterations == stages) { return entry.values; } @@ -2560,17 +2570,17 @@ namespace jdi WeakPeakCurveEntry entry; entry.signal = &signal; - entry.levelPercent = level; + entry.amountPercent = level; entry.iterations = stages; // 与「构建为新信号」共用同一实现(Utilities 内的自由函数), // 因此预览曲线与构建出的采样值逐位一致。 - ComputeWeakPeakBoost(signal.values, static_cast(level) / 100.0, stages, entry.values); + ComputeWeakPeakAdjust(signal.values, static_cast(level) / 100.0, stages, entry.values); m_weakPeakCurveCache.push_back(std::move(entry)); return m_weakPeakCurveCache.back().values; } - bool PlotView::WeakPeakValueAt(const DataFile& file, const SignalData& signal, int levelPercent, + bool PlotView::WeakPeakValueAt(const DataFile& file, const SignalData& signal, int amountPercent, int iterations, double time, double& value) const { const int index = FindNearestSample(file, time); @@ -2580,7 +2590,7 @@ namespace jdi } std::size_t spanBegin = 0; const std::vector* curve = CurveSpanFor( - DerivedCurveKind::WEAK_PEAK, signal, 0, iterations, levelPercent, + DerivedCurveKind::WEAK_PEAK, signal, 0, iterations, amountPercent, static_cast(index), static_cast(index) + 1, spanBegin); if (curve == nullptr || static_cast(index) < spanBegin || (static_cast(index) - spanBegin) >= curve->size()) @@ -2885,10 +2895,10 @@ namespace jdi const bool centerAxis = (kind == EdgePanelKind::CENTER); const bool meanFilter = (kind == EdgePanelKind::MEAN); const bool weakPeak = (kind == EdgePanelKind::PEAK); - // 「均值滤波」「中心轴边沿计数」「弱峰增强」都是逐条目面板(名称 + 复选框)。 + // 「均值滤波」「中心轴边沿计数」「弱峰调整」都是逐条目面板(名称 + 复选框)。 const bool itemized = (centerAxis || meanFilter || weakPeak); - // 标题行 = “GX” 徽标 + 标题(“边沿计数” / “中心轴边沿计数” / “均值滤波” / “弱峰增强” + // 标题行 = “GX” 徽标 + 标题(“边沿计数” / “中心轴边沿计数” / “均值滤波” / “弱峰调整” // + 光标区间尾缀)。 const std::wstring title = meanFilter ? std::wstring(L"均值滤波") @@ -3495,7 +3505,7 @@ namespace jdi m_editTarget == PlotEditTarget::MEAN_FILTER_ITERATIONS || m_editTarget == PlotEditTarget::PEAK_ITERATIONS_MAX) { - // 「均值点数 X」/「迭代次数 N」(均值滤波、中心轴)与「迭代次数 N 上限」(弱峰增强): + // 「均值点数 X」/「迭代次数 N」(均值滤波、中心轴)与「迭代次数 N 上限」(弱峰调整): // 输入框都落在对应的字段/滑块行矩形上。 if (m_editFieldRect.Width <= 0.0f) { @@ -3662,7 +3672,7 @@ namespace jdi return true; } - // 「弱峰增强」迭代次数滑块的**上限**:用户可直接输入一个正整数作为新的量程上限。 + // 「弱峰调整」迭代次数滑块的**上限**:用户可直接输入一个正整数作为新的量程上限。 // 规则(用户明确要求): // * 输入框只允许输入正整数(过滤策略为 BYTE = 纯数字,粘贴同样会被清洗); // * 空内容 / 非法值(0、超出允许范围)→ 保持原上限不变; @@ -3925,7 +3935,7 @@ namespace jdi return LoadCursorW(nullptr, IDC_SIZEWE); case PlotDragMode::PEAK_LEVEL: case PlotDragMode::PEAK_ITERATIONS: - // 「弱峰增强」滑块拖拽 → 左右箭头。 + // 「弱峰调整」滑块拖拽 → 左右箭头。 return LoadCursorW(nullptr, IDC_SIZEWE); case PlotDragMode::MOVE_SIGNAL: // 拖动信号(参考 Photoshop 平移元素的抓握光标)。 @@ -3977,7 +3987,7 @@ namespace jdi // 「构建为新信号」按钮:手型光标作为可点击提示。 return LoadCursorW(nullptr, IDC_HAND); } - // 「弱峰增强」的逐信号滑块:轨道 → 左右箭头;迭代次数右侧数值 → 手型(可点击改上限)。 + // 「弱峰调整」的逐信号滑块:轨道 → 左右箭头;迭代次数右侧数值 → 手型(可点击改上限)。 if (static_cast(panelKind) == EdgePanelKind::PEAK) { for (std::size_t rowIndex = 0; rowIndex < layout.rowIterValues.size(); ++rowIndex) @@ -4248,7 +4258,7 @@ namespace jdi } case PlotDragMode::PEAK_LEVEL: { - // 「弱峰增强」某信号的等级滑块:按滑轨横向位置换算 0–200%。 + // 「弱峰调整」某信号的等级滑块:按滑轨横向位置换算 0–200%。 if (m_tab == nullptr || m_peakDragLane < 0 || m_peakDragLane >= static_cast(m_tab->Lanes().size())) { @@ -4269,16 +4279,18 @@ namespace jdi } const double ratio = UiSliderRatioFromX(layout.rowLevelTracks[static_cast(rowIndex)], fx); - item.levelPercent = - std::max(0, std::min(kWeakPeakPercentMax, - static_cast(std::lround(ratio * 100.0 * kWeakPeakLevelMax)))); + // 滑块量程 -200% – +200%:0 在正中间。 + item.amountPercent = std::max( + kWeakPeakAmountMin, + std::min(kWeakPeakAmountMax, + static_cast(std::lround((ratio * 2.0 - 1.0) * 100.0 * kWeakPeakAmountScale)))); m_weakPeakCurveCache.clear(); m_dragMoved = true; return true; } case PlotDragMode::PEAK_ITERATIONS: { - // 「弱峰增强」某信号的迭代次数滑块:量程为 1 … 该信号自己的上限。 + // 「弱峰调整」某信号的迭代次数滑块:量程为 1 … 该信号自己的上限。 if (m_tab == nullptr || m_peakDragLane < 0 || m_peakDragLane >= static_cast(m_tab->Lanes().size())) { @@ -4630,8 +4642,8 @@ namespace jdi return true; } - // 「中心轴边沿计数」/「均值滤波」/「弱峰增强」面板:逐信号条目 - // (前两者是「名称 + 复选框 + 两个参数输入框」,弱峰增强是「名称 + 复选框 + 两行滑块」)。 + // 「中心轴边沿计数」/「均值滤波」/「弱峰调整」面板:逐信号条目 + // (前两者是「名称 + 复选框 + 两个参数输入框」,弱峰调整是「名称 + 复选框 + 两行滑块」)。 if ((kind == EdgePanelKind::MEAN || kind == EdgePanelKind::CENTER || kind == EdgePanelKind::PEAK) && m_tab != nullptr) @@ -4640,7 +4652,7 @@ namespace jdi const bool boostMode = (kind == EdgePanelKind::PEAK); Lane& tabLane = m_tab->Lanes()[static_cast(index)]; - // 「弱峰增强」:先判两行滑块(数值文本 → 改上限;轨道 → 拖动)。 + // 「弱峰调整」:先判两行滑块(数值文本 → 改上限;轨道 → 拖动)。 if (boostMode) { for (std::size_t rowIndex = 0; rowIndex < layout.rowRefs.size(); ++rowIndex) @@ -4701,11 +4713,13 @@ namespace jdi m_dragMode = PlotDragMode::PEAK_LEVEL; m_peakDragLane = index; m_peakDragRef = ref; - item.levelPercent = static_cast(std::lround( - UiSliderRatioFromX(layout.rowLevelTracks[rowIndex], fx) * 100.0 * - kWeakPeakLevelMax)); - item.levelPercent = - std::max(0, std::min(kWeakPeakPercentMax, item.levelPercent)); + // 单击轨道:直接写入该处对应的幅度(-200% … +200%,0 在正中间)。 + item.amountPercent = static_cast(std::lround( + (UiSliderRatioFromX(layout.rowLevelTracks[rowIndex], fx) * 2.0 - 1.0) * + 100.0 * kWeakPeakAmountScale)); + item.amountPercent = std::max( + kWeakPeakAmountMin, + std::min(kWeakPeakAmountMax, item.amountPercent)); m_weakPeakCurveCache.clear(); return true; } @@ -4740,7 +4754,7 @@ namespace jdi return true; } // 第二排的两个输入框 → 就地编辑该信号的参数(仅 MEAN / CENTER 面板; - // 弱峰增强用的是滑块,已在上面处理)。 + // 弱峰调整用的是滑块,已在上面处理)。 if (layout.rowPoints[rowIndex].Width > 0.0f && UiPointInRect(layout.rowPoints[rowIndex], fx, fy)) { @@ -4771,7 +4785,7 @@ namespace jdi if (boostMode && layout.button.Width > 0.0f && UiPointInRect(layout.button, fx, fy)) { - // 「弱峰增强 → 构建为新信号」:与均值滤波同一套请求机制, + // 「弱峰调整 → 构建为新信号」:与均值滤波同一套请求机制, // **每个信号用自己的等级与迭代次数**(与预览曲线逐位一致)。 const Lane& lane = m_tab->Lanes()[static_cast(index)]; m_pendingWeakPeakItems.clear(); @@ -4790,8 +4804,9 @@ namespace jdi WeakPeakBuild build; build.fileIndex = ref.fileIndex; build.signalIndex = ref.signalIndex; - build.levelPercent = - std::max(0, std::min(kWeakPeakPercentMax, item->levelPercent)); + const int peakAmount = + std::max(kWeakPeakAmountMin, std::min(kWeakPeakAmountMax, item->amountPercent)); + build.amountPercent = peakAmount; // 迭代次数按**该信号自己的滑块量程**(1 … `maxIterations`)夹取。 const int peakMax = std::max( kWeakPeakIterationsMin, @@ -4803,7 +4818,7 @@ namespace jdi if (!m_pendingWeakPeakItems.empty()) { const WeakPeakBuild& first = m_pendingWeakPeakItems.front(); - SetHint(L"正在构建弱峰增强信号(等级 " + std::to_wstring(first.levelPercent) + + SetHint(L"正在构建弱峰调整信号(等级 " + std::to_wstring(first.amountPercent) + L"% / " + std::to_wstring(first.iterations) + L" 次迭代,共 " + std::to_wstring(m_pendingWeakPeakItems.size()) + L" 条)…"); m_pendingWeakPeakLane = index; @@ -5624,7 +5639,7 @@ namespace jdi } // 后台算好了新曲线: // * **静止**时:整幅作废一次,让新曲线(与新的静态场景缓存)立刻出现。 - // * **拖拽中**(拖弱峰增强/均值滤波参数滑块):**不作废整幅** —— 拖拽时参数每帧都在变, + // * **拖拽中**(拖弱峰调整/均值滤波参数滑块):**不作废整幅** —— 拖拽时参数每帧都在变, // 本来就每帧走"样式补丁"路径,补丁会直接取到最新曲线;若此时还整幅作废, // 每帧会退化成整幅重绘(实测单帧 150ms+,与"拖动要跟手"直接冲突)。 // 改为标记"已降级",等拖拽结束后再补一帧全质量。 @@ -5669,7 +5684,7 @@ namespace jdi PaintEdgeCursor(g, m_lanes); PaintSignalInteraction(g); PaintHint(g); - // 网格 Y 轴上下限的输入框、以及「弱峰增强 → 迭代次数上限」的输入框都绘制在最上层 + // 网格 Y 轴上下限的输入框、以及「弱峰调整 → 迭代次数上限」的输入框都绘制在最上层 //(后者覆盖在浮动面板的滑块行之上)。 if (m_editTarget == PlotEditTarget::LANE_Y_MAX || m_editTarget == PlotEditTarget::LANE_Y_MIN || @@ -5722,7 +5737,7 @@ namespace jdi // 外观样式(颜色/粗细/透明度/模式)变化 → 交给下方"样式补丁"分支局部重画。 // ⚠️ 位置必须在"静态场景贴图"**之后**:视界与样式都没变时(典型:鼠标悬停移动) // 应由静态场景贴图命中,绝不能落到这里 —— 否则每两帧就会触发一次"结算整幅重绘", - // 悬停反而变卡。而它又必须在"样式补丁"**之前**:派生曲线参数变化(拖弱峰增强 / + // 悬停反而变卡。而它又必须在"样式补丁"**之前**:派生曲线参数变化(拖弱峰调整 / // 均值滤波滑块)同样只需叠加重画派生曲线,走本分支比"逐网格补丁"更快且质量更高。 // ⚠️ **不要求"正在拖拽"**:非拖拽的视界变化(键盘方向键 / 工具栏)同样走此快路径, // 避免该放大区间"整幅重绘约 124ms"的卡顿。 @@ -6156,7 +6171,7 @@ namespace jdi } const std::vector& signals = Tab().Lanes()[laneIndex].signals; - // 「边沿计数(中心轴)」「均值滤波」「弱峰增强」的开关与每条被勾选信号的参数 + // 「边沿计数(中心轴)」「均值滤波」「弱峰调整」的开关与每条被勾选信号的参数 // 会改变网格内的曲线绘制。它们**只影响网格内容**(不影响网格结构/几何), // 因此只在 includeStyles 时计入 —— 于是拖参数滑块可以走“局部重画”快路径, // 而不必整幅重绘(`FindStyleOnlyLanes` 用 `m_sceneDerived` 定位受影响的网格)。 @@ -6189,7 +6204,7 @@ namespace jdi // 逐信号参数(等级 + 迭代次数)都参与指纹:改任一条的参数都会重画。 mix(static_cast(item.ref.fileIndex)); mix(static_cast(item.ref.signalIndex)); - mix(static_cast(item.levelPercent)); + mix(static_cast(item.amountPercent)); mix(static_cast(item.iterations)); } } @@ -6235,7 +6250,7 @@ namespace jdi std::uint64_t PlotView::SceneDecorHash() const { // 装饰层(图例 + Y 轴刻度)的指纹:含外观样式,但**不含派生曲线参数** - //(拖弱峰增强等级/迭代次数滑块时图例与刻度不变,装饰层缓存不应失效)。 + //(拖弱峰调整等级/迭代次数滑块时图例与刻度不变,装饰层缓存不应失效)。 return SceneContentHash(true, false); } @@ -6297,7 +6312,7 @@ namespace jdi return false; } - // 叠加预览曲线(均值滤波 / 中心轴 / 弱峰增强)的参数变化同样只需局部重画。 + // 叠加预览曲线(均值滤波 / 中心轴 / 弱峰调整)的参数变化同样只需局部重画。 std::vector currentDerived; CollectSceneDerived(currentDerived); const bool derivedComparable = (currentDerived.size() == m_sceneDerived.size()); @@ -6356,7 +6371,7 @@ namespace jdi { target.push_back(item.ref.fileIndex); target.push_back(item.ref.signalIndex); - // 均值滤波/中心轴取 points,弱峰增强取 levelPercent(由 `valueOf` 决定)。 + // 均值滤波/中心轴取 points,弱峰调整取 amountPercent(由 `valueOf` 决定)。 target.push_back(static_cast(valueOf(item))); target.push_back(item.iterations); } @@ -6366,7 +6381,7 @@ namespace jdi flatten(lane.meanFilterItems, entry.meanFilterItems, [](const Lane::MeanFilterItem& item) { return item.points; }); flatten(lane.weakPeakItems, entry.weakPeakItems, - [](const Lane::WeakPeakItem& item) { return item.levelPercent; }); + [](const Lane::WeakPeakItem& item) { return item.amountPercent; }); out[laneIndex] = std::move(entry); } } @@ -7279,7 +7294,7 @@ namespace jdi { continue; } - if (!RenderLaneLayerRegion(laneIndex, layer, 0, 0, defaultWideMin)) + if (!RenderLaneLayerRegion(laneIndex, layer, 0, 0, 0, 0, defaultWideMin)) { continue; } @@ -7290,7 +7305,7 @@ namespace jdi } bool PlotView::RenderLaneLayerRegion(int laneIndex, LaneLayer& layer, int stripX, int stripW, - double wideMin) + int stripY, int stripH, double wideMin) { if (laneIndex < 0 || laneIndex >= static_cast(m_lanes.size())) { @@ -7328,6 +7343,12 @@ namespace jdi stripX = 0; stripW = bitmapWidth; } + // 纵向同理:`stripH <= 0` 表示纵向整块(横向滚动的细条就是这种情形)。 + if (stripH <= 0 || stripY < 0 || stripY + stripH > bitmapHeight) + { + stripY = 0; + stripH = bitmapHeight; + } // 保存当前布局 / 视图 / 网格,临时切换到"宽窗口"映射后离屏渲染,再恢复。 const gd::RectF savedArea = m_area; @@ -7345,8 +7366,8 @@ namespace jdi static_cast(bitmapHeight)); // 只把 `m_area` 限为待渲染的那一段 x 区间,使 GDI/GDI+ 的裁剪只落在它上面; // x 坐标仍用**整条宽窗口**的映射(`m_plotRect` 为完整宽图),因此网格线位置与整块图层一致。 - m_area = gd::RectF(static_cast(stripX), 0.0f, static_cast(stripW), - static_cast(bitmapHeight)); + m_area = gd::RectF(static_cast(stripX), static_cast(stripY), + static_cast(stripW), static_cast(stripH)); Tab().ViewMin() = wideMin; Tab().ViewMax() = wideMin + static_cast(bitmapWidth) / scale; @@ -7459,7 +7480,7 @@ namespace jdi bool ok = true; for (std::size_t index = 0; index < m_layerFront.size(); ++index) { - if (!RenderLaneLayerRegion(static_cast(index), m_layerFront[index], 0, 0, wideMin)) + if (!RenderLaneLayerRegion(static_cast(index), m_layerFront[index], 0, 0, 0, 0, wideMin)) { ok = false; } @@ -7517,16 +7538,17 @@ namespace jdi const bool tooFar = (std::abs(shift) >= bitmapWidth - 4); // 内容(外观 / 网格几何 / 幅值**跨度** / 可见性…)与当前状态不一致。 const bool contentStale = (layer.key != LaneLayerKey(laneIndex)); - // 纵向:幅值**平移**不改内容,只改贴图偏移;但偏移超出画布余量时必须重画(重新居中)。 + // 纵向:幅值**平移**不改内容,只改贴图偏移(与横向同构)。 int vOffset = 0; if (layer.pxPerValue > 0.0) { vOffset = static_cast( std::llround((layer.anchorYMax - m_lanes[index].yMax) * layer.pxPerValue)); } - const int topRow = layer.marginY + vOffset; - const bool verticalTooFar = (topRow < 0) || - (topRow + static_cast(std::ceil(laneHeight)) > layer.height); + const int lanePixels = static_cast(std::ceil(laneHeight)); + // 可用的纵向余量 = 画布高 - 可见高(= 上下各 `marginY`);偏移超出它就只能重画。 + const int maxRoll = std::max(0, layer.height - lanePixels); + const bool verticalTooFar = (std::abs(vOffset) > maxRoll); if (contentStale || tooFar || verticalTooFar) { @@ -7550,6 +7572,51 @@ namespace jdi } continue; } + + // ------------------------------------------------------------------ + // 纵向(幅值)滚动:与横向**完全同构**——内容整体上下移动若干行, + // 再只补画新暴露的那一条(整宽 × 行数)。 + // + // ⚠️ **必须先做纵向再做横向**:横向补条由 `RenderLaneLayerRegion` 渲染, + // 而该函数会把图层的纵向锚点重置为"当前 `yMax`";若先补横向、那时锚点还没跟上, + // 补出来的细条会带着一个错的纵向偏移(贴图后表现为波形错位/撕裂)。 + // ⚠️ 没有这一步时:幅值一变就超出余量 → 每帧都排队**整块重画**(画布比可见网格高 + // 3 倍,代价很大)→ 拖动时"抽搐";而且在重画完成前,`BlitWideBuffer` 只能贴出部分 + // 行(其余留空)→ 网格内出现**空白条带**(用户报告的"绘制异常")。 + // + // ⚠️ **滚动量必须量化**(`kLayerRollQuantum`):不足一个量子的偏移**不滚**, + // 交给贴图时的纵向偏移(`srcY`)处理。这样图层的画布映射在拖动过程中**保持稳定**, + // 折线缓存(键含幅值映射)得以命中;否则每帧滚 1~30 行会让折线每帧重算 + // (实测同样的拖动:量化前 3~4ms 的滚动 + 10~14ms 整帧,量化后回到 ~5ms 量级)。 + // 余量足够大(上下各 `marginY`),因此"不滚只偏移"绝不会超出画布。 + // ------------------------------------------------------------------ + if (std::abs(vOffset) >= kLayerRollQuantum) + { + const int roll = vOffset; // 正 = 内容整体上移 roll 行(窗口向下移动) + int stripY = 0; + int stripH = 0; + if (roll > 0) + { + // 内容上移:new[y] = old[y + roll],底部暴露 roll 行。 + BitBlt(layer.dc, 0, 0, bitmapWidth, layer.height - roll, layer.dc, 0, roll, SRCCOPY); + stripY = layer.height - roll; + stripH = roll; + } + else + { + const int amount = -roll; + // 内容下移:new[y] = old[y - amount],顶部暴露 amount 行。 + BitBlt(layer.dc, 0, amount, bitmapWidth, layer.height - amount, layer.dc, 0, 0, + SRCCOPY); + stripY = 0; + stripH = amount; + } + // 锚点随之移动(与横向 `layer.wideMin += shift / scale` 同一套记账方式)。 + layer.anchorYMax -= static_cast(roll) / layer.pxPerValue; + // 补画暴露的那一条(整宽)。 + RenderLaneLayerRegion(laneIndex, layer, 0, bitmapWidth, stripY, stripH, layer.wideMin); + } + if (shift == 0) { continue; @@ -7573,7 +7640,7 @@ namespace jdi // 的映射,细条与已平移的旧像素之间会有 ≤0.5px 的错位接缝 → 拖动时看起来在抖动。 const int stripWidth = std::min(std::abs(shift), bitmapWidth); const int stripX = (shift > 0) ? (bitmapWidth - stripWidth) : 0; - RenderLaneLayerRegion(laneIndex, layer, stripX, stripWidth, layer.wideMin); + RenderLaneLayerRegion(laneIndex, layer, stripX, stripWidth, 0, 0, layer.wideMin); } } @@ -7955,7 +8022,7 @@ namespace jdi // 只有拖拽中的信号光晕可以进入静态场景缓存(悬停光晕见 PaintHoverHighlight)。 const SignalRef haloRef = SceneHaloRef(); - // 派生预览曲线(中心轴 / 均值滤波 / 弱峰增强)在下方"第二遍"统一叠加绘制, + // 派生预览曲线(中心轴 / 均值滤波 / 弱峰调整)在下方"第二遍"统一叠加绘制, // 见 `PaintLaneDerivedOverlays`(抽出以便宽缓冲区前台路径复用)。 for (const SignalRef& ref : laneSignals) @@ -8294,12 +8361,12 @@ namespace jdi const Lane::WeakPeakItem* item = tabLane.FindWeakPeakItem(ref); if (item != nullptr) { - const int levelPercent = - std::max(0, std::min(kWeakPeakPercentMax, item->levelPercent)); + const int amountPercent = + std::max(kWeakPeakAmountMin, std::min(kWeakPeakAmountMax, item->amountPercent)); const int boostIterations = std::max( kWeakPeakIterationsMin, std::min(kWeakPeakIterationsMaxLimit, item->iterations)); - PaintWeakPeakCurve(g, resolved, lane, levelPercent, boostIterations); + PaintWeakPeakCurve(g, resolved, lane, amountPercent, boostIterations); } } } @@ -8935,13 +9002,13 @@ namespace jdi DrawLabelOutline(g, labelRect, 3.0f * m_scale, ink.outline); UiText(g, text, m_fonts->monoSmall, labelRect, ink.text, TextAlign::CENTER); - // 「边沿计数(中心轴)」/「均值滤波」/「弱峰增强」开启时,纵向光标还要额外标注 + // 「边沿计数(中心轴)」/「均值滤波」/「弱峰调整」开启时,纵向光标还要额外标注 // **派生曲线**在该时刻的取值(它不是信号,因此单独标注,不并入上面的取值文案)。 // 三条曲线只是曲线来源不同,标注规则完全一致,故共用同一段绘制逻辑。 // ⚠️ 曲线只取「可见区间 ∪ 两个光标时刻 ± 保护带」的窗口(与绘制共用缓存), // 因此拖动光标/拖动参数滑块都不会触发整条曲线的重算。 const auto paintDerivedCapsule = [&](DerivedCurveKind kind, int points, - int iterations, int levelPercent, + int iterations, int amountPercent, const std::wstring& suffix) { const int sampleIndex = FindNearestSample(*resolved.file, time); @@ -8969,7 +9036,7 @@ namespace jdi } std::size_t spanBegin = 0; const std::vector* curvePtr = - CurveSpanFor(kind, *resolved.data, points, iterations, levelPercent, begin, + CurveSpanFor(kind, *resolved.data, points, iterations, amountPercent, begin, end, spanBegin, true); if (curvePtr == nullptr) { @@ -9085,18 +9152,18 @@ namespace jdi } if (m_tab->Lanes()[laneIndex].weakPeak) { - // 弱峰增强曲线:同样是只标注被勾选的信号,尾缀为「弱峰增强」。 + // 弱峰调整曲线:同样是只标注被勾选的信号,尾缀为「弱峰调整」。 const Lane::WeakPeakItem* boostItem = m_tab->Lanes()[laneIndex].FindWeakPeakItem(ref); if (boostItem != nullptr) { - const int levelPercent = - std::max(0, std::min(kWeakPeakPercentMax, boostItem->levelPercent)); + const int amountPercent = + std::max(kWeakPeakAmountMin, std::min(kWeakPeakAmountMax, boostItem->amountPercent)); const int iterations = std::max( kWeakPeakIterationsMin, std::min(kWeakPeakIterationsMaxLimit, boostItem->iterations)); - paintDerivedCapsule(DerivedCurveKind::WEAK_PEAK, 0, iterations, levelPercent, - L"弱峰增强"); + paintDerivedCapsule(DerivedCurveKind::WEAK_PEAK, 0, iterations, amountPercent, + L"弱峰调整"); } } } @@ -9279,7 +9346,7 @@ namespace jdi const bool centerAxis = (kind == EdgePanelKind::CENTER); const bool meanFilter = (kind == EdgePanelKind::MEAN); const bool weakPeak = (kind == EdgePanelKind::PEAK); - // 「均值滤波」与「弱峰增强」共用同一套「逐条目 + 底部按钮」排布。 + // 「均值滤波」与「弱峰调整」共用同一套「逐条目 + 底部按钮」排布。 const bool itemized = (centerAxis || meanFilter || weakPeak); const bool hasButton = (meanFilter || weakPeak); const bool enabled = meanFilter ? lane.meanFilter @@ -9312,7 +9379,7 @@ namespace jdi const float titleHeight = kEdgePanelTitleHeight * m_scale; const float rowHeight = kEdgePanelRowHeight * m_scale; const float pad = kEdgePanelPad * m_scale; - // 「中心轴边沿计数」「均值滤波」「弱峰增强」共用同一套「逐条目」排布, + // 「中心轴边沿计数」「均值滤波」「弱峰调整」共用同一套「逐条目」排布, // 因此不再有独立的底部编辑行;只有「边沿计数」保留两行文本式排版。 const float buttonHeight = hasButton ? (kEdgePanelButtonHeight * m_scale) : 0.0f; const float rowsPerSignal = meanFilter ? 1.0f : 2.0f; @@ -9774,7 +9841,7 @@ namespace jdi baseAlpha); // 顶点预算与波形一致(每像素列两个顶点)。 - // 交互期间进一步降到 1/2:派生曲线往往有 10 条(5 网格 × 均值滤波 + 弱峰增强), + // 交互期间进一步降到 1/2:派生曲线往往有 10 条(5 网格 × 均值滤波 + 弱峰调整), // 顶点数就是绘制耗时;降低密度能让拖动参数滑块保持流畅, // 松手后下一帧立刻恢复全密度(用户要求的"低分辨率 → 高分辨率")。 std::size_t budget = @@ -9894,7 +9961,7 @@ namespace jdi } void PlotView::PaintWeakPeakCurve(gd::Graphics& g, const ResolvedSignal& resolved, - const LaneVisual& lane, int levelPercent, int iterations) const + const LaneVisual& lane, int amountPercent, int iterations) const { if (resolved.data == nullptr || resolved.file == nullptr || resolved.style == nullptr) { @@ -9908,13 +9975,13 @@ namespace jdi // 预览曲线一律按**整条信号**计算(与「构建为新信号」逐位一致),详见 `PaintMeanFilterCurve`。 std::size_t spanBegin = 0; const std::vector* curve = CurveSpanFor(DerivedCurveKind::WEAK_PEAK, *resolved.data, 0, - iterations, levelPercent, 0, count, spanBegin, + iterations, amountPercent, 0, count, spanBegin, true); if (curve == nullptr) { return; } - // 派生曲线一律使用同一段绘制逻辑与配色规则(弱峰增强曲线同样如此)。 + // 派生曲线一律使用同一段绘制逻辑与配色规则(弱峰调整曲线同样如此)。 PaintDerivedCurve(g, resolved, lane, *curve, spanBegin); } @@ -10175,14 +10242,14 @@ namespace jdi const Lane::EdgeCenterItem* centerItem = lane.FindEdgeCenterItem(ref); mix(static_cast(centerItem != nullptr ? centerItem->points + 2 : 0)); mix(static_cast(centerItem != nullptr ? centerItem->iterations + 2 : 0)); - // 弱峰增强的逐信号参数(增强等级 % + 迭代次数 + 该信号的滑块上限)直接显示在面板上: + // 弱峰调整的逐信号参数(增强等级 % + 迭代次数 + 该信号的滑块上限)直接显示在面板上: // 上限决定迭代次数滑块的圆钮位置,因此也必须参与缓存键。 const Lane::WeakPeakItem* peakItem = lane.FindWeakPeakItem(ref); - mix(static_cast(peakItem != nullptr ? peakItem->levelPercent + 2 : 0)); + mix(static_cast(peakItem != nullptr ? peakItem->amountPercent + 2 : 0)); mix(static_cast(peakItem != nullptr ? peakItem->iterations + 2 : 0)); mix(static_cast(peakItem != nullptr ? peakItem->maxIterations + 2 : 0)); } - // 弱峰增强的滑块行矩形(每信号两行)。 + // 弱峰调整的滑块行矩形(每信号两行)。 for (const gd::RectF& rect : layout.rowLevelTracks) { mixRect(rect); @@ -10313,12 +10380,12 @@ namespace jdi } // ---- 统计浮动面板(最后绘制,覆盖在波形之上)---- - // 「边沿计数」「边沿计数(中心轴)」「均值滤波」「弱峰增强」共用同一套样式, + // 「边沿计数」「边沿计数(中心轴)」「均值滤波」「弱峰调整」共用同一套样式, // 仅标题、内容行与底部按钮不同。 // // 面板是**静态**内容:只有被拖动 / 悬停 / 参数变化的那一个真正重画, // 其余直接贴上一帧缓存好的像素(15 个面板全画一遍实测约 33ms, - // 而贴图 <1ms —— 拖动弱峰增强滑块时的主要卡顿就来自这一层)。 + // 而贴图 <1ms —— 拖动弱峰调整滑块时的主要卡顿就来自这一层)。 std::vector nextSlots; std::vector dirtyRects; const std::uint64_t panelGlobalKey = PanelGlobalKey(); @@ -10337,7 +10404,7 @@ namespace jdi const bool centerAxis = (kind == EdgePanelKind::CENTER); const bool meanFilter = (kind == EdgePanelKind::MEAN); const bool weakPeak = (kind == EdgePanelKind::PEAK); - // 「均值滤波」与「弱峰增强」共用「逐条目 + 底部按钮」排布。 + // 「均值滤波」与「弱峰调整」共用「逐条目 + 底部按钮」排布。 const bool itemized = (centerAxis || meanFilter || weakPeak); const bool hasButton = (meanFilter || weakPeak); { @@ -10464,9 +10531,9 @@ namespace jdi closeRect.X + crossPad, closeRect.Y + closeRect.Height - crossPad, crossColor, 1.6f * m_scale); - // 「中心轴边沿计数」/「均值滤波」/「弱峰增强」面板都采用「逐条目」排布: + // 「中心轴边沿计数」/「均值滤波」/「弱峰调整」面板都采用「逐条目」排布: // 第一排 = 名称 + 复选框,第二排 = 两个参数输入框(仅勾选的条目, - // 弱峰增强没有参数输入框),中心轴再多第三排 = 对应的统计结果。 + // 弱峰调整没有参数输入框),中心轴再多第三排 = 对应的统计结果。 if (itemized) { for (std::size_t rowIndex = 0; rowIndex < layout.rowRefs.size(); ++rowIndex) @@ -10498,14 +10565,14 @@ namespace jdi continue; } - // MEAN / CENTER 的平滑参数条目(弱峰增强用自己的 `WeakPeakItem`)。 + // MEAN / CENTER 的平滑参数条目(弱峰调整用自己的 `WeakPeakItem`)。 const Lane::MeanFilterItem* item = (meanFilter || centerAxis) ? (meanFilter ? tabLane.FindMeanFilterItem(ref) : tabLane.FindEdgeCenterItem(ref)) : nullptr; - // 「弱峰增强」:每个被勾选的信号各有两行**滑块** + // 「弱峰调整」:每个被勾选的信号各有两行**滑块** //(增强等级 0–200% / 迭代次数 1 … 该信号自定义上限)。 // 迭代次数行的右侧数值可点击 → 整行变为输入框以修改滑块上限。 if (weakPeak) @@ -10550,12 +10617,14 @@ namespace jdi (m_peakDragLane == static_cast(index)) && (m_peakDragRef == ref); const double levelRatio = - static_cast(std::max(0, std::min(kWeakPeakPercentMax, - peakItem->levelPercent))) / - (100.0 * kWeakPeakLevelMax); + (static_cast(std::max(kWeakPeakAmountMin, + std::min(kWeakPeakAmountMax, + peakItem->amountPercent))) / + (100.0 * kWeakPeakAmountScale) + + 1.0) * + 0.5; paintSliderBody(levelTrack, levelRatio, - std::to_wstring(peakItem->levelPercent) + L"%", - levelActive); + std::to_wstring(peakItem->amountPercent) + L"%", levelActive); // 迭代次数滑块:量程 = 1 … 该信号自己的上限。 const int peakMax = std::max(kWeakPeakIterationsMin, diff --git a/Documents/agent_mem.md b/Documents/agent_mem.md index bd1c4c8..d4c3b3f 100644 --- a/Documents/agent_mem.md +++ b/Documents/agent_mem.md @@ -514,6 +514,7 @@ Documents/ 项目文档(含本文件) | 2026-10-12 | DeepSeek-V4.1-Flash(**起源设备**) | 弱峰增强的参数输入改回**逐信号滑块**(每信号两条轨道:增强等级 0–200% / 迭代次数 N),并让「迭代次数 N」**上限可就地编辑**:点击滑块右侧数值 → 整行变成输入框(`EditFilter::BYTE` 只收数字、空内容显示灰提示当前上限、`0`/超限/空 保持原值、下调后当前迭代次数自动被夹到新上限);上限进 `PanelSlotKey` 但不进 `SceneContentHash`(不影响波形重画)。离屏测试 **409/409 通过**。 | | 2026-10-12 | DeepSeek-V4.1-Flash(**起源设备**) | **修复"斜向/不规则拖拽自由移动视界时的卡顿、掉帧与波形错误绘制"、"预览曲线不如普通信号细腻(无抗锯齿)"、"拖动参数滑块时预览曲线跳帧闪烁"**。① 根因:拖拽平移的**纵向分量**会逐帧小改被拖网格的 `yMin/yMax`,而**网格幅值范围同时参与「宽基底可用性」几何指纹与每个网格的图层键**,于是每帧都退化成整幅重绘(`m_fastFrame` 关抗锯齿)+ 5 个网格全部入队(一帧只补 1 个)→ 质量下降、卡顿、图层相互不一致(断线)。修法:`SceneContentHash` 新增 `includeLaneRange`(`SceneGeometryHash()` = `false,true,false`,幅值不进"能不能走快路径"的判定),幅值改由**逐网格** `LaneLayerHash(laneIndex)`(`LaneLayerKey` 的基)承担 → 只让真正变化的那个网格失效;并把 `RenderPendingLaneRebuilds(1)` **移到 `BlitWideBuffer` 之前**(本帧补好的网格本帧就可见)。② 预览曲线抗锯齿不再因交互(`m_fastFrame`)关闭(仅保留"陡峭段过多"这一条形同无声的逃生口)。③ `AcquireNewestCurve` 改为**只按「信号 + 曲线种类」匹配**(参数无关,优先挑覆盖所需区间的条目)→ 拖参数滑块时用上一帧的曲线顶替,后台算好再无缝替换,**不再闪烁/消失**。离屏验证:斜向拖拽 40/40 帧走宽基底快路径(中位 9.2ms,无尖峰),参数连改 10 次 **0 个空白帧**,抗锯齿回归断言(快帧混淆边缘像素 8149/8404);`uitest` **420/420 通过**、`panperf` PASS、`layercheck` 保持"0 次视界覆盖 / 平移对齐 ≤1px"。 | | 2026-10-12 | DeepSeek-V4.1-Flash(**起源设备**) | **修复"深放大下左右平移流畅、上下/斜向平移卡顿"(用户猜测:上下没有预渲染机制——已证实)与"深放大时拖拽光标跑飞"**。① 量化复现:`panperf` 新增纯竖直拖拽 + 逐帧统计**图层重画次数**,并在 5 网格全开 4 类悬浮面板(20 个)后对比 —— 竖直拖拽中位 **166.6ms/帧**、水平 **37.7ms/帧**(4.4 倍差距,用户报告的"上下卡顿"正是它)。② 主因是**面板像素图层被幅值范围击穿**:`PanelGlobalKey()` 计入 `visual.yMin/yMax`,平移的纵向分量每帧小改幅值 → 整层 20 个面板每帧重画(去掉后 166.6→**24.8ms**);次因是**图层没有纵向预渲染余量**(幅值一变就整块重画,实测 40 帧里 40 次 → 修复后 **1 次**)。③ 修法:`LaneLayer` 增加 `marginY/anchorYMax/pxPerValue`,幅值范围按**像素行**精确扩展(上下各半个网格高)、贴图时按 `(anchorYMax - 当前 yMax) * pxPerValue` 取纵向偏移,图层指纹只计幅值**跨度**不计绝对范围;幅值刻度改以**前台可见网格**的跨度/高度取 `NiceStep`(保证与整幅渲染逐像素同源);网格分界线移出图层(改由宽基底路径的 `PaintLaneDividers()` 补画,否则纵向偏移会把它带走)。④ 光标:深放大下平移会把时间光标甩到视界外很远(像"跑飞/丢失")—— 新增 `ClampCursorsAfterPan()`,在**平移**(中键/左键拖拽、Shift+滚轮、方向键)之后夹取;**离散**的视图变化(选区放大/适应视图/按网格时间轴展开)仍然不移动光标;抓取被钉在边缘的光标读数时不再沿用抓取偏移(否则首帧会跳到视界外再被夹回边缘)。验证:`uitest` **432/432**(新增 `[AI]`:贴边读数抓取后光标落在鼠标处误差 0.0px、中键平移 1.5 屏 0 帧出界且跟手)、`panperf` 竖直 3.8~5.0ms(40 帧仅 1 次图层重画)、水平 6.5ms 持平、`layercheck` 0 视界覆盖、新增探针 `.preflight/cursorcheck` 30/30。 | +| 2026-10-12 | DeepSeek-V4.1-Flash(**起源设备**) | **修复"纵向预渲染带出的绘制异常/抽搐"(中键上下拖动、尤其拖到网格之外时),并把「弱峰增强」升级为「弱峰调整」(幅度 -200% – +200%,负值削弱)**。① 绘制异常根因:上一轮给图层加了纵向余量,但"偏移超出余量"仍走**排队整块重画**(一帧只补 1 个网格)→ 重画完成前 `BlitWideBuffer` 只能贴出部分行、**其余留空**(网格内出现空白条带),且重画与贴图的锚点不同源 = "波形没按预期移动";鼠标拖到网格外很远时每帧都触发 → 抽搐。修法:与横向**同构的纵向滚动**(BitBlt 整块滚动 + 只补画暴露的一整条,`anchorYMax -= roll/pxPerValue`),并且**必须先于横向补条**;滚动量**量化**(`kLayerRollQuantum = 16` 像素,不足量子交给贴图 `srcY` 偏移)以保持画布映射稳定、让折线缓存命中(未量化时每帧滚 1~30 行 → 折线每帧重算)。验证:`panperf` 拖出网格 900px 时被拖网格的**空白行数与整幅重绘逐帧一致**(64 vs 64,即无空白条带),竖直拖拽 16 帧仅 2 次图层重画。② 弱峰调整:`ComputeWeakPeakAdjust`(原 `ComputeWeakPeakBoost`)+ `WeakPeakItem.amountPercent`(-200–200,默认 150),面板「增强等级 %」→「调整幅度 %」、滑块量程 -200%–+200%(**0 在正中间**)、数值列加宽以容纳 "-200%";识别规则与旧实现完全一致,正幅度段与旧实现**逐位相同**,负幅度段改为以相邻**异类**极值(波峰看相邻波谷)为参照:-100% 拉到**较近**的那个、-200% 拉到较远的那个(压平/抹除,左右平滑过渡),且**不得反向增强**。离屏验证:`uitest` **443/443**(正幅度 0/50/100/150/200% → 550/555/560/580/600 逐位保持;负幅度 -50/-100/-200% → 峰 500/450/400、谷 500/550/560;超范围夹取;健康峰不动)、`panperf`/`layercheck`/`cursorcheck` 全过。 | --- @@ -2679,3 +2680,42 @@ Documents/ 项目文档(含本文件) - 20 个悬浮面板时:竖直 **166.6 → 24.8ms/帧**,水平 37.7 → 38.8ms(持平)——"上下比左右卡"的不对称消失。 - `layercheck`:`clobbers=0/24`、`pan frames wrong=0/4`、居中图层 vs 整幅重绘逐像素 0 差异;新增探针 `.preflight/cursorcheck`(真文件 + 真缩放)**30/30**(单/双光标拖拽、贴边读数抓取、中键/键盘/滚轮平移)。 - `make clean && make all` 零警告。 + +--- + +## 本轮(2026-10-12,起源设备):纵向滚动补齐(消除绘制异常)+「弱峰调整」(-200%–+200%) + +用户报告:① 上下拖动视界确实流畅了,但**绘制出现错误**——网格内的信号没有按预期移动,并出现抽搐帧;**尤其把中键按在最上方网格上、一路拖到窗口最下方(鼠标离开网格很远)**时最明显;左右拖动同样出现。② 把「弱峰增强」升级为「弱峰调整」:面板的「增强等级」改为「调整幅度」,量程 -200% – +200%,0 以上与原算法一致,0 以下为**削弱**(把弱峰压向相邻波谷、甚至抹除,左右平滑过渡;波谷反之)。 + +### A. 绘制异常/抽搐的根因(上一轮纵向余量只补了一半) + +上一轮给图层加了「上下各半个网格高」的纵向余量,并把幅值平移改成**贴图纵向偏移**,但"偏移超出余量"时仍然走**排队整块重画**(`QueueLaneRebuild` → `RenderPendingLaneRebuilds(1)`,一帧只补一个网格)。于是: + +1. 重画要到(最多)下一帧才生效,而 `BlitWideBuffer` 本帧就按新偏移取源行 → 源行超出画布时 **只能贴出有像素的那一段,其余行留在底色上 = 网格内出现空白条带**("绘制错误")。 +2. 重画后的锚点与"贴图偏移"不同源 → 波形在"偏移画法"与"重画后的居中画法"之间跳 = "没按预期移动"。 +3. 鼠标拖到网格外很远时,每帧的幅值位移都远超余量 → **每帧都触发整块重画**(画布比可见网格高 3 倍,代价不小)= 抽搐。 + +### B. 修法:与横向**完全同构**的纵向滚动 + +- `RenderLaneLayerRegion` 增加 `stripY/stripH`(纵向条带),因此可以只补画"新暴露的一整条"(整宽 × n 行)。 +- `RollWideBuffer` 里新增纵向滚动:`BitBlt` 整块上/下移 `roll` 行 → 补画暴露的那一条 → `layer.anchorYMax -= roll / pxPerValue`(与横向 `layer.wideMin += shift / scale` 同一套记账)。 + ⚠️ **必须在横向补条之前**:横向补条会用"当前 `yMax`"重置图层锚点,顺序反了细条会带上错的纵向偏移。 + ⚠️ **滚动量量化**(`kLayerRollQuantum = 16` 像素):不足量子的偏移**不滚**,交给贴图的 `srcY` 偏移处理。 这样画布映射在拖动过程中保持稳定 → 折线缓存(键含幅值映射)命中;未量化时"每帧滚 1~30 行" 会让折线每帧重算(实测滚动 3~4ms + 整帧 10~14ms)。余量(上下各半个网格高)足以吸收 16px 的残差, 所以"不滚只偏移"绝不会贴出空白。 + +### C. 「弱峰调整」(原「弱峰增强」) + +- 术语统一:`ComputeWeakPeakAdjust`、`WeakPeakItem.amountPercent`(-200–200,默认 150)、 面板标题「弱峰调整」、标签「调整幅度 %」、光标胶囊尾缀「弱峰调整」、 构建信号名 `<原信号> (弱峰调整 X% M次迭代)`(X 可为负)。 +- 滑块:量程 -200% – +200%,**0 在正中间**(`ratio = (amount/200 + 1) / 2`);数值列宽度改为量 `"-200%"`。 +- 目标值(`amount` = 幅度/100,识别规则**与旧实现完全相同**): + * `0 ≤ a ≤ 1`:当前值 → 同类极值中**较温和**的一侧(波峰取较低的波峰、波谷取较高的波谷); + * `1 < a ≤ 2`:该侧 → 另一侧(**与旧实现逐位一致**。⚠️ 波峰 `lower + (a-1)*(upper-lower)`、 波谷 `upper - (a-1)*(upper-lower)` 方向相反,符号不能写成一样——本轮先写错,被既有测试立刻抓住); + * `-1 ≤ a < 0`:当前值 → 相邻**异类**极值中**较近**的一个(波峰取较高的波谷、波谷取较低的波峰); + * `-2 ≤ a < -1`:较近的异类极值 → 较远的那个。即 -100% 拉到较近的波谷、-200% 拉到较远的波谷, 弱峰被压平甚至抹除,左右波形平滑过渡(波谷反向同理)。 + * ⚠️ 负幅度**不得反向增强**:目标夹在"不越过当前值"的一侧(噪声使异类极值落在错侧时)。 +- 平滑偏移(相邻极值之间的 smoothstep 过渡)**逐字未改**,因此负幅度下"一个极值被压低、相邻被抬高"时, 两者之间的**中间采样点可能越过中值线**——这是平滑过渡的正常结果, **不要把"逐点不得越过中值"写成断言**(只在极值点上断言"未反向增强")。 + +### D. 验证(全部离屏,不占用鼠标/键盘/屏幕) + +- `uitest` **443/443**:`[X]` 正幅度 0/50/100/150/200% → 峰 550/555/560/580/600(**旧值逐位保持**)、 谷 450/445/440/420/400;新增负幅度 -50/-100/-200% → 峰 **500/450/400**、谷 **500/550/560**; 超范围(+7 / -7)夹取;"未反向增强"(极值点)与"健康峰不动"; 滑块:中间 = 0%、右端(拖出)= +200%、左端(拖出)= -200%;构建信号名含「弱峰调整」; `[AH]` 纯竖直拖拽 16 帧仅 **2** 次图层重画(量化滚动)。 +- `panperf`:拖出网格 900px(鼠标远在窗口外)+ 对角平移 31 帧,**被拖网格的空白行数与整幅重绘逐帧一致** (64 vs 64 → 无空白条带)、帧时 10~13ms(滚动 2~4ms)、末帧与整幅重绘的差异与水平基线同量级 (best integer shift (-1,0),即已知的 ≤0.5px 整数像素残差)。 +- `layercheck`:`clobbers=0/24`、`pan frames wrong=0/4`、居中图层 vs 整幅重绘逐像素 0 差异; `cursorcheck` 30/30;`make clean && make all` 零警告。 diff --git a/Utilities/Inc/DataModel.hpp b/Utilities/Inc/DataModel.hpp index b5d58b2..f8f215f 100644 --- a/Utilities/Inc/DataModel.hpp +++ b/Utilities/Inc/DataModel.hpp @@ -374,7 +374,7 @@ namespace jdi using EdgeCenterItem = MeanFilterItem; /** - * @brief 「弱峰增强」中某个信号自己的参数条目。 + * @brief 「弱峰调整」中某个信号自己的参数条目。 * * 与「均值滤波」完全一致:**每个被勾选的信号各自持有参数** *(增强等级 0–200% + 迭代次数 N),因此同一网格内不同信号可以使用不同强度。 @@ -385,7 +385,7 @@ namespace jdi struct WeakPeakItem { SignalRef ref; /**< 目标信号。 */ - int levelPercent = 150; /**< 增强等级(0–200;100% = 到达较温和的一侧,200% = 完全拉平)。 */ + int amountPercent = 150; /**< 调整幅度(-200–200;0 = 不动,100% = 到达较温和一侧,200% = 完全拉平;负值为削弱)。 */ int iterations = 1; /**< 迭代次数 N(1 … `maxIterations`)。 */ int maxIterations = 10; /**< 「迭代次数 N」滑块的上限(用户可在面板上修改,恒 ≥ 1)。 */ }; @@ -509,9 +509,9 @@ namespace jdi double meanFilterPanelY = -1.0; /** - * @brief 「实验性功能 → 弱峰增强」开关。 + * @brief 「实验性功能 → 弱峰调整」开关。 * - * 开启后为本网格内**被勾选**的信号额外绘制一条「弱峰增强」曲线: + * 开启后为本网格内**被勾选**的信号额外绘制一条「弱峰调整」曲线: * 识别纹波的波峰/波谷,把“比左右同类极值更弱”的波峰上拉、波谷下拉, * 并按各信号自己的「增强等级」在左右两个同类极值之间取目标值,使其恢复与周边振幅。 * 这条曲线只是叠加显示,不会作为信号加入左侧列表。 @@ -519,35 +519,35 @@ namespace jdi bool weakPeak = false; /** - * @brief 参与弱峰增强预览的信号及其**各自的参数**(只包含被勾选的信号)。 + * @brief 参与弱峰调整预览的信号及其**各自的参数**(只包含被勾选的信号)。 * * 参数(等级 0–200% + 迭代次数 N)按信号保存,与「均值滤波」的实现方式一致: * 同一网格内不同信号可以使用不同的增强强度,也可以分别「构建为新信号」。 */ std::vector weakPeakItems; - /** 取某个信号的弱峰增强参数(未勾选时返回空指针)。 */ + /** 取某个信号的弱峰调整参数(未勾选时返回空指针)。 */ const WeakPeakItem* FindWeakPeakItem(const SignalRef& ref) const; - /** 取得或创建某信号的弱峰增强参数(用于勾选)。 */ + /** 取得或创建某信号的弱峰调整参数(用于勾选)。 */ WeakPeakItem& EnsureWeakPeakItem(const SignalRef& ref); - /** 取消某信号的弱峰增强勾选。 */ + /** 取消某信号的弱峰调整勾选。 */ void RemoveWeakPeakItem(const SignalRef& ref); - /** 取某信号的弱峰增强勾选状态。 */ + /** 取某信号的弱峰调整勾选状态。 */ bool WeakPeakEnabled(const SignalRef& ref) const { return FindWeakPeakItem(ref) != nullptr; } - /** 弱峰增强浮动面板是否展开。 */ + /** 弱峰调整浮动面板是否展开。 */ bool weakPeakPanelOpen = true; - /** 弱峰增强面板左上角位置(客户区像素;<0 表示自动定位)。 */ + /** 弱峰调整面板左上角位置(客户区像素;<0 表示自动定位)。 */ double weakPeakPanelX = -1.0; - /** 弱峰增强面板左上角纵坐标(<0 表示自动定位)。 */ + /** 弱峰调整面板左上角纵坐标(<0 表示自动定位)。 */ double weakPeakPanelY = -1.0; }; @@ -593,37 +593,44 @@ namespace jdi std::vector& out); /** - * @brief 「弱峰增强」:把比周边更弱的纹波波峰/波谷恢复(自适应弱峰增强)。 + * @brief 「弱峰调整」:把比周边更弱的纹波波峰/波谷恢复,或(负幅度时)把弱峰削弱。 * * 本算法**不做任何前置滤波**:需要平滑时用户可以先用「均值滤波」构建一条 - * 滤波后的信号,再对它做弱峰增强。极值点直接在输入采样上识别。 + * 滤波后的信号,再对它做弱峰调整。极值点直接在输入采样上识别。 * * 单阶处理(全 O(n)): * 1. **识别趋势与极值**:扫描单调段,方向发生翻转处的上一个采样点即为 * 波峰(先升后降)或波谷(先降后升);波峰与波谷交替出现。 * 2. **判定“弱”**:对第 k 个波峰,取同类的左右相邻波峰(k−2、k+2)。 * 若本波峰低于两者的均值,说明它是被削弱的那个;波谷反之(高于两者均值)。 - * 3. **目标值**(`level` = 弱峰增强等级,0–2,对应面板上的 0%–200%): - * * 波峰:左右两个波峰中较小者记为 `lower`、较大者记为 `upper`; - * 0 ≤ level ≤ 1 → 目标 = 当前值 + level × (`lower` − 当前值); - * 1 < level ≤ 2 → 目标 = `lower` + (level − 1) × (`upper` − `lower`)。 - * * 波谷:左右两个波谷中较大者记为 `upper`、较小者记为 `lower`; - * 0 ≤ level ≤ 1 → 目标 = 当前值 + level × (`upper` − 当前值); - * 1 < level ≤ 2 → 目标 = `upper` − (level − 1) × (`upper` − `lower`)。 - * 即 **0% 完全不动**(可做极小幅度调整)、**100% 拉到较温和的一侧**、 - * **200% 完全拉平**;两段在 100% 处连续。 + * 这一步在正、负幅度下**完全一致**(用户要求「弱峰的识别定义与之前相同」)。 + * 3. **目标值**(`amount` = 调整幅度,-2 – +2,对应面板上的 -200% – +200%): + * * **正幅度(增强,与原实现逐位一致)**:以**同类**极值为参照。 + * 波峰:较小者记为 `lower`、较大者记为 `upper`; + * 0 ≤ a ≤ 1 → 目标 = 当前值 + a × (`lower` − 当前值); + * 1 < a ≤ 2 → 目标 = `lower` + (a − 1) × (`upper` − `lower`)。 + * 波谷:较大者记为 `upper`、较小者记为 `lower`; + * 0 ≤ a ≤ 1 → 目标 = 当前值 + a × (`upper` − 当前值); + * 1 < a ≤ 2 → 目标 = `upper` − (a − 1) × (`upper` − `lower`)。 + * * **负幅度(削弱)**:以相邻的**异类**极值(波峰的相邻波谷 / 波谷的相邻波峰)为参照。 + * 令 `near` = 较靠近当前值的那个(波峰 → 较高的波谷;波谷 → 较低的波峰)、 + * `far` = 另一个: + * -1 ≤ a < 0 → 目标 = 当前值 + |a| × (`near` − 当前值); + * -2 ≤ a < -1 → 目标 = `near` + (|a| − 1) × (`far` − `near`)。 + * 即 **-100% 把弱峰拉到相邻异类极值中较近的那个**、**-200% 拉到较远的那个** + * (波形被压平甚至抹除)。削弱**不会反向增强**(目标被夹在"不越过当前值"的一侧)。 * 4. **平滑偏移**:极值点本身取其目标值,两侧的数据点按相邻两个极值点的偏移量 * 平滑过渡(平滑步进,两端导数为 0),使波形整体趋势不变、只在弱峰附近被推回。 * * 正常周期的极值不满足条件、偏移量恒为 0,因此输出与输入逐位相同。 * * @param [IN] values 原始采样值。 - * @param [IN] level 弱峰增强等级(0–2,对应 0%–200%,超出范围会被夹取)。 + * @param [IN] amount 调整幅度(-2 – +2,对应 -200% – +200%,超出范围会被夹取)。 * @param [IN] iterations 迭代次数 N(重复应用次数,至少为 1)。 * @param [OUT] out 输出数组(长度与 values 相同;原值为 NaN 处保持 NaN)。 */ - void ComputeWeakPeakBoost(const std::vector& values, double level, int iterations, - std::vector& out); + void ComputeWeakPeakAdjust(const std::vector& values, double amount, int iterations, + std::vector& out); /** * @brief 在指定文件的时间轴上查找与给定时刻最接近的采样点。 diff --git a/Utilities/Src/DataModel.cpp b/Utilities/Src/DataModel.cpp index 6a25278..3fb5688 100644 --- a/Utilities/Src/DataModel.cpp +++ b/Utilities/Src/DataModel.cpp @@ -1046,7 +1046,7 @@ namespace jdi namespace { /** - * @brief 「弱峰增强」内部使用的极值点(波峰 / 波谷)。 + * @brief 「弱峰调整」内部使用的极值点(波峰 / 波谷)。 */ struct PeakExtremum { @@ -1056,17 +1056,18 @@ namespace jdi }; /** - * @brief 「弱峰增强」的单阶处理:把偏弱的波峰上拉、偏弱的波谷下拉。 + * @brief 「弱峰调整」的单阶处理:把偏弱的波峰上拉 / 偏弱的波谷下拉, + * 或(负幅度)把弱峰本身削弱、压平。 * * **不做任何前置滤波**(需要平滑时用户可以先用「均值滤波」构建一条滤波后的信号): * 极值点直接在输入采样上通过单调段的方向翻转识别。 * * @param [IN] input 本阶的输入采样值。 - * @param [IN] strength 弱峰增强等级(0–1,已夹取)。 + * @param [IN] amount 调整幅度(-2 – +2,已夹取;0 = 完全不动)。 * @param [OUT] output 本阶的输出(长度与 input 相同;原值为 NaN 处保持 NaN)。 * @return 确实有采样点被改动返回 true;完全无弱峰时返回 false。 */ - bool ApplyWeakPeakStage(const std::vector& input, double strength, + bool ApplyWeakPeakStage(const std::vector& input, double amount, std::vector& output, std::vector& extremes) { const std::size_t count = input.size(); @@ -1162,32 +1163,67 @@ namespace jdi const double mean = (left.value + right.value) * 0.5; const double lower = std::min(left.value, right.value); const double upper = std::max(left.value, right.value); - // 目标值分段插值(`strength` = 等级 0–2): - // 0 ≤ s ≤ 1:当前值 ↔ 较温和的一侧(波峰取较低的波峰 lower、波谷取较高的波谷 upper); - // 1 < s ≤ 2:该侧 ↔ 另一侧(与原先 0–100% 的语义一致)。 - // 两段在 s = 1 处连续,因此 s = 0 完全不动、s = 2 完全拉平。 - double destination = 0.0; - if (current.type > 0) + if (!(current.type > 0 ? (current.value < mean) : (current.value > mean))) { - // 波峰:低于左右两个波峰的均值 → 判定为弱峰,上拉到等级对应的目标值。 - if (!(current.value < mean)) + // 不是弱峰:本极值点不动。 + continue; + } + double destination = current.value; + if (amount >= 0.0) + { + // ---- 正幅度(增强,与旧实现逐位一致)---- + // 目标值分段插值(`amount` = 幅度 0–2): + // 0 ≤ a ≤ 1:当前值 ↔ 较温和的一侧(波峰取较低的波峰 lower、波谷取较高的波谷 upper); + // 1 < a ≤ 2:该侧 ↔ 另一侧(与原先 0–100% 的语义一致)。 + // 两段在 a = 1 处连续,因此 a = 0 完全不动、a = 2 完全拉平。 + // ⚠️ 波峰从 `lower` **向上**走到 `upper`、波谷从 `upper` **向下**走到 `lower`, + // 两者方向相反(符号不能写成一样)。 + if (current.type > 0) { - continue; + destination = (amount <= 1.0) + ? (current.value + amount * (lower - current.value)) + : (lower + (amount - 1.0) * (upper - lower)); + } + else + { + destination = (amount <= 1.0) + ? (current.value + amount * (upper - current.value)) + : (upper - (amount - 1.0) * (upper - lower)); } - destination = (strength <= 1.0) - ? (current.value + strength * (lower - current.value)) - : (lower + (strength - 1.0) * (upper - lower)); } else { - // 波谷:高于左右两个波谷的均值 → 判定为弱峰,下拉到等级对应的目标值。 - if (!(current.value > mean)) + // ---- 负幅度(削弱)---- + // 参照物改为相邻的**异类**极值(波峰看相邻波谷、波谷看相邻波峰), + // 并且"较近的那个"对波峰是较高的波谷、对波谷是较低的波峰。 + const PeakExtremum& nearLeft = extremes[k - 1]; + const PeakExtremum& nearRight = extremes[k + 1]; + if (nearLeft.type == current.type || nearRight.type == current.type) + { + continue; // 极值序列本应交替;异常时不动这一点 + } + if (!std::isfinite(nearLeft.value) || !std::isfinite(nearRight.value)) { continue; } - destination = (strength <= 1.0) - ? (current.value + strength * (upper - current.value)) - : (upper - (strength - 1.0) * (upper - lower)); + const double nearValue = (current.type > 0) ? std::max(nearLeft.value, nearRight.value) + : std::min(nearLeft.value, nearRight.value); + const double farValue = (current.type > 0) ? std::min(nearLeft.value, nearRight.value) + : std::max(nearLeft.value, nearRight.value); + const double magnitude = std::min(2.0, -amount); + destination = (magnitude <= 1.0) + ? (current.value + magnitude * (nearValue - current.value)) + : (nearValue + (magnitude - 1.0) * (farValue - nearValue)); + // ⚠️ 削弱**不得反向增强**:相邻异类极值落在"错误的一侧"时(例如噪声导致的 + // 波峰反而低于相邻波谷),把目标夹回当前值所在的一侧。 + if (current.type > 0) + { + destination = std::min(destination, current.value); + } + else + { + destination = std::max(destination, current.value); + } } shift[k] = destination - current.value; } @@ -1237,8 +1273,8 @@ namespace jdi } } // namespace - void ComputeWeakPeakBoost(const std::vector& values, double level, int iterations, - std::vector& out) + void ComputeWeakPeakAdjust(const std::vector& values, double amount, int iterations, + std::vector& out) { const std::size_t count = values.size(); out.assign(count, std::numeric_limits::quiet_NaN()); @@ -1249,7 +1285,7 @@ namespace jdi // 第 1 阶:从原始采样值出发(非有限值保持 NaN)。 // 工作缓冲用线程本地变量复用容量:单条曲线动辄上千万采样,若每次调用都重新分配 - // 并零初始化,单条「弱峰增强」的耗时会被明显放大(内存分配 + page fault)。 + // 并零初始化,单条「弱峰调整」的耗时会被明显放大(内存分配 + page fault)。 thread_local std::vector currentBuffer; thread_local std::vector nextBuffer; thread_local std::vector extremesBuffer; @@ -1265,15 +1301,17 @@ namespace jdi } // 「迭代次数 N」与「均值滤波」的迭代次数语义一致:把同一处理**重复应用 N 次**, - // 每一阶以上一阶的结果为输入。一阶时改定量已严格等于「等级」对应的目标值; - // 多次迭代时相邻的弱峰会互相抬高彼此的基准,从而把较宽的弱峰区逐步恢复。 + // 每一阶以上一阶的结果为输入。一阶时改定量已严格等于「幅度」对应的目标值; + // 多次迭代时相邻的弱峰会互相抬高彼此的基准,从而把较宽的弱峰区逐步恢复(或削弱)。 const int stages = std::max(1, std::min(iterations, 1000)); - // 增强等级 0–2(对应面板上的 0%–200%):0 完全不动,1 拉到较温和的一侧,2 完全拉平。 - const double strength = std::max(0.0, std::min(2.0, level)); + // 调整幅度 -2 – +2(对应面板上的 -200% – +200%): + // > 0 增强(0 = 不动,1 = 拉到较温和的一侧,2 = 完全拉平); + // < 0 削弱(-1 = 拉到相邻异类极值中较近的一个,-2 = 拉到较远的那个)。 + const double clamped = std::max(-2.0, std::min(2.0, amount)); std::vector& next = nextBuffer; for (int stage = 0; stage < stages; ++stage) { - if (!ApplyWeakPeakStage(current, strength, next, extremesBuffer)) + if (!ApplyWeakPeakStage(current, clamped, next, extremesBuffer)) { // 这一阶没有任何采样点需要调整:后续迭代次数也不会再有变化,提前结束。 break; @@ -1612,9 +1650,9 @@ namespace jdi } } - // 均值滤波 / 中心轴边沿计数 / 弱峰增强的条目同样持有信号引用,必须一起删除/前移。 + // 均值滤波 / 中心轴边沿计数 / 弱峰调整的条目同样持有信号引用,必须一起删除/前移。 // 三类条目都是「带 `ref` 的结构体」,因此用泛型 lambda 统一处理 - //(弱峰增强的参数与均值滤波不同,不能再共用同一个具体类型)。 + //(弱峰调整的参数与均值滤波不同,不能再共用同一个具体类型)。 const auto purgeRefs = [fileIndex, signalIndex](auto& items) { using ItemType = typename std::decay_t::value_type; @@ -1935,7 +1973,7 @@ namespace jdi lane.meanFilterPanelOpen = source.meanFilterPanelOpen; lane.meanFilterPanelX = source.meanFilterPanelX; lane.meanFilterPanelY = source.meanFilterPanelY; - // 拆分后每个子网格只保留自己那个信号的均值滤波 / 中心轴 / 弱峰增强条目。 + // 拆分后每个子网格只保留自己那个信号的均值滤波 / 中心轴 / 弱峰调整条目。 lane.meanFilterItems.clear(); lane.edgeCenterItems.clear(); lane.weakPeakItems.clear();