use super::tables::{EcLevel, Version};
use crate::output::BitMatrix;
pub const QUIET_ZONE: usize = 2;
pub fn macromodule_index(mpd: usize, x: usize, y: usize) -> usize {
let m = mpd as isize;
let x = x as isize;
let y = y as isize;
let r = if x < y {
if x < m - y - 1 { x } else { m - y - 1 }
} else if y < m - x - 1 {
y
} else {
m - x - 1
};
let mm = (((m - 1) >> 1) - r) * 2 + 1; let b = mm * mm - 1;
let idx = if x == r {
b - y + r
} else if y == r {
b - (mm - 1) * 4 + (x - r)
} else if x == m - r - 1 {
b - (mm - 1) * 3 + (y - r)
} else {
b - (mm - 1) - (x - r)
};
(idx << 1) as usize
}
pub fn layer_ids(layers: usize, ec: usize) -> Vec<u8> {
(0..=layers)
.map(|i| {
if ec == 1 {
(3 - (i as u8 & 0x03)) & 0x03
} else {
((i + 5 - ec) as u8) & 0x03
}
})
.collect()
}
fn ring_of(layers: usize, x: usize, y: usize) -> usize {
let dx = x.abs_diff(layers);
let dy = y.abs_diff(layers);
dx.max(dy)
}
fn draw_frame(m: &mut BitMatrix, x: usize, y: usize) {
let xo = x * 6;
let yo = y * 6;
for i in 0..5 {
m.set(xo + i, yo, true); m.set(xo + i, yo + 5, true); m.set(xo, yo + i, true); m.set(xo + 5, yo + i, true); }
m.set(xo + 5, yo + 5, true); }
fn place_macromodule(m: &mut BitMatrix, x: usize, y: usize, cw: u32) {
let ci = x * 6 + 1;
let ri = y * 6 + 1;
if cw & 0x2000 != 0 {
m.set(ci + 2, ri, true);
}
if cw & 0x1000 != 0 {
m.set(ci + 3, ri, true);
}
for r in 1..4usize {
for c in 0..4usize {
if (cw >> (15 - r * 4 - c)) & 1 != 0 {
m.set(ci + c, ri + r, true);
}
}
}
}
fn read_macromodule(m: &BitMatrix, x: usize, y: usize) -> u32 {
let ci = x * 6 + 1;
let ri = y * 6 + 1;
let mut cw = 0u32;
if m.get(ci + 2, ri) {
cw |= 0x2000;
}
if m.get(ci + 3, ri) {
cw |= 0x1000;
}
for r in 1..4usize {
for c in 0..4usize {
if m.get(ci + c, ri + r) {
cw |= 1 << (15 - r * 4 - c);
}
}
}
cw
}
pub fn render(version: Version, ec: EcLevel, cws: &[u8]) -> BitMatrix {
let layers = version.number() as usize;
let mpd = version.macromodules_per_dim();
let size = version.size();
debug_assert_eq!(cws.len(), version.total_codewords());
let mut m = BitMatrix::new(size, size, QUIET_ZONE);
for y in 0..mpd {
for x in 0..mpd {
if (x + y) % 2 == 0 {
draw_frame(&mut m, x, y);
}
}
}
for y in 0..mpd {
for x in 0..mpd {
let idx = macromodule_index(mpd, x, y);
let cw = ((cws[idx + 1] as u32) << 7) | (cws[idx] as u32);
place_macromodule(&mut m, x, y, cw);
}
}
let ids = layer_ids(layers, ec.number());
for y in 0..mpd {
for x in 0..mpd {
let id = ids[ring_of(layers, x, y)];
let ci = x * 6 + 1;
let ri = y * 6 + 1;
if id & 0x02 != 0 {
m.set(ci, ri, true);
}
if id & 0x01 != 0 {
m.set(ci + 1, ri, true);
}
}
}
m
}
pub fn read_codewords(version: Version, m: &BitMatrix) -> Vec<u8> {
let mpd = version.macromodules_per_dim();
let mut cws = vec![0u8; version.total_codewords()];
for y in 0..mpd {
for x in 0..mpd {
let idx = macromodule_index(mpd, x, y);
let cw = read_macromodule(m, x, y);
cws[idx] = (cw & 0x7F) as u8;
cws[idx + 1] = ((cw >> 7) & 0x7F) as u8;
}
}
cws
}
pub fn read_ec_level(version: Version, m: &BitMatrix) -> Option<EcLevel> {
let layers = version.number() as usize;
let mpd = version.macromodules_per_dim();
let mut counts = vec![[0usize; 4]; layers + 1];
for y in 0..mpd {
for x in 0..mpd {
let ci = x * 6 + 1;
let ri = y * 6 + 1;
let mut id = 0usize;
if m.get(ci, ri) {
id |= 0x02;
}
if m.get(ci + 1, ri) {
id |= 0x01;
}
counts[ring_of(layers, x, y)][id] += 1;
}
}
let observed: Vec<u8> = counts
.iter()
.map(|c| {
let mut best = 0usize;
for v in 1..4 {
if c[v] > c[best] {
best = v;
}
}
best as u8
})
.collect();
let mut best_ec = None;
let mut best_score = 0usize;
for ec in EcLevel::ALL {
let expected = layer_ids(layers, ec.number());
let score = expected
.iter()
.zip(&observed)
.filter(|(a, b)| a == b)
.count();
if score > best_score {
best_score = score;
best_ec = Some(ec);
}
}
best_ec
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn macromodule_indices_are_a_permutation() {
for v in 1..=13u8 {
let ver = Version::new(v).unwrap();
let mpd = ver.macromodules_per_dim();
let mut seen = vec![false; ver.total_codewords()];
for y in 0..mpd {
for x in 0..mpd {
let idx = macromodule_index(mpd, x, y);
assert!(idx + 1 < seen.len(), "index out of range v{v}");
assert!(!seen[idx], "duplicate pair index v{v}");
assert!(!seen[idx + 1], "duplicate pair index v{v}");
seen[idx] = true;
seen[idx + 1] = true;
}
}
assert!(seen.iter().all(|&s| s), "not all codewords covered v{v}");
}
}
#[test]
fn codewords_round_trip() {
for v in 1..=13u8 {
let ver = Version::new(v).unwrap();
let total = ver.total_codewords();
let cws: Vec<u8> = (0..total).map(|i| ((i * 37 + 5) % 128) as u8).collect();
let m = render(ver, EcLevel::L3, &cws);
assert_eq!(read_codewords(ver, &m), cws, "codeword round trip v{v}");
assert_eq!(read_ec_level(ver, &m), Some(EcLevel::L3), "ec v{v}");
}
}
#[test]
fn ec_level_round_trips() {
for v in 1..=13u8 {
let ver = Version::new(v).unwrap();
let total = ver.total_codewords();
let cws: Vec<u8> = vec![0u8; total];
for ec in EcLevel::ALL {
if ver.data_codewords(ec) == 0 {
continue;
}
let m = render(ver, ec, &cws);
assert_eq!(read_ec_level(ver, &m), Some(ec), "v{v} {ec:?}");
}
}
}
}