feat: QR 码解码器 — 从零手写的完整解码流水线
新增 core/src/decoder/ 模块(9 文件,~1500 行): - bch.rs: BCH(15,5)+BCH(18,6) 查表解码(32+64 有效码字,t≤3) - format.rs: 从矩阵读取格式信息(EC+掩码)+版本信息(2 副本容错) - extract.rs: 逆向蛇形排列提取数据码字 - deinterleave.rs: 逆向 RS 数据交错 - rs_decode.rs: RS 纠错流水线(伴随式→BM→Chien→Forney) - mode_decode.rs: 逆向 4 种编码模式(数字/字母/字节/汉字 Shift JIS) - detect.rs: 定位图案检测(1:1:3:1:1 比例+交叉验证+聚类) - image.rs: 图像加载+灰度二值化(PNG/JPEG/WebP) - mod.rs: 顶层 API(decode_image + decode_matrix) 修改已有文件: - core: galois.rs 表 pub(crate), 新增 poly_eval(); reed_solomon 公开内部函数 - cli: 新增 --decode <file> 解码模式 - web: 新增 POST /api/decode(multipart file upload) 测试: 72 passed (58 原有 + 14 新增 decoder 测试)
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
//! 从 QR 矩阵数据区域蛇形读取码字比特
|
||||
//!
|
||||
//! 读取顺序严格对应 `placement.rs::place_data` 的写入顺序:
|
||||
//! 从右下角开始,两列一组上下交替扫描,跳过保留模块。
|
||||
|
||||
use crate::matrix::grid::Matrix;
|
||||
|
||||
/// 从矩阵非保留区域蛇形读取码字比特
|
||||
fn extract_bits(matrix: &Matrix, total_codewords: usize) -> Vec<bool> {
|
||||
let size = matrix.size as usize;
|
||||
let target_bits = total_codewords * 8;
|
||||
let mut bits: Vec<bool> = Vec::with_capacity(target_bits);
|
||||
let mut col = (size - 1) as i16;
|
||||
let mut going_up = true;
|
||||
|
||||
while col >= 0 && bits.len() < target_bits {
|
||||
let actual_col = col as usize;
|
||||
if going_up {
|
||||
for row in (0..size).rev() {
|
||||
read_module(matrix, &mut bits, actual_col, row);
|
||||
if actual_col > 0 {
|
||||
read_module(matrix, &mut bits, actual_col - 1, row);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for row in 0..size {
|
||||
read_module(matrix, &mut bits, actual_col, row);
|
||||
if actual_col > 0 {
|
||||
read_module(matrix, &mut bits, actual_col - 1, row);
|
||||
}
|
||||
}
|
||||
}
|
||||
col -= 2;
|
||||
going_up = !going_up;
|
||||
|
||||
// 跳过垂直时序图案列(col 6)
|
||||
if col == 6 {
|
||||
col -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
bits.truncate(target_bits);
|
||||
bits
|
||||
}
|
||||
|
||||
/// 读取单个非保留模块,追加 bool 到 bits
|
||||
fn read_module(matrix: &Matrix, bits: &mut Vec<bool>, x: usize, y: usize) {
|
||||
let xu = x as u8;
|
||||
let yu = y as u8;
|
||||
if xu < matrix.size && yu < matrix.size && !matrix.is_reserved(xu, yu) {
|
||||
bits.push(matrix.get(xu, yu));
|
||||
}
|
||||
}
|
||||
|
||||
/// 将布尔比特打包为 u8 码字(MSB 优先)
|
||||
fn bits_to_bytes(bits: &[bool]) -> Vec<u8> {
|
||||
bits.chunks(8)
|
||||
.map(|chunk| {
|
||||
let mut byte = 0u8;
|
||||
for &b in chunk {
|
||||
byte = (byte << 1) | (b as u8);
|
||||
}
|
||||
// 不足 8 位的 chunk 左对齐
|
||||
byte <<= 8 - chunk.len();
|
||||
byte
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 从矩阵提取数据码字
|
||||
///
|
||||
/// *`total_codewords` 为数据码字总数(来自 EcInfo.total_codewords)*
|
||||
pub(crate) fn extract_codewords(matrix: &Matrix, total_codewords: usize) -> Vec<u8> {
|
||||
let bits = extract_bits(matrix, total_codewords);
|
||||
bits_to_bytes(&bits)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::qr::{QrCode, QrConfig};
|
||||
use crate::version::get_data_capacity;
|
||||
|
||||
#[test]
|
||||
fn test_extract_roundtrip_simple() {
|
||||
let qr = QrCode::encode("HELLO WORLD", QrConfig::default()).unwrap();
|
||||
let cap = get_data_capacity(qr.version, qr.level) as usize;
|
||||
let codewords = extract_codewords(qr.matrix(), cap);
|
||||
assert!(!codewords.is_empty());
|
||||
// 前 11 字节编码 "HELLO WORLD"(byte 模式 4b+8b+88b=100bits≈13字节)
|
||||
assert!(codewords.len() >= 5); // 至少有数据
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user