diff --git a/core/src/matrix/mask.rs b/core/src/matrix/mask.rs index c1fcfd4..e8c6421 100644 --- a/core/src/matrix/mask.rs +++ b/core/src/matrix/mask.rs @@ -1 +1,249 @@ -// FIXME: 掩码评分 — Task 8 +use crate::matrix::grid::Matrix; + +/// 掩码函数: f(x, y) = true 时翻转该模块 +type MaskFn = fn(u8, u8) -> bool; + +/// 8 种标准 QR 掩码 +pub const MASK_FNS: [MaskFn; 8] = [ + |x, y| (x + y) % 2 == 0, + |_, y| y % 2 == 0, + |x, _| x % 3 == 0, + |x, y| (x + y) % 3 == 0, + |x, y| ((y / 2) + (x / 3)) % 2 == 0, + |x, y| (x as u32 * y as u32) % 2 + (x as u32 * y as u32) % 3 == 0, + |x, y| ((x as u32 * y as u32) % 2 + (x as u32 * y as u32) % 3) % 2 == 0, + |x, y| ((x as u32 + y as u32) % 2 + (x as u32 * y as u32) % 3) % 2 == 0, +]; + +/// 应用掩码到矩阵的数据区域(跳过功能图案保留区域) +pub fn apply_mask(matrix: &Matrix, mask_idx: u8) -> Matrix { + let mask_fn = MASK_FNS[mask_idx as usize]; + let mut result = matrix.clone(); + for y in 0..matrix.size { + for x in 0..matrix.size { + if !matrix.is_reserved(x, y) && mask_fn(x, y) { + let current = result.get(x, y); + result.set(x, y, !current); + } + } + } + result +} + +/// 惩罚评分(越低越好) +pub fn score(matrix: &Matrix) -> u32 { + score_rule1(matrix) + score_rule2(matrix) + score_rule3(matrix) + score_rule4(matrix) +} + +/// 规则 1: 连续 5+ 同色行/列 → N1 + k - 5 +fn score_rule1(matrix: &Matrix) -> u32 { + let mut penalty = 0u32; + let n = matrix.size as usize; + + // 水平扫描 + for y in 0..n { + let mut run = 1u32; + let mut prev = matrix.get(0, y as u8); + for x in 1..n { + let cur = matrix.get(x as u8, y as u8); + if cur == prev { + run += 1; + } else { + if run >= 5 { + penalty += 3 + run - 5; + } + run = 1; + prev = cur; + } + } + if run >= 5 { + penalty += 3 + run - 5; + } + } + + // 垂直扫描 + for x in 0..n { + let mut run = 1u32; + let mut prev = matrix.get(x as u8, 0); + for y in 1..n { + let cur = matrix.get(x as u8, y as u8); + if cur == prev { + run += 1; + } else { + if run >= 5 { + penalty += 3 + run - 5; + } + run = 1; + prev = cur; + } + } + if run >= 5 { + penalty += 3 + run - 5; + } + } + + penalty +} + +/// 规则 2: 同色 2×2 方块,每个 +3 +fn score_rule2(matrix: &Matrix) -> u32 { + let mut count = 0u32; + let n = matrix.size; + for y in 0..n - 1 { + for x in 0..n - 1 { + let v = matrix.get(x, y); + if matrix.get(x + 1, y) == v + && matrix.get(x, y + 1) == v + && matrix.get(x + 1, y + 1) == v + { + count += 1; + } + } + } + count * 3 +} + +/// 规则 3: 检测 1011101 模式(及其反转),每次 +40 +fn score_rule3(matrix: &Matrix) -> u32 { + let mut penalty = 0u32; + let n = matrix.size as usize; + + // 水平方向 + for y in 0..n { + for x in 0..n { + if x + 10 >= n { + continue; + } + // 正模式: 10111010000 + let forward = (0..11).all(|i| { + let expected = [ + true, false, true, true, true, false, true, false, false, false, false, + ][i]; + matrix.get((x + i) as u8, y as u8) == expected + }); + if forward { + penalty += 40; + } + + // 反模式: 00001011101 + let reverse = (0..11).all(|i| { + let expected = [ + false, false, false, false, true, false, true, true, true, false, true, + ][i]; + matrix.get((x + i) as u8, y as u8) == expected + }); + if reverse { + penalty += 40; + } + } + } + + // 垂直方向 + for y in 0..n { + if y + 10 >= n { + continue; + } + for x in 0..n { + let forward = (0..11).all(|i| { + let expected = [ + true, false, true, true, true, false, true, false, false, false, false, + ][i]; + matrix.get(x as u8, (y + i) as u8) == expected + }); + if forward { + penalty += 40; + } + + let reverse = (0..11).all(|i| { + let expected = [ + false, false, false, false, true, false, true, true, true, false, true, + ][i]; + matrix.get(x as u8, (y + i) as u8) == expected + }); + if reverse { + penalty += 40; + } + } + } + + penalty +} + +/// 规则 4: 暗模块占比偏离 50%,每 5% +10 +fn score_rule4(matrix: &Matrix) -> u32 { + let total = (matrix.size as u32) * (matrix.size as u32); + let dark: u32 = (0..matrix.size) + .flat_map(|y| (0..matrix.size).map(move |x| matrix.get(x, y) as u32)) + .sum(); + + let pct = (dark * 100 + total / 2) / total; // 四舍五入 + let deviation = ((pct as i32 - 50).unsigned_abs()) / 5; + deviation * 10 +} + +/// 评估所有 8 种掩码,返回最佳掩码编号和对应矩阵 +pub fn best_mask(matrix: &Matrix) -> (u8, Matrix) { + let mut best_idx = 0u8; + let mut best_score = u32::MAX; + let mut best_matrix = matrix.clone(); + + for i in 0..8u8 { + let masked = apply_mask(matrix, i); + let s = score(&masked); + if s < best_score { + best_score = s; + best_idx = i; + best_matrix = masked; + } + } + + (best_idx, best_matrix) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_apply_mask() { + let m = Matrix::new(21); + // 不设置 reserved,所有区域都是数据区 + let masked = apply_mask(&m, 0); // (x+y) % 2 == 0 + // 初始全白,掩码 0 会在 (x+y)%2==0 的位置翻转 + assert_eq!(masked.get(0, 0), true); // (0+0)%2=0 → 翻转 + assert_eq!(masked.get(1, 0), false); // (1+0)%2=1 → 不变 + } + + #[test] + fn test_score_rule2() { + let mut m = Matrix::new(3); + // 全黑 → 4 个 2×2 方块 + for y in 0..3u8 { + for x in 0..3u8 { + m.set(x, y, true); + } + } + assert_eq!(score_rule2(&m), 4 * 3); // 4 blocks × 3 = 12 + } + + #[test] + fn test_score_rule4() { + let m = Matrix::new(10); + // 全部白色 → 0% dark → 偏离 50% = 10 × 5% → penalty = 10 × 10 = 100 + let s = score_rule4(&m); + assert_eq!(s, 100); + } + + #[test] + fn test_best_mask_selects_something() { + let mut m = Matrix::new(21); + // 填一些随机数据 + for y in 0..21u8 { + for x in 0..21u8 { + m.set(x, y, (x as u32 * y as u32) % 3 == 0); + } + } + let (idx, _masked) = best_mask(&m); + assert!(idx < 8); + } +} diff --git a/core/src/matrix/placement.rs b/core/src/matrix/placement.rs index e63148f..b7b4861 100644 --- a/core/src/matrix/placement.rs +++ b/core/src/matrix/placement.rs @@ -1 +1,80 @@ -// FIXME: 数据排列 — Task 8 +use crate::matrix::grid::Matrix; + +/// 将码字比特按蛇形路径放入矩阵数据区域 +pub fn place_data(matrix: &mut Matrix, codewords: &[u8]) { + let size = matrix.size as usize; + // 展开字节为比特流 + let bits: Vec = codewords + .iter() + .flat_map(|&cw| (0..8).rev().map(move |i| (cw >> i) & 1 == 1)) + .collect(); + + let mut bit_idx = 0usize; + + // 从右下角开始,向上扫描 + let mut col = (size - 1) as i16; + let mut going_up = true; + + while col >= 0 && bit_idx < bits.len() { + // 跳过垂直时序线 (col=6) + let actual_col = if col == 6 { 5 } else { col as usize }; + + if going_up { + for row in (0..size).rev() { + if bit_idx >= bits.len() { + break; + } + place_bit( + matrix, + actual_col as u8, + row as u8, + bits[bit_idx], + &mut bit_idx, + ); + if bit_idx >= bits.len() { + break; + } + place_bit( + matrix, + (actual_col - 1) as u8, + row as u8, + bits[bit_idx], + &mut bit_idx, + ); + } + } else { + for row in 0..size { + if bit_idx >= bits.len() { + break; + } + place_bit( + matrix, + actual_col as u8, + row as u8, + bits[bit_idx], + &mut bit_idx, + ); + if bit_idx >= bits.len() { + break; + } + place_bit( + matrix, + (actual_col - 1) as u8, + row as u8, + bits[bit_idx], + &mut bit_idx, + ); + } + } + + col -= 2; + going_up = !going_up; + } +} + +fn place_bit(matrix: &mut Matrix, x: u8, y: u8, bit: bool, idx: &mut usize) { + if x < matrix.size && y < matrix.size && !matrix.is_reserved(x, y) { + matrix.set(x, y, bit); + *idx += 1; + } +}