From 1adb7e41e44d3d99cf4fa5c0b789e9e6993d11fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E8=88=AA=E5=AE=87?= <3364451258@qq.com> Date: Tue, 16 Jun 2026 23:44:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20QR=20=E7=9F=A9=E9=98=B5=20+=20=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=9B=BE=E6=A1=88=E7=BB=98=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/src/matrix/grid.rs | 36 +++++++- core/src/matrix/patterns.rs | 169 +++++++++++++++++++++++++++++++++++- 2 files changed, 203 insertions(+), 2 deletions(-) diff --git a/core/src/matrix/grid.rs b/core/src/matrix/grid.rs index 8b4a3c0..838e56e 100644 --- a/core/src/matrix/grid.rs +++ b/core/src/matrix/grid.rs @@ -1 +1,35 @@ -// FIXME: 模块矩阵 — Task 7 +/// QR 码模块矩阵 +#[derive(Clone)] +pub struct Matrix { + pub size: u8, + pub modules: Vec>, // true = 暗(黑), false = 亮(白) + pub reserved: Vec>, // true = 功能图案占用,不可放数据 +} + +impl Matrix { + pub fn new(size: u8) -> Self { + let modules = vec![vec![false; size as usize]; size as usize]; + let reserved = vec![vec![false; size as usize]; size as usize]; + Matrix { size, modules, reserved } + } + + pub fn get(&self, x: u8, y: u8) -> bool { + self.modules[y as usize][x as usize] + } + + pub fn set(&mut self, x: u8, y: u8, value: bool) { + self.modules[y as usize][x as usize] = value; + } + + /// 标记为功能图案保留区域 + pub fn reserve(&mut self, x: u8, y: u8) { + if x < self.size && y < self.size { + self.reserved[y as usize][x as usize] = true; + } + } + + /// 是否被功能图案占用(不可放置数据) + pub fn is_reserved(&self, x: u8, y: u8) -> bool { + x < self.size && y < self.size && self.reserved[y as usize][x as usize] + } +} diff --git a/core/src/matrix/patterns.rs b/core/src/matrix/patterns.rs index 9dc32d5..5ce8eb7 100644 --- a/core/src/matrix/patterns.rs +++ b/core/src/matrix/patterns.rs @@ -1 +1,168 @@ -// FIXME: 功能图案 — Task 7 +use crate::matrix::grid::Matrix; + +/// 放置定位图案(3 个角上的大回字形,7×7) +pub fn place_finder_patterns(matrix: &mut Matrix) { + let positions = [ + (0u8, 0u8), // 左上 + (matrix.size - 7, 0), // 右上 + (0, matrix.size - 7), // 左下 + ]; + + for &(fx, fy) in &positions { + for dy in 0..7u8 { + for dx in 0..7u8 { + let is_dark = match (dx, dy) { + // 外边框 + (0, _) | (6, _) | (_, 0) | (_, 6) => true, + // 中间 3×3 方块 + (2..=4, 2..=4) => true, + _ => false, + }; + let x = fx + dx; + let y = fy + dy; + matrix.set(x, y, is_dark); + matrix.reserve(x, y); + } + } + } +} + +/// 放置时序图案(行 6 和列 6 的黑白交替线) +pub fn place_timing_patterns(matrix: &mut Matrix) { + let size = matrix.size; + // 水平时序线 (y = 6) + for x in 8..size - 8 { + matrix.set(x, 6, x % 2 == 0); + matrix.reserve(x, 6); + } + // 垂直时序线 (x = 6) + for y in 8..size - 8 { + matrix.set(6, y, y % 2 == 0); + matrix.reserve(6, y); + } +} + +/// 放置所有对齐图案(5×5 回字形) +pub fn place_alignment_patterns(matrix: &mut Matrix, positions: &[u8]) { + for &cy in positions { + for &cx in positions { + if is_near_finder(cx, cy, matrix.size) { + continue; + } + place_single_alignment(matrix, cx, cy); + } + } +} + +fn place_single_alignment(matrix: &mut Matrix, cx: u8, cy: u8) { + let x0 = cx - 2; + let y0 = cy - 2; + for dy in 0..5u8 { + for dx in 0..5u8 { + let is_dark = match (dx, dy) { + (0, _) | (4, _) | (_, 0) | (_, 4) => true, + (2, 2) => true, + _ => false, + }; + let x = x0 + dx; + let y = y0 + dy; + matrix.set(x, y, is_dark); + matrix.reserve(x, y); + } + } +} + +/// 检查坐标是否与定位图案重叠 +fn is_near_finder(x: u8, y: u8, size: u8) -> bool { + let s = size as i16; + let x = x as i16; + let y = y as i16; + // 左上角 + if x - 2 < 7 && y - 2 < 7 { return true; } + // 右上角 + if x + 2 >= s - 7 && y - 2 < 7 { return true; } + // 左下角 + if x - 2 < 7 && y + 2 >= s - 7 { return true; } + false +} + +/// 预留格式信息区域(15 bit) +pub fn reserve_format_areas(matrix: &mut Matrix) { + let size = matrix.size; + // 定位图案旁的格式信息条 + for i in 0..9u8 { + // 左上水平 + if i != 6 { matrix.reserve(i, 8); } + // 左上垂直 + if i != 6 { matrix.reserve(8, i); } + // 右上垂直 + if i + size - 8 < size { matrix.reserve(size - 1 - i, 8); } + // 左下水平 + if i + size - 8 < size { matrix.reserve(8, size - 1 - i); } + } + // 暗模块位置 + if size > 8 { + matrix.set(8, size - 8, true); + matrix.reserve(8, size - 8); + } +} + +/// 预留版本信息区域(版本 ≥ 7,18 bit) +pub fn reserve_version_areas(matrix: &mut Matrix) { + let size = matrix.size; + for i in 0..6u8 { + for j in 0..3u8 { + // 右上角旁边 (左下方向) + matrix.reserve(size - 11 + j, i); + // 左下角旁边 (右上方向) + matrix.reserve(i, size - 11 + j); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_finder_patterns_v1() { + let mut m = Matrix::new(21); + place_finder_patterns(&mut m); + // 三个角的定位图案外边框应有暗模块 + assert!(m.get(0, 0)); + assert!(m.get(20, 0)); + assert!(m.get(0, 20)); + // 定位图案中心 (3,3) 是暗的 + assert!(m.get(3, 3)); + } + + #[test] + fn test_finder_patterns_reserved() { + let mut m = Matrix::new(21); + place_finder_patterns(&mut m); + // 定位图案区域应全被标记为保留 + assert!(m.is_reserved(0, 0)); + assert!(m.is_reserved(6, 6)); + } + + #[test] + fn test_timing_patterns_alternate() { + let mut m = Matrix::new(21); + place_timing_patterns(&mut m); + assert!(m.get(8, 6)); // 偶数 = 暗 + assert!(!m.get(9, 6)); // 奇数 = 亮 + } + + #[test] + fn test_alignment_patterns_v2() { + let mut m = Matrix::new(25); + place_alignment_patterns(&mut m, &[6, 18]); + // v2 对齐位置 [6, 18],但 (6,6) (6,18) (18,6) 都与定位图案重叠被跳过, + // 只有 (18, 18) 实际绘制 + assert!(m.get(18, 18), "对齐图案中心 (18,18) 应为暗模块"); + // 被跳过的位置不应该有对齐图案轨迹 + assert!(!m.is_reserved(6, 6), "(6,6) 与定位图案重叠,不应标记为保留"); + assert!(!m.is_reserved(6, 18), "(6,18) 与左下定位图案重叠"); + assert!(!m.is_reserved(18, 6), "(18,6) 与右上定位图案重叠"); + } +}