use crate::{
Exceptions, PointCallback,
common::{BitMatrix, Result},
};
use super::AlignmentPattern;
pub struct AlignmentPatternFinder<'a> {
image: &'a BitMatrix,
possibleCenters: Vec<AlignmentPattern>,
startX: u32,
startY: u32,
width: u32,
height: u32,
moduleSize: f32,
resultPointCallback: Option<PointCallback>,
}
impl<'a> AlignmentPatternFinder<'a> {
pub fn new(
image: &'a BitMatrix,
startX: u32,
startY: u32,
width: u32,
height: u32,
moduleSize: f32,
resultPointCallback: Option<PointCallback>,
) -> Self {
Self {
image,
possibleCenters: Vec::with_capacity(5),
startX,
startY,
width,
height,
moduleSize,
resultPointCallback,
}
}
pub fn find(&mut self) -> Result<AlignmentPattern> {
let startX = self.startX;
let height = self.height;
let maxJ = startX + self.width;
let middleI = self.startY + (height / 2);
let mut stateCount = [0u32; 3];
for iGen in 0..height {
let i = (middleI as i32
+ (if (iGen & 0x01) == 0 {
(iGen as i32 + 1) / 2
} else {
-((iGen as i32 + 1) / 2)
})) as u32;
stateCount.fill(0);
let mut j = startX;
while j < maxJ && !self.image.get(j, i) {
j += 1;
}
let mut currentState = 0;
while j < maxJ {
if self.image.get(j, i) {
if currentState == 1 {
stateCount[1] += 1;
} else {
if currentState == 2 {
if self.foundPatternCross(&stateCount) {
if let Some(confirmed) =
self.handlePossibleCenter(&stateCount, i, j)
{
return Ok(confirmed);
}
}
stateCount[0] = stateCount[2];
stateCount[1] = 1;
stateCount[2] = 0;
currentState = 1;
} else {
currentState += 1;
stateCount[currentState] += 1;
}
}
} else {
if currentState == 1 {
currentState += 1;
}
stateCount[currentState] += 1;
}
j += 1;
}
if self.foundPatternCross(&stateCount) {
if let Some(confirmed) = self.handlePossibleCenter(&stateCount, i, maxJ) {
return Ok(confirmed);
}
}
}
if !self.possibleCenters.is_empty() {
Ok(*(self
.possibleCenters
.first()
.ok_or(Exceptions::INDEX_OUT_OF_BOUNDS))?)
} else {
Err(Exceptions::NOT_FOUND)
}
}
#[inline]
fn centerFromEnd(stateCount: &[u32], end: u32) -> f32 {
(end as f32 - stateCount[2] as f32) - stateCount[1] as f32 / 2.0
}
fn foundPatternCross(&self, stateCount: &[u32]) -> bool {
let moduleSize = self.moduleSize;
let maxVariance = moduleSize / 2.0;
for state in stateCount.iter().take(3) {
if (moduleSize - *state as f32).abs() >= maxVariance {
return false;
}
}
true
}
fn crossCheckVertical(
&self,
startI: u32,
centerJ: u32,
maxCount: u32,
originalStateCountTotal: u32,
) -> f32 {
let image = &self.image;
let maxI = image.getHeight();
let mut crossCheckStateCount = [0u32; 3];
let mut i = startI as i32;
while i >= 0 && image.get(centerJ, i as u32) && crossCheckStateCount[1] <= maxCount {
crossCheckStateCount[1] += 1;
i -= 1;
}
if i < 0 || crossCheckStateCount[1] > maxCount {
return f32::NAN;
}
while i >= 0 && !image.get(centerJ, i as u32) && crossCheckStateCount[0] <= maxCount {
crossCheckStateCount[0] += 1;
i -= 1;
}
if crossCheckStateCount[0] > maxCount {
return f32::NAN;
}
i = startI as i32 + 1;
while i < maxI as i32 && image.get(centerJ, i as u32) && crossCheckStateCount[1] <= maxCount
{
crossCheckStateCount[1] += 1;
i += 1;
}
if i == maxI as i32 || crossCheckStateCount[1] > maxCount {
return f32::NAN;
}
while i < maxI as i32
&& !image.get(centerJ, i as u32)
&& crossCheckStateCount[2] <= maxCount
{
crossCheckStateCount[2] += 1;
i += 1;
}
if crossCheckStateCount[2] > maxCount {
return f32::NAN;
}
let stateCountTotal =
crossCheckStateCount[0] + crossCheckStateCount[1] + crossCheckStateCount[2];
if 5 * (stateCountTotal as i64 - originalStateCountTotal as i64).unsigned_abs() as u32
>= 2 * originalStateCountTotal
{
return f32::NAN;
}
if self.foundPatternCross(&crossCheckStateCount) {
Self::centerFromEnd(&crossCheckStateCount, i as u32)
} else {
f32::NAN
}
}
fn handlePossibleCenter(
&mut self,
stateCount: &[u32],
i: u32,
j: u32,
) -> Option<AlignmentPattern> {
let stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2];
let centerJ = Self::centerFromEnd(stateCount, j);
let centerI = self.crossCheckVertical(
i,
centerJ.floor() as u32,
2 * stateCount[1],
stateCountTotal,
);
if !centerI.is_nan() {
let estimatedModuleSize = (stateCount[0] + stateCount[1] + stateCount[2]) as f32 / 3.0;
for center in &self.possibleCenters {
if center.aboutEquals(estimatedModuleSize, centerI, centerJ) {
return Some(center.combineEstimate(centerI, centerJ, estimatedModuleSize));
}
}
let point = AlignmentPattern::new(centerJ, centerI, estimatedModuleSize);
if let Some(rpc) = self.resultPointCallback.clone() {
rpc((&point).into());
}
self.possibleCenters.push(point);
}
None
}
}