「阶数 N」改称「迭代次数 N」;修复大文件放大后的平移卡顿与越界三角样式
一、命名(用户要求) - 实验性功能里的「阶数 N」全部改为「迭代次数 N」(面板滑块标签、均值滤波/中心轴 的数值框标签、构建提示语)。 - 相关标识符与函数参数统一改名:order → iterations(Lane::MeanFilterItem::iterations、 Lane::weakBoostIterations、ComputeWeakWaveBoost/ComputeMovingAverageIterated 的形参、 MeanFilterCurveFor/WeakBoostCurveFor/WeakBoostValueAt/EdgeCenterCountsFor、 各曲线缓存项的字段、EdgePanelLayout::rowIterations/sliderIterations、 PlotEditTarget::MEAN_FILTER_ITERATIONS/EDGE_CENTER_ITERATIONS、 PlotDragMode::BOOST_ITERATIONS、kWeakBoostIterationsMin/Max/Label、 DerivedSignalRequest::iterations 等)。网格拖拽重排里的局部 order 为避免歧义 改名为 laneOrder;`std::memory_order_*`、`rowOrder`→`rowIterations` 一并核对无误。 - 导出信号尾缀统一为「N次迭代」: `<原信号> (N点均值 M次迭代)`、`<原信号> (弱波增强 X% M次迭代)`; 弱波增强此前缺失该信息,现已补上(两种派生信号都恒定带迭代次数)。 状态提示语同步为「… M 次迭代信号。」。 二、越界三角与光标读数的样式统一(用户要求) - 新增 `kMarkerTriangleGap`:网格内数据点越界时,方向三角与数值标注之间**留出间隙** (此前三角与胶囊贴在一起,看起来像胶囊上多出的尖角);中心轴/均值滤波/弱波增强的 派生取值标注同样处理。 - 光标时间读数(以及双光标 Δt 读数)改用与网格内数据点**完全一致**的越界样式: 三角画在胶囊**外侧**、尖端朝外、与胶囊之间留出同样的间隙;为此把读数胶囊在越界一侧 向内让出「三角深度 + 间隙」。旧的"三角压在胶囊上沿"的画法以及为它额外留出的叠放 间距(kCursorTipStackGap)一并删除。 三、大文件放大后"平移 / 改配色非常卡"的根因与修复(用户要求,流畅性优先) 用离屏探针(`.preflight/perf3`)在 VERY_BIG_DATA(1.77GB、2580 万采样 × 5 信号、 1224×796 绘图区)上先量化,实测到的三个根因: 1. **包络带"梳状化"**:当信号的振荡周期与像素列宽度相当时(编码器方波在 2ms/大格下 约 3 个采样点一列),逐列包络会在"全高"与"极窄"之间交替——实测 1224 列里有 988 处 全高跳变。这种多边形让 GDI+ 的扫描线填充耗时暴涨:同一条 2448 顶点的路径 **27.6ms → 1.1ms(±1 列极值扩展后快 25 倍)**。这正是"某些放大等级下平移特别卡" (实测 2ms/大格平移 87ms,其余等级 17~20ms)的原因。 → `EmitEnvelopeBand` 在拼装包络带时对列窗口做 ±1 列的极值扩展(纵向最多各多 1 像素, 肉眼不可辨),一次性消除该病理。 2. **派生预览曲线按折线逐段描边**:均值滤波/弱波增强曲线在可见采样数超过像素列数时 仍按"每 2 像素一个顶点"的抗锯齿折线绘制,而这类曲线常叠在方波上(大量近垂直跳变), 实测单条 12~42ms,5 个网格合计上百毫秒。 → 稠密时改用**逐像素列的包络带**(复用 `EmitEnvelopeBand`)一次 FillPath,实测 0.56ms / 1.53ms(分别快 22 倍 / 28 倍),视觉与细折线一致(列宽就是 1 像素)。 3. **悬停光晕用宽笔描整条折线**:实测悬停一帧多花 35ms。 → 包络带模式下改为"把带整体外扩后填充一次"(`PaintBandHalo`),悬停帧恢复正常。 另外修掉两个相关的固定开销: - `UiMeasureTextWidth` 增加 (字体, 文本) 记忆化缓存:它是布局代码的热点(浮动面板几何 按「网格数 × 面板种类」重复计算,每帧重复测量同一批字符串成百上千次),实测单个面板 的绘制因此从 3.2ms 降到 1ms 量级。字体重建(DPI 变化)时由 `UiClearMeasureCache` 作废。 实测(VERY_BIG_DATA 全选 5 信号,绘图区全重绘 = 平移一帧): - 20ms/大格(用户报告的场景):平移 20.1ms → **11.3ms**; - 2ms/大格(此前最糟):平移 86.7ms → **12.3ms**; - 网格 1 开弱波增强:63.9ms → **17.6ms**;5 个网格全开:136.6ms → **31.9ms**; - 悬停信号:整帧 49.6ms → **15.5ms**(与不悬停持平,光晕几乎免费)。 仍存的已知热点:0.5ms/大格附近(可见约 1000 个采样点、走**逐点描边**路径)平移约 28~39ms —— 该区间采样比像素还稀疏、无法用包络带替代,且用户明确要求保留抗锯齿, 本次未动。 四、验证 - 离屏 harness(窗口 SW_HIDE、不占用鼠标/键盘/屏幕)**355 项检查全部通过**, 新增 `[AD]`:越界读数胶囊向内让位、三角在外侧、三角与胶囊间有真实间隙、 胶囊上方不再有内容;取值标注与三角之间保留 `kMarkerTriangleGap` 的间隙(用 散点模式排除波形干扰后逐像素断言)。`[X]/[Y]/[AA]/[AB]/[Z]` 等既有用例同步通过。 - 性能结论来自 `.preflight/perf3`(离屏探针 + 两套微基准),已随任务清理。
This commit is contained in:
1 parent
8c8508e5f8
commit
b960b03cb5
8 files changed
+666
-301
No files matched your search
@@ -241,7 +241,7 @@ namespace jdi
|
||||
/**
|
||||
* @brief 应用绘图区提交的「实验性功能 → 构建为新信号」请求。
|
||||
*
|
||||
* 为指定网格内的每条被勾选信号各构建一条派生信号(多阶均值滤波或弱波增强),
|
||||
* 为指定网格内的每条被勾选信号各构建一条派生信号(多次迭代均值滤波或弱波增强),
|
||||
* 命名为 `<原信号名称> (N点均值…)` / `<原信号名称> (弱波增强 M%)`,
|
||||
* 插入到原信号所在组的原信号位置**下方**,并把新信号所在的网格移动到原信号网格的下方。
|
||||
*
|
||||
@@ -250,20 +250,20 @@ namespace jdi
|
||||
bool ApplyPendingDerivedBuild();
|
||||
|
||||
/**
|
||||
* @brief 派生信号构建请求(多阶均值滤波 / 弱波增强共用同一套插入流程)。
|
||||
* @brief 派生信号构建请求(多次迭代均值滤波 / 弱波增强共用同一套插入流程)。
|
||||
*/
|
||||
struct DerivedSignalRequest
|
||||
{
|
||||
int fileIndex = -1; /**< 原信号所属文件下标。 */
|
||||
int signalIndex = -1; /**< 原信号下标。 */
|
||||
int points = 5; /**< 多阶均值滤波:均值点数 X(滑平半径)。 */
|
||||
int order = 1; /**< 多阶均值滤波:阶数 N(迭代次数)。 */
|
||||
int points = 5; /**< 多次迭代均值滤波:均值点数 X(滑平半径)。 */
|
||||
int iterations = 1; /**< 多次迭代均值滤波:迭代次数 N(至少 1)。 */
|
||||
int levelPercent = 50; /**< 弱波增强:增强等级(0–100)。 */
|
||||
bool weakBoost = false; /**< true = 弱波增强,false = 多阶均值滤波。 */
|
||||
bool weakBoost = false; /**< true = 弱波增强,false = 多次迭代均值滤波。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 把一条派生信号(多阶均值滤波 / 弱波增强)插入数据层并同步各视图。
|
||||
* @brief 把一条派生信号(多次迭代均值滤波 / 弱波增强)插入数据层并同步各视图。
|
||||
* @param [IN] request 构建请求。
|
||||
* @param [IN] makeVisible 是否在当前标签页中勾选新信号。
|
||||
* @return 构建并插入成功返回 true。
|
||||
|
||||
@@ -1473,30 +1473,22 @@ namespace jdi
|
||||
const std::wstring baseName = source.Title();
|
||||
|
||||
// 1. 计算派生采样值(与网格内的预览曲线共用同一实现,因此逐位一致)。
|
||||
// 多阶均值滤波形如「<原信号名称> (N点均值×M阶)」,
|
||||
// 弱波增强形如「<原信号名称> (弱波增强 M%)」。
|
||||
// 多次迭代均值滤波形如「<原信号名称> (N点均值 M次迭代)」,
|
||||
// 弱波增强形如「<原信号名称> (弱波增强 M% N次迭代)」。
|
||||
std::vector<float> derived_values;
|
||||
SignalData derived;
|
||||
if (request.weakBoost)
|
||||
{
|
||||
ComputeWeakWaveBoost(source.values, static_cast<double>(request.levelPercent) / 100.0,
|
||||
request.order, derived_values);
|
||||
derived.name = baseName + L" (弱波增强 " + std::to_wstring(request.levelPercent) + L"%";
|
||||
if (request.order > 1)
|
||||
{
|
||||
derived.name += L"×" + std::to_wstring(request.order) + L"阶";
|
||||
}
|
||||
derived.name += L")";
|
||||
request.iterations, derived_values);
|
||||
derived.name = baseName + L" (弱波增强 " + std::to_wstring(request.levelPercent) + L"% " +
|
||||
std::to_wstring(request.iterations) + L"次迭代)";
|
||||
}
|
||||
else
|
||||
{
|
||||
ComputeMovingAverageIterated(source.values, request.points, request.order, derived_values);
|
||||
derived.name = baseName + L" (" + std::to_wstring(request.points) + L"点均值";
|
||||
if (request.order > 1)
|
||||
{
|
||||
derived.name += L"×" + std::to_wstring(request.order) + L"阶";
|
||||
}
|
||||
derived.name += L")";
|
||||
ComputeMovingAverageIterated(source.values, request.points, request.iterations, derived_values);
|
||||
derived.name = baseName + L" (" + std::to_wstring(request.points) + L"点均值 " +
|
||||
std::to_wstring(request.iterations) + L"次迭代)";
|
||||
}
|
||||
|
||||
// 2. 组装新信号。
|
||||
@@ -1551,7 +1543,7 @@ namespace jdi
|
||||
request.fileIndex = item.fileIndex;
|
||||
request.signalIndex = item.signalIndex;
|
||||
request.points = item.points;
|
||||
request.order = item.order;
|
||||
request.iterations = item.iterations;
|
||||
request.weakBoost = false;
|
||||
requests.push_back(request);
|
||||
}
|
||||
@@ -1568,7 +1560,7 @@ namespace jdi
|
||||
request.fileIndex = item.fileIndex;
|
||||
request.signalIndex = item.signalIndex;
|
||||
request.levelPercent = item.levelPercent;
|
||||
request.order = item.order;
|
||||
request.iterations = item.iterations;
|
||||
request.weakBoost = true;
|
||||
requests.push_back(request);
|
||||
}
|
||||
@@ -1611,17 +1603,13 @@ namespace jdi
|
||||
std::wstring text = L"已构建 " + std::to_wstring(built) + L" 条 ";
|
||||
if (last.weakBoost)
|
||||
{
|
||||
text += L"弱波增强 " + std::to_wstring(last.levelPercent) + L"%" +
|
||||
std::to_wstring(last.order) + L"阶信号。";
|
||||
text += L"弱波增强 " + std::to_wstring(last.levelPercent) + L"% " +
|
||||
std::to_wstring(last.iterations) + L"次迭代信号。";
|
||||
}
|
||||
else
|
||||
{
|
||||
text += std::to_wstring(last.points) + L"点均值";
|
||||
if (last.order > 1)
|
||||
{
|
||||
text += L"×" + std::to_wstring(last.order) + L"阶";
|
||||
}
|
||||
text += L"滤波信号。";
|
||||
text += std::to_wstring(last.points) + L"点均值 " +
|
||||
std::to_wstring(last.iterations) + L"次迭代滤波信号。";
|
||||
}
|
||||
SetStatusMessage(text, false);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace jdi
|
||||
EDGE_MOVE, /**< 拖动「边沿计数」的横向光标(上下移动幅值)。 */
|
||||
EDGE_PANEL_MOVE, /**< 拖动边沿计数浮动面板。 */
|
||||
BOOST_LEVEL, /**< 拖动「弱波增强」面板中的等级滑块。 */
|
||||
BOOST_ORDER, /**< 拖动「弱波增强」面板中的阶数 N 滑块。 */
|
||||
BOOST_ITERATIONS, /**< 拖动「弱波增强」面板中的迭代次数 N 滑块。 */
|
||||
LANE_REORDER /**< 拖动网格图例中的 GX 编号徽标:上下调整网格次序。 */
|
||||
};
|
||||
|
||||
@@ -57,9 +57,9 @@ namespace jdi
|
||||
LANE_Y_MIN, /**< 编辑某网格的 Y 轴最小值。 */
|
||||
SIGNAL_NAME, /**< 编辑图例中某信号的显示名称。 */
|
||||
EDGE_CENTER_POINTS, /**< 编辑「中心轴边沿计数」的均值点数 X。 */
|
||||
EDGE_CENTER_ORDER, /**< 编辑「中心轴边沿计数」的阶数 N。 */
|
||||
EDGE_CENTER_ITERATIONS, /**< 编辑「中心轴边沿计数」的迭代次数 N。 */
|
||||
MEAN_FILTER_POINTS, /**< 编辑「均值滤波」的均值点数 X。 */
|
||||
MEAN_FILTER_ORDER /**< 编辑「均值滤波」的阶数 N。 */
|
||||
MEAN_FILTER_ITERATIONS /**< 编辑「均值滤波」的迭代次数 N。 */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -507,7 +507,7 @@ namespace jdi
|
||||
int fileIndex = -1; /**< 信号所属文件下标。 */
|
||||
int signalIndex = -1; /**< 文件内信号下标。 */
|
||||
int points = 5; /**< 均值点数 X(滑平半径)。 */
|
||||
int order = 1; /**< 阶数 N(迭代次数)。 */
|
||||
int iterations = 1; /**< 迭代次数 N(至少 1)。 */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -531,7 +531,7 @@ namespace jdi
|
||||
int fileIndex = -1; /**< 信号所属文件下标。 */
|
||||
int signalIndex = -1; /**< 文件内信号下标。 */
|
||||
int levelPercent = 50; /**< 弱波增强等级(0–100)。 */
|
||||
int order = 1; /**< 阶数 N(1–10)。 */
|
||||
int iterations = 1; /**< 迭代次数 N(1–10)。 */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -572,19 +572,19 @@ namespace jdi
|
||||
double LaneWeakBoostLevel(int laneIndex) const;
|
||||
|
||||
/**
|
||||
* @brief 读取某网格的弱波增强阶数 N(1–10)。
|
||||
* @brief 读取某网格的弱波增强迭代次数 N(1–10)。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @return 阶数;网格不存在时返回 1。
|
||||
* @return 迭代次数;网格不存在时返回 1。
|
||||
*/
|
||||
int LaneWeakBoostOrder(int laneIndex) const;
|
||||
int LaneWeakBoostIterations(int laneIndex) const;
|
||||
|
||||
/**
|
||||
* @brief 设置某网格的弱波增强阶数 N(1–10,超出范围会被夹取)。
|
||||
* @brief 设置某网格的弱波增强迭代次数 N(1–10,超出范围会被夹取)。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] order 阶数。
|
||||
* @return 阶数发生变化返回 true。
|
||||
* @param [IN] iterations 迭代次数。
|
||||
* @return 迭代次数发生变化返回 true。
|
||||
*/
|
||||
bool SetLaneWeakBoostOrder(int laneIndex, int order);
|
||||
bool SetLaneWeakBoostIterations(int laneIndex, int iterations);
|
||||
|
||||
/**
|
||||
* @brief 设置某网格的弱波增强等级(0–1,超出范围会被夹取)。
|
||||
@@ -595,26 +595,26 @@ namespace jdi
|
||||
bool SetLaneWeakBoostLevel(int laneIndex, double level);
|
||||
|
||||
/**
|
||||
* @brief 获取弱波增强曲线(按「信号 + 增强等级 + 阶数」缓存)。
|
||||
* @brief 获取弱波增强曲线(按「信号 + 增强等级 + 迭代次数」缓存)。
|
||||
* @param [IN] signal 目标信号。
|
||||
* @param [IN] levelPercent 弱波增强等级(0–100,整数百分比)。
|
||||
* @param [IN] order 阶数 N(1–10)。
|
||||
* @param [IN] iterations 迭代次数 N(1–10)。
|
||||
* @return 曲线数值数组(长度与 `signal.values` 相同;缺失处为 NaN)。
|
||||
*/
|
||||
const std::vector<float>& WeakBoostCurveFor(const SignalData& signal, int levelPercent,
|
||||
int order) const;
|
||||
int iterations) const;
|
||||
|
||||
/**
|
||||
* @brief 取某个时刻的弱波增强曲线取值(用于纵向光标的取值标注)。
|
||||
* @param [IN] file 信号所属文件。
|
||||
* @param [IN] signal 目标信号。
|
||||
* @param [IN] levelPercent 弱波增强等级(0–100)。
|
||||
* @param [IN] order 阶数 N(1–10)。
|
||||
* @param [IN] iterations 迭代次数 N(1–10)。
|
||||
* @param [IN] time 采样时刻。
|
||||
* @param [OUT] value 输出取值。
|
||||
* @return 取到有效值返回 true。
|
||||
*/
|
||||
bool WeakBoostValueAt(const DataFile& file, const SignalData& signal, int levelPercent, int order,
|
||||
bool WeakBoostValueAt(const DataFile& file, const SignalData& signal, int levelPercent, int iterations,
|
||||
double time, double& value) const;
|
||||
|
||||
/**
|
||||
@@ -1084,19 +1084,19 @@ namespace jdi
|
||||
gd::RectF edit; /**< 面板底部编辑行(无编辑内容时宽度为 0)。 */
|
||||
gd::RectF button; /**< 面板底部按钮(无按钮时宽度为 0)。 */
|
||||
gd::RectF slider; /**< 「弱波增强」等级滑块的轨道矩形(无滑块时宽度为 0)。 */
|
||||
gd::RectF sliderOrder; /**< 「弱波增强」阶数 N 滑块的轨道矩形(无滑块时宽度为 0)。 */
|
||||
gd::RectF sliderIterations; /**< 「弱波增强」迭代次数 N 滑块的轨道矩形(无滑块时宽度为 0)。 */
|
||||
int rows = 0; /**< 统计行数(每条可见信号一行)。 */
|
||||
/**
|
||||
* @brief 「逐条目」面板的矩形(MEAN 与 CENTER 面板共用同一套排布)。
|
||||
*
|
||||
* 索引与 `rowRefs` 一一对应(即网格内可见信号的顺序):
|
||||
* `rowChecks` 为第一排右侧的复选框;`rowPoints` / `rowOrder` 为第二排的两个输入框;
|
||||
* `rowChecks` 为第一排右侧的复选框;`rowPoints` / `rowIterations` 为第二排的两个输入框;
|
||||
* `rowStats` 为第三排的统计结果(中心轴边沿计数专用)。
|
||||
* 未勾选的条目对应的矩形均为空。
|
||||
*/
|
||||
std::vector<gd::RectF> rowChecks;
|
||||
std::vector<gd::RectF> rowPoints;
|
||||
std::vector<gd::RectF> rowOrder;
|
||||
std::vector<gd::RectF> rowIterations;
|
||||
std::vector<gd::RectF> rowStats;
|
||||
std::vector<SignalRef> rowRefs;
|
||||
};
|
||||
@@ -1112,28 +1112,28 @@ namespace jdi
|
||||
/**
|
||||
* @brief 获取多阶(迭代)均值滤波曲线,结果带缓存。
|
||||
*
|
||||
* 语义:以「均值点数 X」作滑平半径,对输入重复做 `order` 次滑动平均。
|
||||
* 语义:以「均值点数 X」作滑平半径,对输入重复做 `iterations` 次滑动平均。
|
||||
* 「实验性功能 → 均值滤波」与「中心轴边沿计数」共用本函数,二者曲线因此逐位一致。
|
||||
*
|
||||
* @param [IN] signal 目标信号。
|
||||
* @param [IN] points 均值点数 X(滑平半径)。
|
||||
* @param [IN] order 阶数 N(迭代次数,至少 1)。
|
||||
* @param [IN] iterations 迭代次数 N(至少 1)。
|
||||
* @return 曲线数值数组(长度与 `signal.values` 相同;缺失处为 NaN)。
|
||||
*/
|
||||
const std::vector<float>& MeanFilterCurveFor(const SignalData& signal, int points,
|
||||
int order) const;
|
||||
int iterations) const;
|
||||
|
||||
/**
|
||||
* @brief 取某个时刻的多阶均值滤波曲线取值(用于纵向光标的取值标注)。
|
||||
* @brief 取某个时刻的多次迭代均值滤波曲线取值(用于纵向光标的取值标注)。
|
||||
* @param [IN] file 信号所属文件。
|
||||
* @param [IN] signal 目标信号。
|
||||
* @param [IN] points 均值点数 X。
|
||||
* @param [IN] order 阶数 N。
|
||||
* @param [IN] iterations 迭代次数 N。
|
||||
* @param [IN] time 采样时刻。
|
||||
* @param [OUT] value 输出取值。
|
||||
* @return 取到有效值返回 true。
|
||||
*/
|
||||
bool MeanFilterValueAt(const DataFile& file, const SignalData& signal, int points, int order,
|
||||
bool MeanFilterValueAt(const DataFile& file, const SignalData& signal, int points, int iterations,
|
||||
double time, double& value) const;
|
||||
|
||||
/**
|
||||
@@ -1155,19 +1155,19 @@ namespace jdi
|
||||
/**
|
||||
* @brief 统计信号穿越“均值滤波曲线”的上/下边沿数量,结果带缓存。
|
||||
*
|
||||
* 曲线由「均值点数 X + 阶数 N」确定,与「实验性功能 → 均值滤波」的预览曲线逐位一致。
|
||||
* 曲线由「均值点数 X + 迭代次数 N」确定,与「实验性功能 → 均值滤波」的预览曲线逐位一致。
|
||||
*
|
||||
* @param [IN] file 信号所属文件。
|
||||
* @param [IN] signal 目标信号。
|
||||
* @param [IN] points 均值点数 X。
|
||||
* @param [IN] order 阶数 N。
|
||||
* @param [IN] iterations 迭代次数 N。
|
||||
* @param [IN] hasRange 是否只统计某个时间区间(双光标模式下为光标区间)。
|
||||
* @param [IN] rangeMin 区间左端点时间。
|
||||
* @param [IN] rangeMax 区间右端点时间。
|
||||
* @param [OUT] rising 上升沿数量(信号从下往上越过均值滤波曲线)。
|
||||
* @param [OUT] falling 下降沿数量(信号从上往下穿过均值滤波曲线)。
|
||||
*/
|
||||
void EdgeCenterCountsFor(const DataFile& file, const SignalData& signal, int points, int order,
|
||||
void EdgeCenterCountsFor(const DataFile& file, const SignalData& signal, int points, int iterations,
|
||||
bool hasRange, double rangeMin, double rangeMax, std::size_t& rising,
|
||||
std::size_t& falling) const;
|
||||
|
||||
@@ -1188,7 +1188,7 @@ namespace jdi
|
||||
bool LaneEdgeCenterSignalEnabled(int laneIndex, const SignalRef& ref) const;
|
||||
|
||||
/**
|
||||
* @brief 绘制一段派生曲线(中心轴均值滤波 / 多阶均值滤波共用同一实现)。
|
||||
* @brief 绘制一段派生曲线(中心轴均值滤波 / 多次迭代均值滤波共用同一实现)。
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
* @param [IN] resolved 信号视图。
|
||||
* @param [IN] lane 网格绘制信息。
|
||||
@@ -1198,15 +1198,15 @@ namespace jdi
|
||||
const std::vector<float>& curve) const;
|
||||
|
||||
/**
|
||||
* @brief 绘制某条信号的多阶均值滤波曲线(与中心平均值曲线同一配色派生规则)。
|
||||
* @brief 绘制某条信号的多次迭代均值滤波曲线(与中心平均值曲线同一配色派生规则)。
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
* @param [IN] resolved 信号视图。
|
||||
* @param [IN] lane 网格绘制信息。
|
||||
* @param [IN] points 均值点数 X(滑平半径)。
|
||||
* @param [IN] order 阶数 N(迭代次数)。
|
||||
* @param [IN] iterations 迭代次数 N(至少 1)。
|
||||
*/
|
||||
void PaintMeanFilterCurve(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
|
||||
int points, int order) const;
|
||||
int points, int iterations) const;
|
||||
|
||||
/**
|
||||
* @brief 绘制某条信号的弱波增强曲线(与均值滤波曲线同一配色派生规则)。
|
||||
@@ -1214,10 +1214,10 @@ namespace jdi
|
||||
* @param [IN] resolved 信号视图。
|
||||
* @param [IN] lane 网格绘制信息。
|
||||
* @param [IN] levelPercent 弱波增强等级(0–100)。
|
||||
* @param [IN] order 阶数 N(1–10)。
|
||||
* @param [IN] iterations 迭代次数 N(1–10)。
|
||||
*/
|
||||
void PaintWeakBoostCurve(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
|
||||
int levelPercent, int order) const;
|
||||
int levelPercent, int iterations) const;
|
||||
|
||||
/**
|
||||
* @brief 数据点标记的位置与"越界方向"(用于决定画圆点还是贴边三角)。
|
||||
@@ -1483,22 +1483,22 @@ namespace jdi
|
||||
};
|
||||
mutable std::vector<EdgeCountEntry> m_edgeCountCache; /**< 边沿统计缓存(纯缓存,逻辑上不影响对象状态)。 */
|
||||
|
||||
/** 多阶均值滤波曲线缓存项(按「信号指针 + 均值点数 + 阶数」标识)。 */
|
||||
/** 多次迭代均值滤波曲线缓存项(按「信号指针 + 均值点数 + 迭代次数」标识)。 */
|
||||
struct MeanCurveEntry
|
||||
{
|
||||
const SignalData* signal = nullptr;
|
||||
int points = 5;
|
||||
int order = 1;
|
||||
int iterations = 1;
|
||||
std::vector<float> values; /**< 与 signal->values 等长;缺失处为 NaN。 */
|
||||
};
|
||||
mutable std::deque<MeanCurveEntry> m_meanCurveCache;
|
||||
|
||||
/** 弱波增强曲线缓存项(按「信号指针 + 增强等级百分比 + 阶数」标识)。 */
|
||||
/** 弱波增强曲线缓存项(按「信号指针 + 增强等级百分比 + 迭代次数」标识)。 */
|
||||
struct WeakBoostCurveEntry
|
||||
{
|
||||
const SignalData* signal = nullptr;
|
||||
int levelPercent = 50; /**< 弱波增强等级(0–100)。 */
|
||||
int order = 1; /**< 阶数 N(1–10)。 */
|
||||
int iterations = 1; /**< 迭代次数 N(1–10)。 */
|
||||
std::vector<float> values; /**< 与 signal->values 等长;缺失处为 NaN。 */
|
||||
};
|
||||
mutable std::deque<WeakBoostCurveEntry> m_weakBoostCurveCache;
|
||||
@@ -1509,7 +1509,7 @@ namespace jdi
|
||||
const DataFile* file = nullptr; /**< 所属数据分组。 */
|
||||
const SignalData* signal = nullptr; /**< 目标信号。 */
|
||||
int points = 5; /**< 均值点数 X。 */
|
||||
int order = 1; /**< 阶数 N。 */
|
||||
int iterations = 1; /**< 迭代次数 N。 */
|
||||
bool hasRange = false; /**< 是否限定在时间区间内统计。 */
|
||||
bool reusable = false; /**< 计数是否完全由 [begin, end) 决定。 */
|
||||
std::size_t begin = 0; /**< 已统计的采样区间起点(含)。 */
|
||||
@@ -1713,7 +1713,16 @@ namespace jdi
|
||||
void PaintLegendHighlight(gd::Graphics& g);
|
||||
|
||||
/**
|
||||
* @brief 绘制某条信号的悬停/拖拽光晕。
|
||||
* @brief 绘制某条信号的悬停/拖拽光晕(按缓存的折线模式选择最省的画法)。
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
* @param [IN] resolved 已解析的信号。
|
||||
* @param [IN] cached 该信号的折线缓存项(包络带模式下会走带外扩填充)。
|
||||
*/
|
||||
void PaintSignalHaloFor(gd::Graphics& g, const ResolvedSignal& resolved,
|
||||
const PolylineCacheEntry& cached);
|
||||
|
||||
/**
|
||||
* @brief 绘制某条信号的悬停/拖拽光晕(折线描边版本)。
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
* @param [IN] resolved 已解析的信号。
|
||||
* @param [IN] points 折线顶点。
|
||||
@@ -1722,6 +1731,20 @@ namespace jdi
|
||||
void PaintSignalHalo(gd::Graphics& g, const ResolvedSignal& resolved,
|
||||
const std::vector<gd::PointF>& points, const std::vector<std::size_t>& breaks);
|
||||
|
||||
/**
|
||||
* @brief 稠密包络带的光晕:把带整体外扩 `extra` 像素后填充一次。
|
||||
*
|
||||
* 比用宽画笔描整条折线快一个数量级(含大量近垂直跳变的信号上尤其明显),
|
||||
* 观感也更均匀。
|
||||
*
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
* @param [IN] band 包络带顶点(上包络正向 + 下包络反向)。
|
||||
* @param [IN] bandBreaks 各段起点。
|
||||
* @param [IN] extra 纵向额外外扩量(像素)。
|
||||
*/
|
||||
void PaintBandHalo(gd::Graphics& g, const std::vector<gd::PointF>& band,
|
||||
const std::vector<std::size_t>& bandBreaks, float extra);
|
||||
|
||||
/**
|
||||
* @brief 计算某网格图例中 GX 编号徽标的矩形(绘制与命中测试共用,保证一致)。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+15
-15
@@ -359,7 +359,7 @@ namespace jdi
|
||||
* @brief 「均值滤波」中某个信号自己的参数。
|
||||
*
|
||||
* 仅参与列表中的信号才会被勾选预览;参数按信号保存,
|
||||
* 因此同一条网格内不同信号可以使用不同的均值点数与阶数。
|
||||
* 因此同一条网格内不同信号可以使用不同的均值点数与迭代次数。
|
||||
* 「边沿计数(中心轴)」的平滑参数与它**完全同构**(同一套命名与作用),
|
||||
* 见 `EdgeCenterItem`。
|
||||
*/
|
||||
@@ -367,7 +367,7 @@ namespace jdi
|
||||
{
|
||||
SignalRef ref; /**< 目标信号。 */
|
||||
int points = 5; /**< 均值点数 X(滑平半径,窗口 = 2X+1)。 */
|
||||
int order = 1; /**< 阶数 N(重复滤波次数,至少 1)。 */
|
||||
int iterations = 1; /**< 迭代次数 N(重复滤波次数,至少 1)。 */
|
||||
};
|
||||
|
||||
/** 中心轴边沿计数的平滑参数:与均值滤波共用同一结构与同一套语义。 */
|
||||
@@ -423,7 +423,7 @@ namespace jdi
|
||||
* @brief 「实验性功能 → 边沿计数(中心轴)」开关。
|
||||
*
|
||||
* 开启后为本网格内**被勾选**的信号额外绘制一条均值滤波曲线(与「实验性功能 → 均值滤波」
|
||||
* 用的是同一套参数:均值点数 X + 阶数 N),并按“信号穿越该滤波曲线”统计上/下边沿。
|
||||
* 用的是同一套参数:均值点数 X + 迭代次数 N),并按“信号穿越该滤波曲线”统计上/下边沿。
|
||||
* 这条曲线只是叠加显示,不会作为信号加入左侧列表。
|
||||
*/
|
||||
bool edgeCenter = false;
|
||||
@@ -432,7 +432,7 @@ namespace jdi
|
||||
* @brief 参与中心轴边沿计数的信号及其平滑参数(只包含被勾选的信号)。
|
||||
*
|
||||
* 参数语义与 `meanFilterItems` **完全一致**(`points` = 均值点数 X,
|
||||
* `order` = 阶数 N),因此两条曲线的数值逐位相同。
|
||||
* `iterations` = 迭代次数 N),因此两条曲线的数值逐位相同。
|
||||
*/
|
||||
std::vector<EdgeCenterItem> edgeCenterItems;
|
||||
|
||||
@@ -463,7 +463,7 @@ namespace jdi
|
||||
/**
|
||||
* @brief 「实验性功能 → 均值滤波」开关。
|
||||
*
|
||||
* 开启后为本网格内**被勾选**的信号额外绘制一条多阶滑动平均曲线(按各自的均值点数 X
|
||||
* 开启后为本网格内**被勾选**的信号额外绘制一条多次迭代滑动平均曲线(按各自的均值点数 X
|
||||
* 重复 N 阶),并在浮动面板中提供每个信号自己的参数与「构建为新信号」按钮。
|
||||
* 与其它实验性功能一样属于网格自身状态,重建网格时按信号继承。
|
||||
*/
|
||||
@@ -514,13 +514,13 @@ namespace jdi
|
||||
double weakBoostLevel = 0.5;
|
||||
|
||||
/**
|
||||
* @brief 弱波增强的阶数 N(1–10)。
|
||||
* @brief 弱波增强的迭代次数 N(1–10)。
|
||||
*
|
||||
* 与「均值滤波」的阶数 N 一样表示“把同一处理重复应用 N 次”(每一阶以上一阶为输入):
|
||||
* 与「均值滤波」的迭代次数 N 一样表示“把同一处理重复应用 N 次”(每一阶以上一阶为输入):
|
||||
* 一阶时改定量就已严格等于「等级」对应的目标值;多阶会把相邻弱波互相抬高
|
||||
* 彼此的基准,从而把较宽的弱波区逐步恢复。
|
||||
*/
|
||||
int weakBoostOrder = 1;
|
||||
int weakBoostIterations = 1;
|
||||
|
||||
/** 参与弱波增强预览的信号条目(只包含被勾选的信号)。 */
|
||||
std::vector<WeakBoostItem> weakBoostItems;
|
||||
@@ -578,17 +578,17 @@ namespace jdi
|
||||
void ComputeMovingAverage(const std::vector<double>& values, int radius, std::vector<float>& out);
|
||||
|
||||
/**
|
||||
* @brief 计算多阶(迭代)滑动平均:把 N 点滑动平均重复应用 `order` 次。
|
||||
* @brief 计算多次迭代滑动平均:把 N 点滑动平均重复应用 `iterations` 次。
|
||||
*
|
||||
* 每次迭代以上一次的结果为输入(NaN 不参与平均),因此阶数越高曲线越平滑。
|
||||
* `order <= 1` 时与 `ComputeMovingAverage` 完全一致。
|
||||
* 每次迭代以上一次的结果为输入(NaN 不参与平均),因此迭代次数越高曲线越平滑。
|
||||
* `iterations <= 1` 时与 `ComputeMovingAverage` 完全一致。
|
||||
*
|
||||
* @param [IN] values 原始采样值。
|
||||
* @param [IN] radius 滑平半径 N(0 表示原样返回)。
|
||||
* @param [IN] order 阶数(迭代次数,至少为 1)。
|
||||
* @param [IN] iterations 迭代次数(至少为 1)。
|
||||
* @param [OUT] out 输出数组(长度与 values 相同)。
|
||||
*/
|
||||
void ComputeMovingAverageIterated(const std::vector<double>& values, int radius, int order,
|
||||
void ComputeMovingAverageIterated(const std::vector<double>& values, int radius, int iterations,
|
||||
std::vector<float>& out);
|
||||
|
||||
/**
|
||||
@@ -614,10 +614,10 @@ namespace jdi
|
||||
*
|
||||
* @param [IN] values 原始采样值。
|
||||
* @param [IN] level 弱波增强等级(0–1,超出范围会被夹取)。
|
||||
* @param [IN] order 阶数 N(重复应用次数,至少为 1;与均值滤波的阶数同一语义)。
|
||||
* @param [IN] iterations 迭代次数 N(重复应用次数,至少为 1)。
|
||||
* @param [OUT] out 输出数组(长度与 values 相同;原值为 NaN 处保持 NaN)。
|
||||
*/
|
||||
void ComputeWeakWaveBoost(const std::vector<double>& values, double level, int order,
|
||||
void ComputeWeakWaveBoost(const std::vector<double>& values, double level, int iterations,
|
||||
std::vector<float>& out);
|
||||
|
||||
/**
|
||||
|
||||
@@ -256,6 +256,15 @@ namespace jdi
|
||||
*/
|
||||
std::uint32_t UiBlendOver(std::uint32_t background, std::uint32_t argb);
|
||||
|
||||
/**
|
||||
* @brief 作废「文本宽度测量」的记忆化缓存。
|
||||
*
|
||||
* `UiMeasureTextWidth` 内部按 (字体句柄, 文本) 缓存测量结果以摊薄 GDI 调用开销;
|
||||
* 字体句柄被重建(DPI 变化等)时必须调用本函数,否则系统复用的同一句柄值会命中
|
||||
* 旧宽度,造成布局错位。
|
||||
*/
|
||||
void UiClearMeasureCache();
|
||||
|
||||
/**
|
||||
* @brief 一次性填充多个圆角矩形(同色同圆角)。
|
||||
*
|
||||
|
||||
+11
-11
@@ -137,7 +137,7 @@ namespace jdi
|
||||
bool weakBoost = false;
|
||||
std::vector<Lane::WeakBoostItem> weakBoostItems;
|
||||
double weakBoostLevel = 0.5;
|
||||
int weakBoostOrder = 1;
|
||||
int weakBoostIterations = 1;
|
||||
bool weakBoostPanelOpen = true;
|
||||
double weakBoostPanelX = -1.0;
|
||||
double weakBoostPanelY = -1.0;
|
||||
@@ -169,7 +169,7 @@ namespace jdi
|
||||
carry.weakBoost = lane.weakBoost;
|
||||
carry.weakBoostItems = lane.weakBoostItems;
|
||||
carry.weakBoostLevel = lane.weakBoostLevel;
|
||||
carry.weakBoostOrder = lane.weakBoostOrder;
|
||||
carry.weakBoostIterations = lane.weakBoostIterations;
|
||||
carry.weakBoostPanelOpen = lane.weakBoostPanelOpen;
|
||||
carry.weakBoostPanelX = lane.weakBoostPanelX;
|
||||
carry.weakBoostPanelY = lane.weakBoostPanelY;
|
||||
@@ -212,7 +212,7 @@ namespace jdi
|
||||
lane.weakBoost = carry.weakBoost;
|
||||
lane.weakBoostItems = carry.weakBoostItems;
|
||||
lane.weakBoostLevel = carry.weakBoostLevel;
|
||||
lane.weakBoostOrder = carry.weakBoostOrder;
|
||||
lane.weakBoostIterations = carry.weakBoostIterations;
|
||||
lane.weakBoostPanelOpen = carry.weakBoostPanelOpen;
|
||||
lane.weakBoostPanelX = carry.weakBoostPanelX;
|
||||
lane.weakBoostPanelY = carry.weakBoostPanelY;
|
||||
@@ -1020,10 +1020,10 @@ namespace jdi
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ComputeMovingAverageIterated(const std::vector<double>& values, int radius, int order,
|
||||
void ComputeMovingAverageIterated(const std::vector<double>& values, int radius, int iterations,
|
||||
std::vector<float>& out)
|
||||
{
|
||||
const int stages = std::max(1, order);
|
||||
const int stages = std::max(1, iterations);
|
||||
// 第 1 阶:从原始采样值出发。
|
||||
ComputeMovingAverage(values, radius, out);
|
||||
if (stages == 1)
|
||||
@@ -1230,7 +1230,7 @@ namespace jdi
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ComputeWeakWaveBoost(const std::vector<double>& values, double level, int order,
|
||||
void ComputeWeakWaveBoost(const std::vector<double>& values, double level, int iterations,
|
||||
std::vector<float>& out)
|
||||
{
|
||||
const std::size_t count = values.size();
|
||||
@@ -1251,17 +1251,17 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
|
||||
// 「阶数 N」与「均值滤波」的阶数语义一致:把同一处理**重复应用 N 次**,
|
||||
// 「迭代次数 N」与「均值滤波」的迭代次数语义一致:把同一处理**重复应用 N 次**,
|
||||
// 每一阶以上一阶的结果为输入。一阶时改定量已严格等于「等级」对应的目标值;
|
||||
// 多阶时相邻的弱波会互相抬高彼此的基准,从而把较宽的弱波区逐步恢复。
|
||||
const int stages = std::max(1, std::min(order, 1000));
|
||||
// 多次迭代时相邻的弱波会互相抬高彼此的基准,从而把较宽的弱波区逐步恢复。
|
||||
const int stages = std::max(1, std::min(iterations, 1000));
|
||||
const double strength = std::max(0.0, std::min(1.0, level));
|
||||
std::vector<float> next;
|
||||
for (int stage = 0; stage < stages; ++stage)
|
||||
{
|
||||
if (!ApplyWeakBoostStage(current, strength, next))
|
||||
{
|
||||
// 这一阶没有任何采样点需要调整:后续阶数也不会再有变化,提前结束。
|
||||
// 这一阶没有任何采样点需要调整:后续迭代次数也不会再有变化,提前结束。
|
||||
break;
|
||||
}
|
||||
current.swap(next);
|
||||
@@ -1912,7 +1912,7 @@ namespace jdi
|
||||
lane.edgeCenterPanelY = source.edgeCenterPanelY;
|
||||
lane.weakBoost = source.weakBoost;
|
||||
lane.weakBoostLevel = source.weakBoostLevel;
|
||||
lane.weakBoostOrder = source.weakBoostOrder;
|
||||
lane.weakBoostIterations = source.weakBoostIterations;
|
||||
lane.weakBoostPanelOpen = source.weakBoostPanelOpen;
|
||||
lane.weakBoostPanelX = source.weakBoostPanelX;
|
||||
lane.weakBoostPanelY = source.weakBoostPanelY;
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <string>
|
||||
|
||||
namespace jdi
|
||||
{
|
||||
@@ -89,11 +95,52 @@ namespace jdi
|
||||
}
|
||||
return font;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace
|
||||
{
|
||||
/**
|
||||
* @brief 文本宽度测量的缓存键(字体句柄 + 文本)。
|
||||
*/
|
||||
struct MeasureKey
|
||||
{
|
||||
HFONT font = nullptr;
|
||||
std::wstring text;
|
||||
bool operator==(const MeasureKey& other) const
|
||||
{
|
||||
return font == other.font && text == other.text;
|
||||
}
|
||||
};
|
||||
struct MeasureKeyHash
|
||||
{
|
||||
std::size_t operator()(const MeasureKey& key) const
|
||||
{
|
||||
std::size_t hash = std::hash<const void*>()(key.font);
|
||||
for (const wchar_t character : key.text)
|
||||
{
|
||||
hash = hash * 131u + static_cast<std::size_t>(character);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
/** 上限保护:超过时整体清空(缓存只是加速手段,清空后重新填充即可)。 */
|
||||
constexpr std::size_t kMeasureCacheLimit = 4096;
|
||||
std::unordered_map<MeasureKey, float, MeasureKeyHash> g_measureCache;
|
||||
std::mutex g_measureMutex;
|
||||
} // namespace
|
||||
|
||||
void UiClearMeasureCache()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_measureMutex);
|
||||
g_measureCache.clear();
|
||||
}
|
||||
|
||||
bool UiFonts::Create(UINT targetDpi)
|
||||
{
|
||||
Destroy();
|
||||
// 字体句柄会被重新创建,旧句柄可能被系统复用:必须作废按句柄缓存的文本宽度,
|
||||
// 否则复用同一句柄值的新字体会命中旧宽度(布局错位)。
|
||||
UiClearMeasureCache();
|
||||
dpi = (targetDpi == 0) ? 96u : targetDpi;
|
||||
|
||||
const double scale = static_cast<double>(dpi) / 96.0;
|
||||
@@ -359,6 +406,21 @@ namespace jdi
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// 记忆化:文本宽度只能走 GDI(GetDC + GetTextExtentPoint32W + ReleaseDC),
|
||||
// 单次约 10µs;而布局代码每帧会把同一批字符串**重复测量成百上千次**——
|
||||
// 例如浮动面板的几何是按「网格数 × 面板种类」反复重算的(5 网格 × 4 类 = 20 次,
|
||||
// 每次十来个字符串),实测光这一项就占 3ms 以上。字体与文本都相同的测量结果
|
||||
// 必然一致,因此按 (字体, 文本) 做一层缓存;字体被重建时由 `UiClearMeasureCache` 作废。
|
||||
const MeasureKey key{font, text};
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_measureMutex);
|
||||
const auto found = g_measureCache.find(key);
|
||||
if (found != g_measureCache.end())
|
||||
{
|
||||
return found->second;
|
||||
}
|
||||
}
|
||||
|
||||
HDC hdc = GetDC(nullptr);
|
||||
if (hdc == nullptr)
|
||||
{
|
||||
@@ -371,7 +433,16 @@ namespace jdi
|
||||
SelectObject(hdc, oldFont);
|
||||
ReleaseDC(nullptr, hdc);
|
||||
|
||||
return static_cast<float>(size.cx);
|
||||
const float width = static_cast<float>(size.cx);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_measureMutex);
|
||||
if (g_measureCache.size() >= kMeasureCacheLimit)
|
||||
{
|
||||
g_measureCache.clear();
|
||||
}
|
||||
g_measureCache.emplace(key, width);
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
std::wstring UiEllipsize(const std::wstring& text, HFONT font, float maxWidth)
|
||||
|
||||
Reference in new issue
Block a user