修复上下拖动后的绘制错误(图层条带 GDI 裁剪)+「弱峰调整」定义/默认值/符号显示
一、绘制错误真因:图层条带渲染时 GDI 网格线越界作画并烘进图层 上一轮把图层条带从"只有横向条"扩展到"横向条 + 纵向条"(纵向滚动补画)。但 PaintLane 里 GDI (网格线/分界线)的裁剪矩形 RectF(m_area.X, clipTop, m_area.Width, clipBottom - clipTop) 只把 x 限制在条带内、y 仍是整个网格可见范围。纵向条带(全宽 × 窄 y)渲染时: * GDI 网格线被画到条带之外(那儿的像素由滚动而来、本来是正确的); * 那些行原本是"波形盖在网格线之上",变成"网格线盖在波形之上",与正确渲染不一致; * 错误像素就在图层位图里,会被后续每次平移带着移动 → "拖到哪儿都有绘制错误"。 修法:把"本次允许改动的区域"算成 laneClip = IntersectRect(m_area, 网格可见矩形),同时用于 Graphics::SetClip 与 GDI 的 gridClip(横向补条本来就没问题,所以此前只有纵向/斜向暴露)。 二、新增探针 .preflight/dragcheck(复现用户手势) 注入真实中键消息:先把最上方网格向上拖半个网格高(触发纵向滚动),再保持按住拖左右/上下/斜向、 最后拖出窗口;每帧把图层画布的有效条带(marginY + vOffset 起、网格高行)与"用同一 wideMin 新渲染 的画布"逐像素比较(滚动是纯整数平移,必须逐像素相同;用 FromHBITMAP + LockBits 直接读像素)。 修复前:被拖网格整行不一致(2410 px ≈ 2 行全宽,错误行固定在 canvas 行 138 = 某条横向网格线, 直接指向 GDI 网格线);修复后:最差 ≤171 px(仅补条边界的抗锯齿边缘),0 处绘制错误。 三、「弱峰调整」定义 "是否偏弱"改为(正/负幅度一致):波峰必须同时低于左右两侧各自的「相邻波峰 + 相邻波谷」均值; 波谷必须同时高于上述两个均值。与旧实现(只与左右两个同类极值比较)不同:整段均匀偏弱的纹波 不再被判为弱峰,因此合成测试波形重设计(分段线性、谷恒 500、弱峰 520、健康峰 620/640)。 四、默认幅度与符号显示 默认幅度改为 0%(面板提出即不改动波形);滑块右侧数值始终显示符号(+150% / -150% / 0%), 数值列按 "+200%" 取宽避免跳动。 五、验证(全部离屏,不占用鼠标/键盘/屏幕) - uitest 441/441:[X] 正幅度 0/50/100/150/200% → 峰 520/570/620/630/640、弱谷 580/540/500/500/500; 负幅度 -50/-100/-200% → 峰 510/500/500、谷 590/600/620;超范围夹取;"未反向增强"(极值点); 健康段逐位不变;30% 幅度下 N=1/2/3/10 → 550/571/571/571(收敛);默认 0%;滑块中间 = 0%、 两端 = ±200%;数值带符号(+150% 与 -150% 墨迹等宽、都比 0% 宽 ≥8px)。 - [Y] 端到端:合成 ripple(谷恒 500、一个弱峰 520)弱峰区间 20 → 70(健康 120)。 - dragcheck PASS(0 绘制错误);panperf 竖直中位 5.1ms、水平 7.1ms、20 面板竖直 25.0ms / 水平 41.3ms; layercheck clobbers=0/24、pan frames wrong=0/4;cursorcheck 30/30;make clean && make all 零警告。
This commit is contained in:
1 parent
ff3319b842
commit
14b60f5d9e
6 files changed
+125
-40
No files matched your search
@@ -56,7 +56,24 @@ namespace jdi
|
||||
const float kPlotSidePadding = 8.0f; /**< 绘图区左右留白(逻辑像素)。 */
|
||||
const float kLaneDividerWidth = 2.0f; /**< 网格之间分界线的宽度。 */
|
||||
const float kSignalHitTolerance = 6.0f; /**< 信号曲线命中容差(逻辑像素)。 */
|
||||
const float kCursorSnapMinPixels = 6.0f; /**< 光标吸附采样点的最小采样间隔(逻辑像素)。 */
|
||||
/**
|
||||
* @brief 两个矩形的交集(空交集返回宽高为 0 的矩形)。
|
||||
*
|
||||
* 用于把 GDI 绘制(不受 GDI+ 裁剪约束)限制在"本次真正要重画的区域"内,
|
||||
* 见 `PaintLane` 里的 `laneClip`。
|
||||
*/
|
||||
gd::RectF IntersectRect(const gd::RectF& first, const gd::RectF& second)
|
||||
{
|
||||
const float left = std::max(first.X, second.X);
|
||||
const float top = std::max(first.Y, second.Y);
|
||||
const float right = std::min(first.X + first.Width, second.X + second.Width);
|
||||
const float bottom = std::min(first.Y + first.Height, second.Y + second.Height);
|
||||
if (right <= left || bottom <= top)
|
||||
{
|
||||
return gd::RectF(left, top, 0.0f, 0.0f);
|
||||
}
|
||||
return gd::RectF(left, top, right - left, bottom - top);
|
||||
} const float kCursorSnapMinPixels = 6.0f; /**< 光标吸附采样点的最小采样间隔(逻辑像素)。 */
|
||||
const float kLaneLabelPad = 6.0f; /**< 网格内侧标尺带的内边距。 */
|
||||
const float kLegendHeight = 19.0f; /**< 图例条目高度(逻辑像素)。 */
|
||||
const float kLegendPad = 5.0f; /**< 图例相对网格左上角的内缩。 */
|
||||
@@ -145,12 +162,13 @@ namespace jdi
|
||||
/**
|
||||
* @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"));
|
||||
}
|
||||
|
||||
@@ -4818,7 +4836,7 @@ namespace jdi
|
||||
if (!m_pendingWeakPeakItems.empty())
|
||||
{
|
||||
const WeakPeakBuild& first = m_pendingWeakPeakItems.front();
|
||||
SetHint(L"正在构建弱峰调整信号(等级 " + std::to_wstring(first.amountPercent) +
|
||||
SetHint(L"正在构建弱峰调整信号(幅度 " + std::to_wstring(first.amountPercent) +
|
||||
L"% / " + std::to_wstring(first.iterations) + L" 次迭代,共 " +
|
||||
std::to_wstring(m_pendingWeakPeakItems.size()) + L" 条)…");
|
||||
m_pendingWeakPeakLane = index;
|
||||
@@ -7876,8 +7894,17 @@ namespace jdi
|
||||
// 与当前裁剪区求交(而不是替换):局部重画(RepaintSceneCachePatch)时
|
||||
// 外层已经按“需要重画的网格范围”设过裁剪区,这里必须保留,否则会重画到
|
||||
// 其他网格上。整幅重绘时外层没有裁剪区,等同于原来的行为。
|
||||
g.SetClip(gd::RectF(m_area.X, clipTop, m_area.Width, clipBottom - clipTop),
|
||||
gd::CombineModeIntersect);
|
||||
//
|
||||
// ⚠️ **本次调用只允许改动 `m_area`** —— 它既是"整幅渲染"的绘图区,也是
|
||||
// "信号图层"的**条带**(横向补条 = 窄 x 区间 × 全高;纵向补条 = 全宽 × 窄 y 区间)。
|
||||
// GDI 绘制(网格线 / 分界线)**不受 GDI+ 裁剪约束**,所以它们的裁剪矩形必须
|
||||
// **同时**与 `m_area` 求交:否则纵向补条时横向网格线会被画到条带之外的已正确像素上、
|
||||
// 覆盖在波形之上("网格线盖住波形"),并**永久烘进图层**、随之后的每次平移一起移动
|
||||
// —— 正是用户报告的"上下拖动之后,左右/上下/斜向拖动都出现绘制错误"。
|
||||
const gd::RectF laneClip = IntersectRect(
|
||||
gd::RectF(m_area.X, m_area.Y, m_area.Width, m_area.Height),
|
||||
gd::RectF(lane.rect.X, clipTop, lane.rect.Width, clipBottom - clipTop));
|
||||
g.SetClip(laneClip, gd::CombineModeIntersect);
|
||||
|
||||
|
||||
// 幅值网格线(先画线,标尺稍后作为覆盖层绘制,避免被波形遮挡)。
|
||||
@@ -7888,7 +7915,7 @@ namespace jdi
|
||||
// 而一张网格有几十条网格线(5 个网格就是数百条),逐条调用会让单帧多花十几毫秒
|
||||
// —— 这正是"放大后拖动网格视界很卡"的主因。GDI 绘制不受 GDI+ 裁剪约束,
|
||||
// 因此显式传入裁剪矩形;半透明辅助线先与网格底色混合成不透明色。
|
||||
const gd::RectF gridClip = gd::RectF(m_area.X, clipTop, m_area.Width, clipBottom - clipTop);
|
||||
const gd::RectF gridClip = laneClip;
|
||||
/** 标尺数值文本的裁剪矩形(GDI 文本需要显式裁剪,否则会越过绘图区)。 */
|
||||
const gd::RectF clipRectForLabels = gridClip;
|
||||
const std::uint32_t minorLineColor = UiBlendOver(m_theme->cardBg, WithAlpha(m_theme->gridWeak, kMinorLineAlpha));
|
||||
@@ -8357,7 +8384,7 @@ namespace jdi
|
||||
}
|
||||
if (weakPeakOn)
|
||||
{
|
||||
// 每个信号用自己的「增强等级 + 迭代次数」(与面板输入框、构建新信号完全一致)。
|
||||
// 每个信号用自己的「增强等级 + 迭代次数」(与面板滑块、构建新信号完全一致)。
|
||||
const Lane::WeakPeakItem* item = tabLane.FindWeakPeakItem(ref);
|
||||
if (item != nullptr)
|
||||
{
|
||||
@@ -9387,7 +9414,7 @@ namespace jdi
|
||||
// 逐条目面板:每个条目 = 第一排(信号名 + 复选框)
|
||||
// + 参数部分(**只在该条目被勾选时才占位**,因此面板高度随勾选数量动态变化):
|
||||
// * MEAN / CENTER:一行「均值点数 X + 迭代次数 N」输入框(中心轴再多一行统计结果);
|
||||
// * PEAK:两行滑块(增强等级 % / 迭代次数 N),每个信号各有一套自己的参数。
|
||||
// * PEAK:两行滑块(调整幅度 % / 迭代次数 N),每个信号各有一套自己的参数。
|
||||
const bool itemHasParams = (centerAxis || meanFilter || weakPeak);
|
||||
const float weakPeakSliderRow = kWeakPeakSliderRowHeight * m_scale;
|
||||
float meanRowHeight = 0.0f;
|
||||
@@ -10242,7 +10269,7 @@ 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->amountPercent + 2 : 0));
|
||||
@@ -10573,7 +10600,7 @@ namespace jdi
|
||||
: nullptr;
|
||||
|
||||
// 「弱峰调整」:每个被勾选的信号各有两行**滑块**
|
||||
//(增强等级 0–200% / 迭代次数 1 … 该信号自定义上限)。
|
||||
//(调整幅度 -200% – +200% / 迭代次数 1 … 该信号自定义上限)。
|
||||
// 迭代次数行的右侧数值可点击 → 整行变为输入框以修改滑块上限。
|
||||
if (weakPeak)
|
||||
{
|
||||
@@ -10624,7 +10651,9 @@ namespace jdi
|
||||
1.0) *
|
||||
0.5;
|
||||
paintSliderBody(levelTrack, levelRatio,
|
||||
std::to_wstring(peakItem->amountPercent) + L"%", levelActive);
|
||||
(peakItem->amountPercent > 0 ? L"+" : L"") +
|
||||
std::to_wstring(peakItem->amountPercent) + L"%",
|
||||
levelActive);
|
||||
|
||||
// 迭代次数滑块:量程 = 1 … 该信号自己的上限。
|
||||
const int peakMax = std::max(kWeakPeakIterationsMin,
|
||||
|
||||
Reference in new issue
Block a user