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:
@@ -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 dy = tr.1 - tl.1;
|
||||||
let dist_px = (dx * dx + dy * dy).sqrt() as f32;
|
let dist_px = (dx * dx + dy * dy).sqrt() as f32;
|
||||||
let dist_modules = dist_px / module_size 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)
|
ver.clamp(1, 40)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,9 +226,10 @@ fn find_finders(gray: &[Vec<bool>]) -> Option<[FinderMatch; 3]> {
|
|||||||
finders.swap(1, 2);
|
finders.swap(1, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove 会使后续元素前移,必须每次 remove(0)
|
||||||
let f0 = finders.remove(0);
|
let f0 = finders.remove(0);
|
||||||
let f1 = finders.remove(1);
|
let f1 = finders.remove(0);
|
||||||
let f2 = finders.remove(2);
|
let f2 = finders.remove(0);
|
||||||
|
|
||||||
// ZXing 几何验证:module_size 一致性 + 勾股定理
|
// ZXing 几何验证:module_size 一致性 + 勾股定理
|
||||||
if !validate_finder_geometry(&[&f0, &f1, &f2]) {
|
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 dy = tr.cy as f64 - tl.cy as f64;
|
||||||
let dist_px = (dx * dx + dy * dy).sqrt() as f32;
|
let dist_px = (dx * dx + dy * dy).sqrt() as f32;
|
||||||
let dist_modules = dist_px / module_size 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 version = ver.clamp(1, 40);
|
||||||
|
|
||||||
let size = 17 + version as usize * 4;
|
let size = 17 + version as usize * 4;
|
||||||
|
|||||||
@@ -32,11 +32,8 @@ fn extract_bits(matrix: &Matrix, total_codewords: usize) -> Vec<bool> {
|
|||||||
}
|
}
|
||||||
col -= 2;
|
col -= 2;
|
||||||
going_up = !going_up;
|
going_up = !going_up;
|
||||||
|
// 垂直时序图案列(col 6)由 read_module 自动跳过保留区,
|
||||||
// 跳过垂直时序图案列(col 6)
|
// 无需显式 skip,否则会导致列配对错位、行序反转
|
||||||
if col == 6 {
|
|
||||||
col -= 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bits.truncate(target_bits);
|
bits.truncate(target_bits);
|
||||||
|
|||||||
@@ -419,4 +419,26 @@ mod tests {
|
|||||||
assert!(svg.contains("#FF0000"));
|
assert!(svg.contains("#FF0000"));
|
||||||
assert!(svg.contains("#0000FF"));
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user