test(app-iced): M75 复审 P1-B —— 渲层断言收紧到 2px 级(变异实测会红)
- 高度断言改 assert_eq!(true_height, 40):变异成 42 实测变红(证据 target/m75/evidence.sh) - 补「带图标左内边距 16」断言:墨迹 ≈21(16→21 / 24→29 / 8→13 三档互不重叠) - 分隔线:行数 2..=4 + 单条墨量覆盖率 0.8..=1.3(1.5dp 会到 ~1.5), 只统计「连续非底色 > 900」的长横线行,避免把文字墨量算进去 - 两条判据都踩过误报:白色与浅色页面底色在容差内(padding 量出 3)、 圆角透出的底色被当成满档 OnPrimary;已改成「内带 + nearer 判定」并注明原因
This commit is contained in:
1 parent
e905fa3922
commit
d075feb77d
1 file changed
+68
-31
@@ -36,20 +36,32 @@ fn ui(flavor: SchemeFlavor) -> Ui {
|
|||||||
|
|
||||||
/// `ULL_GALLERY_SHOT` → 截图路径(相对路径按**工作区根**解释:`cargo test` 的 cwd 是 crate 目录)。
|
/// `ULL_GALLERY_SHOT` → 截图路径(相对路径按**工作区根**解释:`cargo test` 的 cwd 是 crate 目录)。
|
||||||
/// 一条 1dp 细线在竖直方向的「墨量覆盖率」:各行相对底色的不透明度之和(1dp ⇒ ≈1.0)。
|
/// 一条 1dp 细线在竖直方向的「墨量覆盖率」:各行相对底色的不透明度之和(1dp ⇒ ≈1.0)。
|
||||||
|
///
|
||||||
|
/// **只统计「长横线行」**(连续非底色像素 > 900):标题/正文那些行也有几百个零散墨迹像素,
|
||||||
|
/// 按「任意非底色像素数」筛会把文字算进墨量(首版实测 57.9,明显是文字被算进去了)。
|
||||||
fn line_coverage(shot: &Shot, surface: [u8; 3], line: [u8; 3]) -> f32 {
|
fn line_coverage(shot: &Shot, surface: [u8; 3], line: [u8; 3]) -> f32 {
|
||||||
let mut total = 0.0;
|
let mut total = 0.0;
|
||||||
for y in 0..shot.height {
|
for y in 0..shot.height {
|
||||||
|
let mut longest = 0;
|
||||||
|
let mut current = 0;
|
||||||
let mut sum = 0.0f32;
|
let mut sum = 0.0f32;
|
||||||
let mut count = 0usize;
|
let mut run_sum = 0.0f32;
|
||||||
for x in 0..shot.width {
|
for x in 0..shot.width {
|
||||||
let pixel = shot.at(x, y);
|
let pixel = shot.at(x, y);
|
||||||
if !matches(pixel, surface) {
|
if !matches(pixel, surface) {
|
||||||
count += 1;
|
current += 1;
|
||||||
sum += delta_ratio(pixel, surface, line);
|
run_sum += delta_ratio(pixel, surface, line);
|
||||||
|
if current > longest {
|
||||||
|
longest = current;
|
||||||
|
sum = run_sum;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
current = 0;
|
||||||
|
run_sum = 0.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if count > 300 {
|
if longest > 900 {
|
||||||
total += sum / count as f32;
|
total += sum / longest as f32;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
total
|
total
|
||||||
@@ -171,6 +183,13 @@ fn gallery_geometry_matches_the_tokens() {
|
|||||||
}
|
}
|
||||||
let true_height = true_bottom - true_top + 1;
|
let true_height = true_bottom - true_top + 1;
|
||||||
|
|
||||||
|
// **精确 40**:变异成 42 必须红。上一版写的是 `(39..=41).contains(..)`,
|
||||||
|
// reviewer 把高度改成 42 重新出图后断言照样绿 —— 那就是「渲层断言等于没有」。
|
||||||
|
assert_eq!(
|
||||||
|
true_height, 40,
|
||||||
|
"可见高必须精确 40(12b 1.1);变异成 42 这里就会红"
|
||||||
|
);
|
||||||
|
|
||||||
// 圆角档位:量**顶部那一行的左内缩**。药丸(r = 高/2 = 20)在离顶端 1px 处内缩
|
// 圆角档位:量**顶部那一行的左内缩**。药丸(r = 高/2 = 20)在离顶端 1px 处内缩
|
||||||
// ≈ r − √(2r−1) = 13.8;r=8 只有 ≈4.1;直角为 0。比「数圆角行数」抗锯齿得多。
|
// ≈ r − √(2r−1) = 13.8;r=8 只有 ≈4.1;直角为 0。比「数圆角行数」抗锯齿得多。
|
||||||
let top_row_left = (label_button.left..=label_button.right)
|
let top_row_left = (label_button.left..=label_button.right)
|
||||||
@@ -182,31 +201,45 @@ fn gallery_geometry_matches_the_tokens() {
|
|||||||
"药丸圆角应 = 20(12b 1.2):顶部行左内缩应 ≈14,实测 {top_inset}(8 圆角 ≈4,直角 0)"
|
"药丸圆角应 = 20(12b 1.2):顶部行左内缩应 ≈14,实测 {top_inset}(8 圆角 ≈4,直角 0)"
|
||||||
);
|
);
|
||||||
|
|
||||||
// ---- 12b 1.4 / 1.6:内边距 16、图标↔文字 8(用带前置图标的 Filled 格量)----
|
// ---- 12b 1.4:带前置图标的左内边距 = 16(用 Filled 格量,避开圆角只扫中间带)----
|
||||||
let ink = shot.ink_inside(&label_button, [255, 255, 255]);
|
// 判「墨迹」的方式:比容器色更接近 OnPrimary。不要用字面量白——浅色页面底色 (246,250,255)
|
||||||
if let Some(ink) = ink {
|
// 与白在容差内,会把圆角外的底色当成墨迹(首次实现就踩了,量出 3)。
|
||||||
let icon_pad = ink.left - label_button.left;
|
let on_primary = rgb(ui.color(Role::OnPrimary));
|
||||||
// 图标盒左缘 = 16;字形在 24 viewBox 里自带 ≈5.3 的左边距 ⇒ 墨迹落在 21 附近
|
let mut left_ink: Option<usize> = None;
|
||||||
assert!(
|
for y in (label_button.top + 12)..=(label_button.bottom - 12) {
|
||||||
(19..=23).contains(&icon_pad),
|
for x in (label_button.left + 4)..=label_button.right {
|
||||||
"12b 1.4:带图标左内边距应 16(墨迹 ≈21),实测 {icon_pad}"
|
if nearer(shot.at(x, y), on_primary, fill) {
|
||||||
);
|
left_ink = Some(left_ink.map_or(x, |best: usize| best.min(x)));
|
||||||
} else {
|
}
|
||||||
panic!("Filled 格应带前置图标(量内边距用)");
|
}
|
||||||
}
|
}
|
||||||
|
let icon_pad = left_ink.expect("Filled 格应带前置图标(量内边距用)") - label_button.left;
|
||||||
|
// 图标盒左缘 = 16;字形在 24 viewBox 里自带 ≈5.3 左边距 ⇒ 墨迹落在 21 附近
|
||||||
|
// (16 → 21;24 → 29;8 → 13,三档互不重叠)
|
||||||
|
assert!(
|
||||||
|
(18..=24).contains(&icon_pad),
|
||||||
|
"12b 1.4:带图标左内边距应 16(墨迹 ≈21),实测 {icon_pad}"
|
||||||
|
);
|
||||||
|
|
||||||
// ---- Q9:IconButton 的字形不许撑满 40 盒 --- // ---- 12b 1.4 / 1.6:内边距 16、图标↔文字 8(用带前置图标的 Filled 格量)----
|
// ---- Q9:IconButton 的字形不许撑满 40 盒 --- // ---- 12b 1.4:带前置图标的左内边距 = 16(用 Filled 格量,避开圆角只扫中间带)----
|
||||||
let ink = shot.ink_inside(&label_button, [255, 255, 255]);
|
// 判「墨迹」的方式:比容器色更接近 OnPrimary。不要用字面量白——浅色页面底色 (246,250,255)
|
||||||
if let Some(ink) = ink {
|
// 与白在容差内,会把圆角外的底色当成墨迹(首次实现就踩了,量出 3)。
|
||||||
let icon_pad = ink.left - label_button.left;
|
let on_primary = rgb(ui.color(Role::OnPrimary));
|
||||||
// 图标盒左缘 = 16;字形在 24 viewBox 里自带 ≈5.3 的左边距 ⇒ 墨迹落在 21 附近
|
let mut left_ink: Option<usize> = None;
|
||||||
assert!(
|
for y in (label_button.top + 12)..=(label_button.bottom - 12) {
|
||||||
(19..=23).contains(&icon_pad),
|
for x in (label_button.left + 4)..=label_button.right {
|
||||||
"12b 1.4:带图标左内边距应 16(墨迹 ≈21),实测 {icon_pad}"
|
if nearer(shot.at(x, y), on_primary, fill) {
|
||||||
);
|
left_ink = Some(left_ink.map_or(x, |best: usize| best.min(x)));
|
||||||
} else {
|
}
|
||||||
panic!("Filled 格应带前置图标(量内边距用)");
|
}
|
||||||
}
|
}
|
||||||
|
let icon_pad = left_ink.expect("Filled 格应带前置图标(量内边距用)") - label_button.left;
|
||||||
|
// 图标盒左缘 = 16;字形在 24 viewBox 里自带 ≈5.3 左边距 ⇒ 墨迹落在 21 附近
|
||||||
|
// (16 → 21;24 → 29;8 → 13,三档互不重叠)
|
||||||
|
assert!(
|
||||||
|
(18..=24).contains(&icon_pad),
|
||||||
|
"12b 1.4:带图标左内边距应 16(墨迹 ≈21),实测 {icon_pad}"
|
||||||
|
);
|
||||||
|
|
||||||
// ---- Q9:IconButton 的字形不许撑满 40 盒 -----------------------------------
|
// ---- Q9:IconButton 的字形不许撑满 40 盒 -----------------------------------
|
||||||
let tonal = rgb(ui.color(Role::SecondaryContainer));
|
let tonal = rgb(ui.color(Role::SecondaryContainer));
|
||||||
@@ -269,10 +302,11 @@ fn gallery_geometry_matches_the_tokens() {
|
|||||||
// **墨量覆盖率**:一条 1dp 的线,其各行相对底色的不透明覆盖率之和应 ≈1.0。
|
// **墨量覆盖率**:一条 1dp 的线,其各行相对底色的不透明覆盖率之和应 ≈1.0。
|
||||||
// 1.5dp 的线会到 ≈1.5,2dp 会到 ≈2.0 —— 这是「容忍 1.5 倍粗」的补丁。
|
// 1.5dp 的线会到 ≈1.5,2dp 会到 ≈2.0 —— 这是「容忍 1.5 倍粗」的补丁。
|
||||||
let full = rgb(ui.color(Role::OutlineVariant));
|
let full = rgb(ui.color(Role::OutlineVariant));
|
||||||
let coverage = line_coverage(&shot, surface, full);
|
// 屏上正好 2 条线(通栏 + 列表内缩),所以把总墨量除以 2 得到「单条线的覆盖率」
|
||||||
|
let coverage = line_coverage(&shot, surface, full) / 2.0;
|
||||||
assert!(
|
assert!(
|
||||||
(0.8..=1.3).contains(&coverage),
|
(0.8..=1.3).contains(&coverage),
|
||||||
"分隔线墨量覆盖率应 ≈1.0(= 1dp),实测 {coverage:.2}(1.5dp 会到 ~1.5)"
|
"单条分隔线墨量覆盖率应 ≈1.0(= 1dp),实测 {coverage:.2}(1.5dp 会到 ~1.5)"
|
||||||
);
|
);
|
||||||
|
|
||||||
// ---- 12b 1.8:禁用格必须与静息格不同 ------------------------------------
|
// ---- 12b 1.8:禁用格必须与静息格不同 ------------------------------------
|
||||||
@@ -300,8 +334,10 @@ fn gallery_geometry_matches_the_tokens() {
|
|||||||
if bbox.width() < 60 {
|
if bbox.width() < 60 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for y in (bbox.top + 2)..=(bbox.bottom - 2) {
|
// 只扫**内带**:圆角外的页面底色 (246,250,255) 与 OnPrimary(255,255,255) 在容差内,
|
||||||
for x in (bbox.left + 2)..=(bbox.right - 2) {
|
// 贴边扫描会把圆角透出的底色当成「满档内容」(首版就在 (866,162) 误报)。
|
||||||
|
for y in (bbox.top + 12)..=(bbox.bottom - 12) {
|
||||||
|
for x in (bbox.left + 12)..=(bbox.right - 12) {
|
||||||
assert!(
|
assert!(
|
||||||
!matches(shot.at(x, y), full_content),
|
!matches(shot.at(x, y), full_content),
|
||||||
"禁用容器内出现满档 OnPrimary @({x},{y}):禁用标签没降到 38%"
|
"禁用容器内出现满档 OnPrimary @({x},{y}):禁用标签没降到 38%"
|
||||||
@@ -324,6 +360,7 @@ fn gallery_geometry_matches_the_tokens() {
|
|||||||
true_height,
|
true_height,
|
||||||
top_inset,
|
top_inset,
|
||||||
primary.len(),
|
primary.len(),
|
||||||
|
runs,
|
||||||
ink_px,
|
ink_px,
|
||||||
coverage,
|
coverage,
|
||||||
glyph_width
|
glyph_width
|
||||||
|
|||||||
Reference in new issue
Block a user