use super::matrix::Matrix;
use super::tables::Version;
use crate::pbm::Bitmap;
fn trim_quiet_zone(bm: &Bitmap) -> Result<(usize, usize, usize, usize), &'static str> {
let mut y0 = None;
for y in 0..bm.height {
if (0..bm.width).any(|x| bm.get(x, y)) {
y0 = Some(y);
break;
}
}
let y0 = y0.ok_or("image is all white")?;
let mut y1 = bm.height - 1;
while y1 > y0 && !(0..bm.width).any(|x| bm.get(x, y1)) {
y1 -= 1;
}
let mut x0 = None;
for x in 0..bm.width {
if (y0..=y1).any(|y| bm.get(x, y)) {
x0 = Some(x);
break;
}
}
let x0 = x0.ok_or("image is all white")?;
let mut x1 = bm.width - 1;
while x1 > x0 && !(y0..=y1).any(|y| bm.get(x1, y)) {
x1 -= 1;
}
Ok((x0, y0, x1, y1))
}
fn first_black_run_length(bm: &Bitmap, x0: usize, y0: usize, x1: usize) -> usize {
let mut len = 0;
for x in x0..=x1 {
if bm.get(x, y0) {
len += 1;
} else {
break;
}
}
len
}
pub fn matrix_from_bitmap(bm: &Bitmap) -> Result<Matrix, &'static str> {
let (x0, y0, x1, y1) = trim_quiet_zone(bm)?;
let region_w = x1 + 1 - x0;
let region_h = y1 + 1 - y0;
if region_w != region_h {
return Err("QR region is not square");
}
let run = first_black_run_length(bm, x0, y0, x1);
if run < 7 || run % 7 != 0 {
return Err("could not detect module size from finder top edge");
}
let module_size = run / 7;
if region_w % module_size != 0 {
return Err("region size is not an integer multiple of module size");
}
let n = region_w / module_size;
if !(21..=177).contains(&n) || (n - 17) % 4 != 0 {
return Err("decoded module count is not a valid QR size");
}
let version = Version::new(((n - 17) / 4) as u8);
let mut modules = vec![false; n * n];
let center_offset = module_size / 2;
for row in 0..n {
for col in 0..n {
let cy = y0 + row * module_size + center_offset;
let cx = x0 + col * module_size + center_offset;
modules[row * n + col] = bm.get(cx, cy);
}
}
Ok(Matrix::from_modules(version, modules))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bch::EcLevel;
use crate::encode;
use crate::render::render_to_bitmap;
#[test]
fn round_trip_minimal() {
let uri = b"otpauth://totp/T:a@b?secret=JBSWY3DPEHPK3PXP&issuer=T";
let (matrix, _, _) = encode::encode(uri, EcLevel::M).unwrap();
let bm = render_to_bitmap(&matrix, 1, 0);
let recovered_matrix = matrix_from_bitmap(&bm).unwrap();
let recovered = crate::decode::decode(&recovered_matrix).unwrap();
assert_eq!(recovered.as_slice(), uri.as_ref());
}
#[test]
fn round_trip_with_quiet_zone_and_scale() {
let uri = b"otpauth://totp/Lab:lihao@golia.jp?secret=ABCDEFGHIJKLMNOP&issuer=Lab";
let (matrix, _, _) = encode::encode(uri, EcLevel::L).unwrap();
let bm = render_to_bitmap(&matrix, 3, 4); let recovered_matrix = matrix_from_bitmap(&bm).unwrap();
let recovered = crate::decode::decode(&recovered_matrix).unwrap();
assert_eq!(recovered.as_slice(), uri.as_ref());
}
}