use crate::config::DecoderConfig;
use crate::image::Image;
use crate::img_scanner::ImageScanner;
use crate::symbol::{Bounds, Point, Symbol, SymbolType};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FinderRegion {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
}
pub struct ScanResult {
symbols: Vec<Symbol>,
finder_regions: Vec<FinderRegion>,
}
impl ScanResult {
pub(crate) fn new(symbols: Vec<Symbol>, finder_regions: Vec<FinderRegion>) -> Self {
Self {
symbols,
finder_regions,
}
}
pub fn symbols(&self) -> &[Symbol] {
&self.symbols
}
pub fn into_symbols(self) -> Vec<Symbol> {
self.symbols
}
pub fn finder_regions(&self) -> &[FinderRegion] {
&self.finder_regions
}
}
impl IntoIterator for ScanResult {
type Item = Symbol;
type IntoIter = std::vec::IntoIter<Symbol>;
fn into_iter(self) -> Self::IntoIter {
self.symbols.into_iter()
}
}
impl<'a> IntoIterator for &'a ScanResult {
type Item = &'a Symbol;
type IntoIter = std::slice::Iter<'a, Symbol>;
fn into_iter(self) -> Self::IntoIter {
self.symbols.iter()
}
}
impl std::ops::Deref for ScanResult {
type Target = [Symbol];
fn deref(&self) -> &[Symbol] {
&self.symbols
}
}
pub struct Scanner {
scanner: ImageScanner,
retries: Vec<Retry>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Retry {
UndecodedRegions,
Downscaled,
Smoothed,
}
impl Retry {
fn enabled(config: &DecoderConfig) -> Vec<Retry> {
let mut retries = Vec::new();
if config.retry_undecoded_regions {
retries.push(Retry::UndecodedRegions);
}
if config.retry_downscaled {
retries.push(Retry::Downscaled);
}
if config.retry_smoothed {
retries.push(Retry::Smoothed);
}
retries
}
}
#[derive(Debug, Clone, Copy)]
struct Mapping {
num: i32,
den: i32,
dx: i32,
dy: i32,
}
impl Mapping {
const IDENTITY: Self = Self {
num: 1,
den: 1,
dx: 0,
dy: 0,
};
fn upscaled_crop(scale: u32, x: u32, y: u32) -> Self {
Self {
num: 1,
den: scale as i32,
dx: x as i32,
dy: y as i32,
}
}
fn downscaled(scale: i32, offset: i32) -> Self {
Self {
num: scale,
den: 1,
dx: offset,
dy: offset,
}
}
fn apply(&self, pt: &mut Point) {
pt.x = (pt.x * self.num + self.den / 2) / self.den + self.dx;
pt.y = (pt.y * self.num + self.den / 2) / self.den + self.dy;
}
}
impl Scanner {
pub fn new() -> Self {
Self::with_config(DecoderConfig::all())
}
pub fn with_config(config: DecoderConfig) -> Self {
let retries = Retry::enabled(&config);
Self {
scanner: ImageScanner::with_config(config),
retries,
}
}
pub fn scan(&mut self, image: &mut Image) -> ScanResult {
let (mut symbols, raw_regions) = self.scanner.scan_image(image.as_mut_image());
let mut finder_regions: Vec<FinderRegion> = raw_regions
.into_iter()
.map(|(x, y, w, h)| FinderRegion {
x,
y,
width: w,
height: h,
})
.collect();
for i in 0..self.retries.len() {
match self.retries[i] {
Retry::UndecodedRegions => {
if !finder_regions.is_empty() {
let regions = std::mem::take(&mut finder_regions);
finder_regions = self.retry_regions(image, &mut symbols, regions);
}
}
Retry::Downscaled => self.retry_whole_image(
image,
&mut symbols,
&mut finder_regions,
Self::retry_downscaled,
),
Retry::Smoothed if !finder_regions.is_empty() => self.retry_whole_image(
image,
&mut symbols,
&mut finder_regions,
Self::retry_smoothed,
),
Retry::Smoothed => {}
}
}
ScanResult::new(symbols, finder_regions)
}
fn rescan(&mut self, derived: &mut Image, mapping: Mapping, symbols: &mut Vec<Symbol>) -> bool {
let (found, _) = self.scanner.scan_image(derived.as_mut_image());
let mut found: Vec<Symbol> = found
.into_iter()
.filter(|s| is_2d(s.symbol_type()))
.collect();
if found.is_empty() {
return false;
}
for sym in &mut found {
for pt in &mut sym.pts {
mapping.apply(pt);
}
}
merge_symbols(symbols, found);
true
}
fn retry_whole_image(
&mut self,
image: &Image,
symbols: &mut Vec<Symbol>,
finder_regions: &mut Vec<FinderRegion>,
retry: fn(&mut Self, &Image, &mut Vec<Symbol>),
) {
if symbols.iter().any(|s| is_2d(s.symbol_type())) {
return;
}
let before = symbols.len();
retry(self, image, symbols);
let recovered: Vec<Bounds> = symbols[before..]
.iter()
.filter_map(Symbol::bounds)
.collect();
finder_regions.retain(|region| !recovered.iter().any(|b| intersects(region, b)));
}
fn retry_regions(
&mut self,
image: &Image,
symbols: &mut Vec<Symbol>,
finder_regions: Vec<FinderRegion>,
) -> Vec<FinderRegion> {
const SCALES: &[u32] = &[2, 4, 6];
let apply_area_filter = image.width() >= 200 && image.height() >= 200;
let image_area = image.width() as u64 * image.height() as u64;
let area_limit = image_area / 10;
const MAX_RETRIED_REGIONS: usize = 16;
let mut unresolved: Vec<FinderRegion> = Vec::new();
let mut retried = 0usize;
for region in &finder_regions {
if retried >= MAX_RETRIED_REGIONS {
unresolved.push(*region);
continue;
}
if apply_area_filter {
let region_area = region.width as u64 * region.height as u64;
if region_area > area_limit {
unresolved.push(*region);
continue;
}
}
let pad_x = region.width / 2;
let pad_y = region.height / 2;
let cx = region.x.saturating_sub(pad_x);
let cy = region.y.saturating_sub(pad_y);
let cw = (region.width + 2 * pad_x).min(image.width().saturating_sub(cx));
let ch = (region.height + 2 * pad_y).min(image.height().saturating_sub(cy));
let Some(cropped) = image.crop(cx, cy, cw, ch) else {
unresolved.push(*region);
continue;
};
retried += 1;
let decoded = SCALES.iter().any(|&scale| {
cropped.upscale(scale).is_some_and(|mut upscaled| {
self.rescan(
&mut upscaled,
Mapping::upscaled_crop(scale, cx, cy),
symbols,
)
})
});
if !decoded {
unresolved.push(*region);
}
}
unresolved
}
fn retry_downscaled(&mut self, image: &Image, symbols: &mut Vec<Symbol>) {
const MIN_SIDE: u32 = 1024;
const MAX_STEPS: u32 = 2;
let mut scale = 1i32;
let mut offset = 0i32;
let mut current = None;
for _ in 0..MAX_STEPS {
let source = current.as_ref().unwrap_or(image);
if source.width().min(source.height()) < MIN_SIDE {
return;
}
let Some(mut smaller) = source.downscale(2) else {
return;
};
offset += 2 * scale;
scale *= 2;
if self.rescan(&mut smaller, Mapping::downscaled(scale, offset), symbols) {
return;
}
current = Some(smaller);
}
}
fn retry_smoothed(&mut self, image: &Image, symbols: &mut Vec<Symbol>) {
self.rescan(&mut image.smooth(), Mapping::IDENTITY, symbols);
}
}
fn intersects(region: &FinderRegion, bounds: &Bounds) -> bool {
let (rx0, ry0) = (region.x as i64, region.y as i64);
let (rx1, ry1) = (rx0 + region.width as i64, ry0 + region.height as i64);
let (bx0, by0) = (bounds.x as i64, bounds.y as i64);
let (bx1, by1) = (bx0 + bounds.width as i64, by0 + bounds.height as i64);
rx0 < bx1 && bx0 < rx1 && ry0 < by1 && by0 < ry1
}
fn is_2d(sym: SymbolType) -> bool {
matches!(sym, SymbolType::QrCode | SymbolType::SqCode)
}
fn merge_symbols(symbols: &mut Vec<Symbol>, retry_symbols: Vec<Symbol>) {
for sym in retry_symbols {
if !symbols
.iter()
.any(|s| s.symbol_type() == sym.symbol_type() && s.data == sym.data)
{
symbols.push(sym);
}
}
}
impl Default for Scanner {
fn default() -> Self {
Self::new()
}
}