修复上下拖动时的绘制异常/抽搐(纵向预渲染补齐)+「弱峰增强」升级为「弱峰调整」(-200%–+200%)
一、纵向绘制异常/抽搐的根因 上一轮给图层加了「上下各半个网格高」的纵向预渲染余量,但"偏移超出余量"时仍走**排队整块重画** (一帧只补一个网格):重画生效前 BlitWideBuffer 只能贴出有像素的那一部分,其余行留在底色上 = 网格内出现空白条带("绘制错误");且重画后的锚点与"贴图偏移"不同源 = 波形"没按预期移动"。 鼠标按在最上方网格一路拖到窗口最下方(离开网格很远)时,每帧的幅值位移都远超余量 → 每帧触发整块 重画 = 抽搐;左右拖动时若鼠标同时有纵向位移,同样触发。 二、修法:与横向完全同构的纵向滚动 - RenderLaneLayerRegion 增加 stripY/stripH(纵向条带),可只补画"新暴露的一整条"。 - RollWideBuffer 新增纵向滚动:BitBlt 整块上/下移 roll 行 → 补画暴露的那一条 → layer.anchorYMax -= roll / pxPerValue(与横向 wideMin 同一套记账)。 必须在横向补条之前(横向补条会用当前 yMax 重置图层锚点,顺序反了细条会带上错的纵向偏移)。 - 滚动量量化(kLayerRollQuantum = 16 像素):不足量子的偏移不滚,交给贴图的 srcY 偏移处理, 使画布映射在拖动中保持稳定、折线缓存(键含幅值映射)得以命中。 未量化时"每帧滚 1~30 行"会让折线每帧重算(实测滚动 3~4ms + 整帧 10~14ms)。 三、「弱峰调整」(原「弱峰增强」) - 术语统一:ComputeWeakPeakAdjust、WeakPeakItem.amountPercent(-200–200,默认 150)、面板标题 「弱峰调整」、标签「调整幅度 %」、光标胶囊尾缀、构建信号名「<原信号> (弱峰调整 X% M次迭代)」。 - 滑块量程 -200% – +200%,0 在正中间(ratio = (amount/200 + 1) / 2);数值列加宽以容纳 "-200%"。 - 目标值(识别规则与旧实现完全相同;amount = 幅度/100): * 0 ≤ a ≤ 1:当前值 → 同类极值中较温和的一侧; * 1 < a ≤ 2:该侧 → 另一侧(与旧实现逐位一致); * -1 ≤ a < 0:当前值 → 相邻异类极值中较近的一个(波峰看较高的波谷、波谷看较低的波峰); * -2 ≤ a < -1:较近的异类极值 → 较远的那个(弱峰被压平甚至抹除,左右平滑过渡)。 - 负幅度不得反向增强:目标夹在"不越过当前值"的一侧。平滑偏移的逐点过渡逻辑未改动。 四、验证(全部离屏,不占用鼠标/键盘/屏幕) - 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; 超范围夹取、"未反向增强"(极值点)、健康峰不动;滑块中间 = 0%、拖出两端 = ±200%; 构建信号名含「弱峰调整」;[AH] 纯竖直拖拽 16 帧仅 2 次图层重画。 - panperf:拖出网格 900px + 对角平移 31 帧,被拖网格的空白行数与整幅重绘逐帧一致(64 vs 64 → 无空白条带),帧时 10~13ms,末帧差异与水平基线同量级(已知的 ≤0.5px 整数像素残差)。 - layercheck:clobbers=0/24、pan frames wrong=0/4、居中图层 vs 整幅重绘逐像素 0 差异; cursorcheck 30/30;make clean && make all 零警告。
This commit is contained in:
1 parent
cb030573fa
commit
ff3319b842
7 files changed
+380
-224
No files matched your search
@@ -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。
|
||||
|
||||
@@ -1481,14 +1481,14 @@ namespace jdi
|
||||
|
||||
// 1. 计算派生采样值(与网格内的预览曲线共用同一实现,因此逐位一致)。
|
||||
// 多次迭代均值滤波形如「<原信号名称> (N点均值 M次迭代)」,
|
||||
// 弱峰增强形如「<原信号名称> (弱峰增强 M% N次迭代)」。
|
||||
// 弱峰调整形如「<原信号名称> (弱峰调整 M% N次迭代)」。
|
||||
std::vector<float> derived_values;
|
||||
SignalData derived;
|
||||
if (request.weakPeak)
|
||||
{
|
||||
ComputeWeakPeakBoost(source.values, static_cast<double>(request.levelPercent) / 100.0,
|
||||
ComputeWeakPeakAdjust(source.values, static_cast<double>(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<DerivedSignalRequest> 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);
|
||||
}
|
||||
|
||||
@@ -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<MeanFilterBuild>& 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<WeakPeakBuild>& 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<float>& WeakPeakCurveFor(const SignalData& signal, int levelPercent,
|
||||
const std::vector<float>& 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<double>& timeTicks);
|
||||
|
||||
/**
|
||||
* @brief 绘制某网格的全部**派生预览曲线**(均值滤波 / 中心轴 / 弱峰增强),叠加在原信号之上。
|
||||
* @brief 绘制某网格的全部**派生预览曲线**(均值滤波 / 中心轴 / 弱峰调整),叠加在原信号之上。
|
||||
*
|
||||
* 从 `PaintLane` 抽出,供「宽缓冲区」前台路径复用:宽缓冲只存"基底"(网格/波形),
|
||||
* 派生曲线每帧作为叠加层单独绘制,因此拖派生参数滑块时无需重画波形。
|
||||
@@ -1155,7 +1155,7 @@ namespace jdi
|
||||
std::vector<gd::RectF> rowIterations;
|
||||
std::vector<gd::RectF> 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<float>* 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<MeanFilterBuild> m_pendingMeanFilterItems; /**< 待构建的信号及其参数。 */
|
||||
std::vector<WeakPeakBuild> m_pendingWeakPeakItems; /**< 待构建的弱峰增强信号及其参数。 */
|
||||
std::vector<WeakPeakBuild> m_pendingWeakPeakItems; /**< 待构建的弱峰调整信号及其参数。 */
|
||||
bool m_mouseOverPanel = false; /**< 上一帧鼠标是否落在某个实验性功能面板上。 */
|
||||
int m_peakDragLane = -1; /**< 正在拖动弱峰滑块的面板所属网格。 */
|
||||
SignalRef m_peakDragRef; /**< 正在拖动弱峰滑块的**信号**(每个信号一套参数)。 */
|
||||
@@ -1769,18 +1769,18 @@ namespace jdi
|
||||
};
|
||||
mutable std::deque<MeanCurveEntry> 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<float> values; /**< 与 signal->values 等长;缺失处为 NaN。 */
|
||||
};
|
||||
mutable std::deque<WeakPeakCurveEntry> 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<int> edgeCenterItems;
|
||||
std::vector<int> 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 前台补画各网格的**分界线**(网格顶部那条较重的横线)。
|
||||
*
|
||||
|
||||
@@ -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<float> 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<float> out;
|
||||
const std::vector<double>& values = signal.values;
|
||||
@@ -344,7 +354,7 @@ namespace jdi
|
||||
values.begin() + static_cast<std::ptrdiff_t>(hi));
|
||||
if (kind == static_cast<int>(DerivedCurveKind::WEAK_PEAK))
|
||||
{
|
||||
ComputeWeakPeakBoost(window, static_cast<double>(levelPercent) / 100.0, iterations, out);
|
||||
ComputeWeakPeakAdjust(window, static_cast<double>(amountPercent) / 100.0, iterations, out);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2158,7 +2168,7 @@ namespace jdi
|
||||
}
|
||||
|
||||
const std::vector<float>* 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<int>(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<std::size_t>(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<std::mutex> lock(m_curveMutex);
|
||||
// 同一「信号 + 曲线种类」只保留**一个**待算任务,并以**最新参数**为准:
|
||||
// 拖弱峰增强等级/迭代次数滑块时每帧参数都变,若每次都入队会把队列灌满整段曲线任务,
|
||||
// 拖弱峰调整等级/迭代次数滑块时每帧参数都变,若每次都入队会把队列灌满整段曲线任务,
|
||||
// 后台线程池被淹没 → 与 UI 抢 CPU → 拖动反而卡顿。这里把同 (signal, kind) 的旧待算任务
|
||||
// 全部移除(正在计算的那个由工作线程另行负责,其结果是旧参数,会被后续条目取代)。
|
||||
bool identicalPending = false;
|
||||
@@ -2449,7 +2459,7 @@ namespace jdi
|
||||
//(结构变化前宿主会先 `PauseDerivedAsync`)。
|
||||
std::vector<float> 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<std::mutex> lock(m_curveMutex);
|
||||
@@ -2546,13 +2556,13 @@ namespace jdi
|
||||
}
|
||||
|
||||
const std::vector<float>& 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<double>(level) / 100.0, stages, entry.values);
|
||||
ComputeWeakPeakAdjust(signal.values, static_cast<double>(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<float>* curve = CurveSpanFor(
|
||||
DerivedCurveKind::WEAK_PEAK, signal, 0, iterations, levelPercent,
|
||||
DerivedCurveKind::WEAK_PEAK, signal, 0, iterations, amountPercent,
|
||||
static_cast<std::size_t>(index), static_cast<std::size_t>(index) + 1, spanBegin);
|
||||
if (curve == nullptr || static_cast<std::size_t>(index) < spanBegin ||
|
||||
(static_cast<std::size_t>(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<EdgePanelKind>(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<int>(m_tab->Lanes().size()))
|
||||
{
|
||||
@@ -4269,16 +4279,18 @@ namespace jdi
|
||||
}
|
||||
const double ratio =
|
||||
UiSliderRatioFromX(layout.rowLevelTracks[static_cast<std::size_t>(rowIndex)], fx);
|
||||
item.levelPercent =
|
||||
std::max(0, std::min(kWeakPeakPercentMax,
|
||||
static_cast<int>(std::lround(ratio * 100.0 * kWeakPeakLevelMax))));
|
||||
// 滑块量程 -200% – +200%:0 在正中间。
|
||||
item.amountPercent = std::max(
|
||||
kWeakPeakAmountMin,
|
||||
std::min(kWeakPeakAmountMax,
|
||||
static_cast<int>(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<int>(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<std::size_t>(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<int>(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<int>(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<std::size_t>(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<SignalRef>& signals = Tab().Lanes()[laneIndex].signals;
|
||||
// 「边沿计数(中心轴)」「均值滤波」「弱峰增强」的开关与每条被勾选信号的参数
|
||||
// 「边沿计数(中心轴)」「均值滤波」「弱峰调整」的开关与每条被勾选信号的参数
|
||||
// 会改变网格内的曲线绘制。它们**只影响网格内容**(不影响网格结构/几何),
|
||||
// 因此只在 includeStyles 时计入 —— 于是拖参数滑块可以走“局部重画”快路径,
|
||||
// 而不必整幅重绘(`FindStyleOnlyLanes` 用 `m_sceneDerived` 定位受影响的网格)。
|
||||
@@ -6189,7 +6204,7 @@ namespace jdi
|
||||
// 逐信号参数(等级 + 迭代次数)都参与指纹:改任一条的参数都会重画。
|
||||
mix(static_cast<std::uint64_t>(item.ref.fileIndex));
|
||||
mix(static_cast<std::uint64_t>(item.ref.signalIndex));
|
||||
mix(static_cast<std::uint64_t>(item.levelPercent));
|
||||
mix(static_cast<std::uint64_t>(item.amountPercent));
|
||||
mix(static_cast<std::uint64_t>(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<SceneDerivedEntry> 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<int>(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<int>(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<float>(bitmapHeight));
|
||||
// 只把 `m_area` 限为待渲染的那一段 x 区间,使 GDI/GDI+ 的裁剪只落在它上面;
|
||||
// x 坐标仍用**整条宽窗口**的映射(`m_plotRect` 为完整宽图),因此网格线位置与整块图层一致。
|
||||
m_area = gd::RectF(static_cast<float>(stripX), 0.0f, static_cast<float>(stripW),
|
||||
static_cast<float>(bitmapHeight));
|
||||
m_area = gd::RectF(static_cast<float>(stripX), static_cast<float>(stripY),
|
||||
static_cast<float>(stripW), static_cast<float>(stripH));
|
||||
Tab().ViewMin() = wideMin;
|
||||
Tab().ViewMax() = wideMin + static_cast<double>(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<int>(index), m_layerFront[index], 0, 0, wideMin))
|
||||
if (!RenderLaneLayerRegion(static_cast<int>(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<int>(
|
||||
std::llround((layer.anchorYMax - m_lanes[index].yMax) * layer.pxPerValue));
|
||||
}
|
||||
const int topRow = layer.marginY + vOffset;
|
||||
const bool verticalTooFar = (topRow < 0) ||
|
||||
(topRow + static_cast<int>(std::ceil(laneHeight)) > layer.height);
|
||||
const int lanePixels = static_cast<int>(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<double>(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<float>* 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<float>* 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<std::uint64_t>(centerItem != nullptr ? centerItem->points + 2 : 0));
|
||||
mix(static_cast<std::uint64_t>(centerItem != nullptr ? centerItem->iterations + 2 : 0));
|
||||
// 弱峰增强的逐信号参数(增强等级 % + 迭代次数 + 该信号的滑块上限)直接显示在面板上:
|
||||
// 弱峰调整的逐信号参数(增强等级 % + 迭代次数 + 该信号的滑块上限)直接显示在面板上:
|
||||
// 上限决定迭代次数滑块的圆钮位置,因此也必须参与缓存键。
|
||||
const Lane::WeakPeakItem* peakItem = lane.FindWeakPeakItem(ref);
|
||||
mix(static_cast<std::uint64_t>(peakItem != nullptr ? peakItem->levelPercent + 2 : 0));
|
||||
mix(static_cast<std::uint64_t>(peakItem != nullptr ? peakItem->amountPercent + 2 : 0));
|
||||
mix(static_cast<std::uint64_t>(peakItem != nullptr ? peakItem->iterations + 2 : 0));
|
||||
mix(static_cast<std::uint64_t>(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<PanelSlot> nextSlots;
|
||||
std::vector<gd::RectF> 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<int>(index)) &&
|
||||
(m_peakDragRef == ref);
|
||||
const double levelRatio =
|
||||
static_cast<double>(std::max(0, std::min(kWeakPeakPercentMax,
|
||||
peakItem->levelPercent))) /
|
||||
(100.0 * kWeakPeakLevelMax);
|
||||
(static_cast<double>(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,
|
||||
|
||||
Reference in new issue
Block a user