Files
QRGen/core/src/decoder/image.rs
T
Serendipity effc88c6d7 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 测试)
2026-06-19 20:36:12 +08:00

32 lines
1.0 KiB
Rust

//! 图像加载与二值化
//!
//! 使用 `image` crate 加载 PNG/JPEG/WebP,转为灰度再二值化为布尔矩阵。
/// 从图像字节加载并二值化
///
/// 步骤:解码 → 灰度 → 按中位数阈值二值化
pub(crate) fn load_and_binarize(bytes: &[u8]) -> Result<Vec<Vec<bool>>, String> {
let img = image::load_from_memory(bytes).map_err(|e| format!("图像解码失败: {e}"))?;
let gray = img.to_luma8();
let (w, h) = gray.dimensions();
let width = w as usize;
let height = h as usize;
// 计算中位数阈值
let mut all_pixels: Vec<u8> = gray.iter().copied().collect();
all_pixels.sort_unstable();
let threshold = all_pixels[all_pixels.len() / 2];
// 二值化:像素 < 阈值 → true(暗模块),否则 false(亮模块)
let matrix: Vec<Vec<bool>> = (0..height)
.map(|y| {
(0..width)
.map(|x| gray.get_pixel(x as u32, y as u32).0[0] < threshold)
.collect()
})
.collect();
Ok(matrix)
}