use super::matrix::Matrix;
pub fn mask_condition(mask: u8, row: usize, col: usize) -> bool {
let r = row as i32;
let c = col as i32;
match mask {
0 => (r + c) % 2 == 0,
1 => r % 2 == 0,
2 => c % 3 == 0,
3 => (r + c) % 3 == 0,
4 => (r / 2 + c / 3) % 2 == 0,
5 => (r * c) % 2 + (r * c) % 3 == 0,
6 => ((r * c) % 2 + (r * c) % 3) % 2 == 0,
7 => ((r + c) % 2 + (r * c) % 3) % 2 == 0,
_ => panic!("mask out of range: {}", mask),
}
}
pub fn apply_mask(matrix: &mut Matrix, mask: u8) {
let n = matrix.size;
for r in 0..n {
for c in 0..n {
if matrix.is_reserved(r, c) {
continue;
}
if mask_condition(mask, r, c) {
let v = matrix.get(r, c);
matrix.set_data(r, c, !v);
}
}
}
}
#[allow(dead_code)]
fn build_flip_table(matrix: &Matrix, mask: u8, format_info: u32) -> Vec<bool> {
let n = matrix.size;
let mut flip = vec![false; n * n];
for r in 0..n {
for c in 0..n {
if !matrix.is_reserved(r, c) && mask_condition(mask, r, c) {
flip[r * n + c] = true;
}
}
}
let get_bit = |k: u32| ((format_info >> k) & 1) == 1;
for i in 0u32..6 {
flip[(i as usize) * n + 8] = get_bit(i);
}
flip[7 * n + 8] = get_bit(6);
flip[8 * n + 8] = get_bit(7);
flip[8 * n + 7] = get_bit(8);
for i in 9u32..15 {
let col = (14 - i) as usize;
flip[8 * n + col] = get_bit(i);
}
for i in 0u32..8 {
flip[8 * n + (n - 1 - i as usize)] = get_bit(i);
}
for i in 8u32..15 {
let row = n - 15 + i as usize;
flip[row * n + 8] = get_bit(i);
}
flip
}
fn row_pass<F: Fn(usize, usize) -> bool>(n: usize, get: F) -> (u32, u32, usize, u32) {
const PATTERN_A: u16 = 0b101_1101_0000;
const PATTERN_B: u16 = 0b000_0101_1101;
const MASK_11: u16 = 0x7FF;
let mut rule1 = 0u32;
let mut rule2 = 0u32;
let mut rule3 = 0u32;
let mut dark = 0usize;
let mut prev_row = vec![false; n];
let mut cur_row = vec![false; n];
for r in 0..n {
for c in 0..n {
cur_row[c] = get(r, c);
}
let mut run_color = cur_row[0];
let mut run_len = 1u32;
let mut window: u16 = if run_color { 1 } else { 0 };
if run_color {
dark += 1;
}
for c in 1..n {
let v = cur_row[c];
if v {
dark += 1;
}
window = ((window << 1) | v as u16) & MASK_11;
if c >= 10 && (window == PATTERN_A || window == PATTERN_B) {
rule3 += 40;
}
if v == run_color {
run_len += 1;
} else {
if run_len >= 5 {
rule1 += 3 + (run_len - 5);
}
run_color = v;
run_len = 1;
}
}
if run_len >= 5 {
rule1 += 3 + (run_len - 5);
}
if r > 0 {
for c in 0..n - 1 {
let a = prev_row[c];
if prev_row[c + 1] == a && cur_row[c] == a && cur_row[c + 1] == a {
rule2 += 3;
}
}
}
std::mem::swap(&mut prev_row, &mut cur_row);
}
(rule1, rule2, dark, rule3)
}
fn col_pass<F: Fn(usize, usize) -> bool>(n: usize, get: F) -> (u32, u32) {
const PATTERN_A: u16 = 0b101_1101_0000;
const PATTERN_B: u16 = 0b000_0101_1101;
const MASK_11: u16 = 0x7FF;
let mut rule1 = 0u32;
let mut rule3 = 0u32;
for c in 0..n {
let first = get(0, c);
let mut run_color = first;
let mut run_len = 1u32;
let mut window: u16 = if first { 1 } else { 0 };
for r in 1..n {
let v = get(r, c);
window = ((window << 1) | v as u16) & MASK_11;
if r >= 10 && (window == PATTERN_A || window == PATTERN_B) {
rule3 += 40;
}
if v == run_color {
run_len += 1;
} else {
if run_len >= 5 {
rule1 += 3 + (run_len - 5);
}
run_color = v;
run_len = 1;
}
}
if run_len >= 5 {
rule1 += 3 + (run_len - 5);
}
}
(rule1, rule3)
}
fn rule4_from_dark(dark: usize, total_modules: usize) -> u32 {
let percent = (dark * 100) / total_modules;
let deviation = if percent >= 50 { percent - 50 } else { 50 - percent };
(deviation as u32 / 5) * 10
}
pub fn score(matrix: &Matrix) -> u32 {
let n = matrix.size;
let (r1h, r2, dark, r3h) = row_pass(n, |r, c| matrix.get(r, c));
let (r1v, r3v) = col_pass(n, |r, c| matrix.get(r, c));
let r4 = rule4_from_dark(dark, n * n);
r1h + r1v + r2 + r3h + r3v + r4
}
#[allow(dead_code)]
pub fn score_with_mask(matrix: &Matrix, mask: u8, format_info: u32) -> u32 {
let n = matrix.size;
let flip = build_flip_table(matrix, mask, format_info);
let get = |r: usize, c: usize| matrix.get(r, c) ^ flip[r * n + c];
let (r1h, r2, dark, r3h) = row_pass(n, get);
let (r1v, r3v) = col_pass(n, get);
let r4 = rule4_from_dark(dark, n * n);
r1h + r1v + r2 + r3h + r3v + r4
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tables::Version;
#[test]
fn mask_self_inverse() {
let mut m = Matrix::new(Version::new(1));
let mut data_cells = Vec::new();
for r in 0..m.size {
for c in 0..m.size {
if !m.is_reserved(r, c) {
let v = (r * 3 + c * 7) % 2 == 0;
m.set_data(r, c, v);
data_cells.push((r, c, v));
}
}
}
for mask in 0..8u8 {
apply_mask(&mut m, mask);
apply_mask(&mut m, mask);
for &(r, c, v) in &data_cells {
assert_eq!(m.get(r, c), v, "mask {} 双向 XOR 后 ({},{}) 不还原", mask, r, c);
}
}
}
#[test]
fn mask_condition_table() {
assert!(mask_condition(0, 0, 0));
assert!(!mask_condition(0, 0, 1));
assert!(mask_condition(1, 0, 5));
assert!(!mask_condition(1, 1, 5));
assert!(mask_condition(2, 7, 0));
assert!(!mask_condition(2, 7, 1));
}
#[test]
fn rule1_long_run() {
let mut m = Matrix::new(Version::new(1));
for c in 9..17 {
if !m.is_reserved(10, c) {
m.set_data(10, c, true);
}
}
let s = score(&m);
assert!(s >= 6, "score was {}", s);
}
#[test]
fn virtual_mask_score_equals_physical() {
use crate::bch::{EcLevel, encode_format};
use crate::encode::write_format_info_bits;
use crate::tables::Version;
for v in [1u8, 2, 3, 7, 11] {
let mut m = Matrix::new(Version::new(v));
for r in 0..m.size {
for c in 0..m.size {
if !m.is_reserved(r, c) {
m.set_data(r, c, (r * 7 + c * 13) % 5 < 2);
}
}
}
for level in [EcLevel::L, EcLevel::M, EcLevel::Q, EcLevel::H] {
for mask in 0u8..8 {
let fmt = encode_format(level, mask);
let mut physical = m.clone();
apply_mask(&mut physical, mask);
write_format_info_bits(&mut physical, fmt);
let s_physical = score(&physical);
let s_virtual = score_with_mask(&m, mask, fmt);
assert_eq!(
s_physical, s_virtual,
"v{} {:?} mask {} differ",
v, level, mask
);
}
}
}
}
#[test]
fn select_best_mask_picks_low_score() {
let mut m = Matrix::new(Version::new(1));
for r in 0..m.size {
for c in 0..m.size {
if !m.is_reserved(r, c) {
m.set_data(r, c, (r * 13 + c * 31) % 7 < 3);
}
}
}
let (mask, score_val) = select_best_mask(&mut m, |_m, _mask| {
});
assert!(mask < 8);
assert!(score_val < 10_000); }
}
#[allow(dead_code)]
pub fn select_best_mask<F>(matrix: &mut Matrix, mut write_format_info: F) -> (u8, u32)
where
F: FnMut(&mut Matrix, u8),
{
let mut best = (0u8, u32::MAX);
for m in 0u8..8 {
apply_mask(matrix, m);
write_format_info(matrix, m);
let s = score(matrix);
if s < best.1 {
best = (m, s);
}
apply_mask(matrix, m); }
apply_mask(matrix, best.0);
write_format_info(matrix, best.0);
best
}