fix: 解码器3个bug修复 — 编码→解码往返恢复 + 新增往返测试

1. detect.rs: remove()索引bug — 元素前移后索引未更新,改用每次remove(0)
2. detect.rs: 版本估算公式修正 — (dist-14)/4 → (dist-10)/4,符合ZXing公式
3. extract.rs: 移除显式col 6跳过 — read_module已自动跳过保留区,显式skip导致列配对错位/行序反转

新增 test_roundtrip_png: 矩阵往返 + PNG往返双验证
This commit is contained in:
2026-06-27 14:53:48 +08:00
parent 309c9429ea
commit ce9c8b1b6e
3 changed files with 31 additions and 9 deletions
+7 -4
View File
@@ -145,7 +145,7 @@ pub(crate) fn estimate_version_from_tl_tr(tl: (f64, f64), tr: (f64, f64), module
let dy = tr.1 - tl.1;
let dist_px = (dx * dx + dy * dy).sqrt() as f32;
let dist_modules = dist_px / module_size as f32;
let ver = ((dist_modules as i32 - 14) / 4) as u8;
let ver = ((dist_modules as i32 - 10) / 4) as u8;
ver.clamp(1, 40)
}
@@ -226,9 +226,10 @@ fn find_finders(gray: &[Vec<bool>]) -> Option<[FinderMatch; 3]> {
finders.swap(1, 2);
}
// remove 会使后续元素前移,必须每次 remove(0)
let f0 = finders.remove(0);
let f1 = finders.remove(1);
let f2 = finders.remove(2);
let f1 = finders.remove(0);
let f2 = finders.remove(0);
// ZXing 几何验证:module_size 一致性 + 勾股定理
if !validate_finder_geometry(&[&f0, &f1, &f2]) {
@@ -303,7 +304,9 @@ pub(crate) fn detect_and_extract(
let dy = tr.cy as f64 - tl.cy as f64;
let dist_px = (dx * dx + dy * dy).sqrt() as f32;
let dist_modules = dist_px / module_size as f32;
let ver = ((dist_modules as i32 - 14) / 4) as u8;
// ZXing 公式: totalModules = dist/moduleSize + 7, version = (totalModules - 17) / 4
// 简化为: version = (dist_modules + 7 - 17) / 4 = (dist_modules - 10) / 4
let ver = ((dist_modules as i32 - 10) / 4) as u8;
let version = ver.clamp(1, 40);
let size = 17 + version as usize * 4;
+2 -5
View File
@@ -32,11 +32,8 @@ fn extract_bits(matrix: &Matrix, total_codewords: usize) -> Vec<bool> {
}
col -= 2;
going_up = !going_up;
// 跳过垂直时序图案列(col 6
if col == 6 {
col -= 1;
}
// 垂直时序图案列(col 6)由 read_module 自动跳过保留区,
// 无需显式 skip,否则会导致列配对错位、行序反转
}
bits.truncate(target_bits);
+22
View File
@@ -419,4 +419,26 @@ mod tests {
assert!(svg.contains("#FF0000"));
assert!(svg.contains("#0000FF"));
}
#[test]
fn test_roundtrip_png() {
let config = QrConfig {
level: EcLevel::M,
version: VersionMode::Auto,
margin: 4,
};
let qr = QrCode::encode("Hello World", config).unwrap();
// 直接从矩阵解码
let matrix: Vec<Vec<bool>> = qr.modules().to_vec();
let result = crate::decoder::decode_matrix(&matrix)
.expect("矩阵解码往返失败");
assert_eq!(result.text, "Hello World");
// PNG 往返
let png_bytes = qr.to_png_bytes(8, None).unwrap();
let result = crate::decoder::decode_image(&png_bytes)
.expect("PNG 解码往返失败");
assert_eq!(result.text, "Hello World");
}
}