mod finder;
mod grid;
mod tiles;
use crate::geometry::{Location, Point, Quad};
use crate::image::GrayFrame;
use crate::pipeline::{Candidate, Fingerprint, Hints};
use crate::symbology::Symbology;
use crate::traits::Detect;
use finder::FinderHit;
use grid::DownGrid;
use tiles::{Family, Region};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Families {
pub matrix: bool,
pub linear: bool,
}
impl Families {
pub const ALL: Families = Families {
matrix: true,
linear: true,
};
pub const MATRIX: Families = Families {
matrix: true,
linear: false,
};
pub const LINEAR: Families = Families {
matrix: false,
linear: true,
};
fn allows(self, family: Family) -> bool {
match family {
Family::Matrix => self.matrix,
Family::Linear => self.linear,
}
}
}
impl Default for Families {
fn default() -> Self {
Families::ALL
}
}
#[derive(Debug, Clone, Copy)]
pub struct LocateOptions {
pub downscale: usize,
pub families: Families,
pub max_candidates: usize,
pub tile: usize,
pub edge_density: f32,
pub min_region_tiles: usize,
pub anisotropy: f32,
pub seed: u64,
}
impl Default for LocateOptions {
fn default() -> Self {
LocateOptions {
downscale: 2,
families: Families::ALL,
max_candidates: 16,
tile: 8,
edge_density: 0.12,
min_region_tiles: 3,
anisotropy: 0.55,
seed: 0x_D0D0_CAFE,
}
}
}
pub fn locate(frame: &GrayFrame<'_>, opts: &LocateOptions) -> Vec<Candidate> {
if frame.width() < 8 || frame.height() < 8 {
return Vec::new();
}
let grid = DownGrid::build(frame, opts.downscale);
let finders = finder::find(&grid);
let mut regions = tiles::regions(
&grid,
opts.tile,
opts.edge_density,
opts.min_region_tiles,
opts.anisotropy,
);
regions.sort_by_key(|r| std::cmp::Reverse(r.area()));
let scale = grid.scale as f32;
let mut out: Vec<Candidate> = Vec::new();
for region in ®ions {
if out.len() >= opts.max_candidates {
break;
}
let hit = enclosing_finder(&finders, region);
let family = if hit.is_some() {
Family::Matrix
} else {
region.family
};
if !opts.families.allows(family) {
continue;
}
let symbology = match (family, hit.is_some()) {
(Family::Matrix, true) => Symbology::QrCode,
(Family::Matrix, false) => Symbology::DataMatrix,
(Family::Linear, _) => Symbology::Code128,
};
let module_size = hit.map(|h| h.module * scale);
let outline = region_quad(region, scale);
let location = Location {
outline,
rotation: None,
module_size,
};
out.push(Candidate {
location,
symbology: Some(symbology),
fingerprint: Some(fingerprint(&grid, region, family)),
known: None,
});
}
out
}
fn enclosing_finder<'a>(finders: &'a [FinderHit], region: &Region) -> Option<&'a FinderHit> {
finders
.iter()
.filter(|f| {
let x = f.x as usize;
let y = f.y as usize;
x >= region.x0 && x < region.x1 && y >= region.y0 && y < region.y1
})
.max_by_key(|f| f.count)
}
fn region_quad(region: &Region, scale: f32) -> Quad {
let x0 = region.x0 as f32 * scale;
let y0 = region.y0 as f32 * scale;
let x1 = region.x1 as f32 * scale;
let y1 = region.y1 as f32 * scale;
Quad::new([
Point::new(x0, y0),
Point::new(x1, y0),
Point::new(x1, y1),
Point::new(x0, y1),
])
}
fn fingerprint(grid: &DownGrid, region: &Region, family: Family) -> Fingerprint {
let mut bits: u64 = 0;
let w = (region.x1 - region.x0).max(1);
let h = (region.y1 - region.y0).max(1);
let mut sum = 0u64;
let mut n = 0u64;
for gy in 0..4 {
for gx in 0..4 {
let x = region.x0 + gx * w / 4 + w / 8;
let y = region.y0 + gy * h / 4 + h / 8;
sum += u64::from(grid.luma(x, y));
n += 1;
}
}
let mean = (sum / n.max(1)) as u8;
let mut i = 0;
for gy in 0..4 {
for gx in 0..4 {
let x = region.x0 + gx * w / 4 + w / 8;
let y = region.y0 + gy * h / 4 + h / 8;
if grid.luma(x, y) <= mean {
bits |= 1 << i;
}
i += 1;
}
}
let tag: u64 = match family {
Family::Matrix => 0x1,
Family::Linear => 0x2,
};
let mut v = bits | (tag << 62);
v ^= v >> 33;
v = v.wrapping_mul(0xFF51_AFD7_ED55_8CCD);
v ^= v >> 33;
Fingerprint(v)
}
#[derive(Debug, Clone, Copy, Default)]
pub struct FrameDetector {
pub options: LocateOptions,
}
impl FrameDetector {
pub fn new() -> Self {
FrameDetector::default()
}
pub fn with_options(options: LocateOptions) -> Self {
FrameDetector { options }
}
}
impl Detect for FrameDetector {
fn detect(&self, frame: &GrayFrame<'_>, hints: &Hints) -> Vec<Candidate> {
let mut candidates = locate(frame, &self.options);
for c in &mut candidates {
if let Some(fp) = c.fingerprint
&& let Some(known) = hints.find(fp)
{
c.known = Some(known.symbol.clone());
}
}
candidates
}
}