diff --git a/cli/src/main.rs b/cli/src/main.rs index c0500e5..71d20ba 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -266,7 +266,7 @@ fn build_mode(mode: &str, opts: &EncodeOpts, fb: &str) -> Result { let pwd = opts .password .as_deref() - .or_else(|| env_pwd.as_deref()) + .or(env_pwd.as_deref()) .unwrap_or(""); Ok(text_builder::build_wifi_text( s, diff --git a/core/src/decoder/detect.rs b/core/src/decoder/detect.rs index 657cf56..0fc1c62 100644 --- a/core/src/decoder/detect.rs +++ b/core/src/decoder/detect.rs @@ -51,12 +51,14 @@ fn scan_row(gray: &[Vec], row: usize) -> Vec<(usize, usize, usize)> { if base < 2.0 { continue; } - let tolerance = 0.4; + let tolerance = 0.5; // ZXing 标准:moduleSize/2 容差 let check = |v: f32, expected: f32| (v - expected * base).abs() < base * tolerance; if check(r0, 1.0) && check(r1, 1.0) && check(r2, 3.0) && check(r3, 1.0) && check(r4, 1.0) { - let cx = runs[i + 2].0 + runs[i + 2].1 / 2; - centers.push((cx, row, total as usize)); + // ZXing 的 end-based 中心公式: end - r4 - r3 - r2/2 (浮点精度更高) + let end = runs[i + 4].0 + runs[i + 4].1; + let cx = end as f32 - r4 - r3 - r2 / 2.0; + centers.push((cx as usize, row, total as usize)); } } @@ -93,13 +95,13 @@ fn scan_col(gray: &[Vec], col: usize) -> Vec<(usize, usize, usize)> { continue; } - let tolerance = 0.4; + let tolerance = 0.5; // ZXing 标准:moduleSize/2 容差 let check = |v: f32, expected: f32| (v - expected * base).abs() < base * tolerance; if check(r0, 1.0) && check(r1, 1.0) && check(r2, 3.0) && check(r3, 1.0) && check(r4, 1.0) { - let cy = runs[i + 2].0 + runs[i + 2].1 / 2; - let total_px = total as usize; - centers.push((col, cy, total_px)); + let end = runs[i + 4].0 + runs[i + 4].1; + let cy = end as f32 - r4 - r3 - r2 / 2.0; + centers.push((col, cy as usize, total as usize)); } } @@ -215,43 +217,65 @@ fn find_finders(gray: &[Vec]) -> Option<[FinderMatch; 3]> { // 排序:左上、右上、左下 finders.sort_by(|a, b| { - let da = a.cx * a.cx + a.cy * a.cy; // 到原点的距离 + let da = a.cx * a.cx + a.cy * a.cy; let db = b.cx * b.cx + b.cy * b.cy; da.cmp(&db) }); - // 区分右上(X 最大)和左下(Y 最大) if finders[1].cx < finders[2].cx { finders.swap(1, 2); } let f0 = finders.remove(0); - let f1 = finders.remove(0); - let f2 = finders.remove(0); + let f1 = finders.remove(1); + let f2 = finders.remove(2); + + // ZXing 几何验证:module_size 一致性 + 勾股定理 + if !validate_finder_geometry(&[&f0, &f1, &f2]) { + return None; + } Some([f0, f1, f2]) } -/// 估算定位图案大小(像素) -fn estimate_finder_size(gray: &[Vec], cx: usize, cy: usize) -> usize { - // 从中心点水平扫描连续暗像素 - let mut left = cx; - while left > 0 { - if cy < gray.len() && left < gray[0].len() && gray[cy][left] { - left -= 1; - } else { - break; - } +/// ZXing 风格几何验证:3 个定位图案的 module_size 必须一致且构成近似直角三角形 +fn validate_finder_geometry(finders: &[&FinderMatch; 3]) -> bool { + let s0 = finders[0].size as f64; + let s1 = finders[1].size as f64; + let s2 = finders[2].size as f64; + + // module_size 一致性: 最大偏差 < 10% + let avg_size = (s0 + s1 + s2) / 3.0; + let max_dev = avg_size * 0.10; + if (s0 - avg_size).abs() > max_dev + || (s1 - avg_size).abs() > max_dev + || (s2 - avg_size).abs() > max_dev + { + return false; } - let mut right = cx; - while right + 1 < gray[0].len() { - if cy < gray.len() && gray[cy][right] { - right += 1; - } else { - break; - } + + // 勾股定理验证: |C - sqrt(A² + B²)| / min(C, sqrt(A² + B²)) < 15% + let ax = finders[1].cx as f64 - finders[0].cx as f64; + let ay = finders[1].cy as f64 - finders[0].cy as f64; + let bx = finders[2].cx as f64 - finders[0].cx as f64; + let by = finders[2].cy as f64 - finders[0].cy as f64; + + let a_len = (ax * ax + ay * ay).sqrt(); + let b_len = (bx * bx + by * by).sqrt(); + let c_len = { + let cx = finders[2].cx as f64 - finders[1].cx as f64; + let cy = finders[2].cy as f64 - finders[1].cy as f64; + (cx * cx + cy * cy).sqrt() + }; + + let hypotenuse = (a_len * a_len + b_len * b_len).sqrt(); + let min = c_len.min(hypotenuse); + if min < 1.0 { + return true; // 太小,跳过检查 } - right - left + let deviation = (c_len - hypotenuse).abs() / min; + + deviation < 0.15 } /// 从二值化图像中提取 QR 布尔矩阵 @@ -360,7 +384,7 @@ fn check_quiet_zone(modules: &[Vec], size: usize) -> bool { true } -/// 计算定位图案比例偏差(与理想 1:1:3:1:1 的偏差,0=完美, 1=最大) +/// 计算定位图案比例偏差(0=完美, 1=最大),使用 base=total/7 对齐 ZXing fn compute_finder_deviation(gray: &[Vec], finder: &FinderMatch) -> f64 { let row = finder.cy; let mut runs: Vec = Vec::new(); @@ -382,14 +406,14 @@ fn compute_finder_deviation(gray: &[Vec], finder: &FinderMatch) -> f64 { let mut best_dev = 1.0f64; for i in 0..runs.len().saturating_sub(4) { - let avg = - (runs[i] + runs[i + 1] + runs[i + 2] + runs[i + 3] + runs[i + 4]) as f64 / 5.0; - if avg < 2.0 { + let total = (runs[i] + runs[i + 1] + runs[i + 2] + runs[i + 3] + runs[i + 4]) as f64; + let base = total / 7.0; + if base < 2.0 { continue; } - let expected = [avg, avg, 3.0 * avg, avg, avg]; + let expected = [base, base, 3.0 * base, base, base]; let dev: f64 = (0..5) - .map(|j| (runs[j] as f64 - expected[j]).abs() / avg) + .map(|j| (runs[i + j] as f64 - expected[j]).abs() / base) .sum::() / 5.0; if dev < best_dev { diff --git a/core/src/decoder/perspective.rs b/core/src/decoder/perspective.rs index 722a48a..848231f 100644 --- a/core/src/decoder/perspective.rs +++ b/core/src/decoder/perspective.rs @@ -143,9 +143,10 @@ fn count_finder_hits(gray: &[Vec]) -> usize { let (mut col,mut runs)=(0,vec![]); while col 20 { return (StatusCode::BAD_REQUEST, "边距过大(最大 20)").into_response(); } - if size < 1 || size > 20 { + if !(1..=20).contains(&size) { return (StatusCode::BAD_REQUEST, "模块大小需在 1-20 之间").into_response(); } @@ -77,7 +77,6 @@ fn generate_qr_inner(text: &str, level_str: &str, margin: u8, size: u8, fmt: &st level, version: VersionMode::Auto, margin, - ..Default::default() }; let qr = match QrCode::encode(text, config) {