use crate::{
common::{
cpp_essentials::{
Direction, FixedPattern, IsPattern, PatternRow, PatternType, PatternView,
},
BitMatrix, Quadrilateral,
},
point_f, Point,
};
use super::{
BitMatrixCursorTrait, EdgeTracer, FastEdgeToEdgeCounter, Pattern, RegressionLine,
RegressionLineTrait, UpdateMinMax, UpdateMinMaxFloat,
};
pub fn CenterFromEnd<const N: usize, T: Into<f32> + std::iter::Sum<T> + Copy>(
pattern: &[T; N],
end: f32,
) -> f32 {
if N == 5 {
let a: f32 = pattern[4].into() + pattern[3].into() + pattern[2].into() / 2.0;
let b: f32 =
pattern[4].into() + (pattern[3].into() + pattern[2].into() + pattern[1].into()) / 2.0;
let c: f32 = (pattern[4].into()
+ pattern[3].into()
+ pattern[2].into()
+ pattern[1].into()
+ pattern[0].into())
/ 2.0;
end - (2.0 * a + b + c) / 4.0
} else if N == 3 {
let a: f32 = pattern[2].into() + pattern[1].into() / 2.0;
let b: f32 = (pattern[2].into() + pattern[1].into() + pattern[0].into()) / 2.0;
end - (2.0 * a + b) / 3.0
} else {
let a: f32 =
pattern.iter().skip(N / 2 + 1).copied().sum::<T>().into() + pattern[N / 2].into() / 2.0;
end - a
}
}
pub fn ReadSymmetricPattern<const N: usize, Cursor: BitMatrixCursorTrait>(
cur: &mut Cursor,
range: i32,
) -> Option<Pattern<N>> {
assert!(N % 2 == 1);
assert!(range > 0);
let mut range = range;
let mut res: Pattern<N> = [0; N];
let s_2 = res.len() as isize / 2;
let mut cuo = cur.turnedBack();
let mut next = |cur: &mut Cursor, i: isize| {
let v = cur.stepToEdge(Some(1), Some(range), None);
res[(s_2 + i) as usize] = (res[(s_2 + i) as usize] as i32 + v) as u16;
if range != 0 {
range -= v;
}
v
};
for i in 0..=s_2 {
if !next(cur, i) == 0 || !next(&mut cuo, -i) == 0 {
return None;
}
}
res[s_2 as usize] -= 1;
Some(res)
}
pub fn CheckSymmetricPattern<
const E2E: bool,
const LEN: usize,
const SUM: usize,
T: BitMatrixCursorTrait,
>(
cur: &mut T,
pattern: &Pattern<LEN>,
range: i32,
updatePosition: bool,
) -> i32 {
let mut range = range;
let mut curFwd: FastEdgeToEdgeCounter = FastEdgeToEdgeCounter::new(cur);
let binding = cur.turnedBack();
let mut curBwd: FastEdgeToEdgeCounter = FastEdgeToEdgeCounter::new(&binding);
let centerFwd = curFwd.stepToNextEdge(range as u32) as i32;
if centerFwd == 0 {
return 0;
}
let centerBwd = curBwd.stepToNextEdge(range as u32) as i32;
if centerBwd == 0 {
return 0;
}
assert!(range > 0);
let mut res: PatternRow = PatternRow::new(vec![0; LEN]);
let s_2 = (res.len()) / 2;
res[s_2] = (centerFwd + centerBwd - 1) as u16; range -= res[s_2] as i32;
let mut next = |cur: &mut FastEdgeToEdgeCounter, i: isize| {
let v = cur.stepToNextEdge(range as u32) as i32;
res[(s_2 as isize + i) as usize] = v as u16;
range -= v;
v
};
for i in 1..=s_2 {
if next(&mut curFwd, i as isize) == 0 || next(&mut curBwd, -(i as isize)) == 0 {
return 0;
}
}
if IsPattern::<E2E, LEN, SUM, false>(
&PatternView::new(&res),
&FixedPattern::<LEN, SUM, false>::with_reference(pattern),
None,
0.0,
0.0,
) == 0.0
{
return 0;
}
if updatePosition {
cur.step(Some((res[s_2] as i32 / 2 - (centerBwd - 1)) as f32));
}
res.into_iter().sum::<PatternType>() as i32
}
pub fn AverageEdgePixels<T: BitMatrixCursorTrait>(
cur: &mut T,
range: i32,
numOfEdges: u32,
) -> Option<Point> {
let mut sum = Point::default();
for _i in 0..numOfEdges {
if !cur.isInSelf() {
return None;
}
cur.stepToEdge(Some(1), Some(range), None);
sum += cur.p().centered() + (cur.p() + cur.back()).centered()
}
Some(sum / (2 * numOfEdges) as f32)
}
pub fn CenterOfDoubleCross(
image: &BitMatrix,
center: Point,
range: i32,
numOfEdges: u32,
) -> Option<Point> {
let mut sum = Point::default();
for d in [
point_f(0.0, 1.0),
point_f(1.0, 0.0),
point_f(1.0, 1.0),
point_f(1.0, -1.0),
] {
let avr1 = AverageEdgePixels(&mut EdgeTracer::new(image, center, d), range, numOfEdges)?;
let avr2 = AverageEdgePixels(&mut EdgeTracer::new(image, center, -d), range, numOfEdges)?;
sum += avr1 + avr2;
}
Some(sum / 8.0)
}
pub fn CenterOfRing(
image: &BitMatrix,
center: Point,
range: i32,
nth: i32,
requireCircle: bool,
) -> Option<Point> {
let radius = range;
let inner = nth < 0;
let nth = nth.abs();
let mut cur = EdgeTracer::new(image, center, point_f(0.0, 1.0));
if cur.stepToEdge(Some(nth), Some(radius), Some(inner)) == 0 {
return None;
}
cur.turnRight(); let edgeDir = if inner {
Direction::Left
} else {
Direction::Right
};
let mut neighbourMask = 0;
let start = cur.p();
let mut sum = Point::default();
let mut n = 0;
loop {
sum += cur.p().centered();
n += 1;
neighbourMask |= 1
<< (4.0
+ Point::dot(
Point::floor(Point::bresenhamDirection(cur.p() - center)),
point_f(1.0, 3.0),
)) as u32;
if !cur.stepAlongEdge(edgeDir, None) {
return None;
}
if Point::maxAbsComponent(cur.p - center) > radius as f32
|| center == cur.p
|| n > 4 * 2 * range
{
return None;
}
if !(cur.p != start) {
break;
}
}
if requireCircle && neighbourMask != 0b111101111 {
return None;
}
Some(sum / n as f32)
}
pub fn CenterOfRings(
image: &BitMatrix,
center: Point,
range: i32,
numOfRings: u32,
) -> Option<Point> {
let mut n = 1;
let mut sum = center;
for i in 2..(numOfRings + 1) {
let c = CenterOfRing(image, center.floor(), range, i as i32, true)?;
if c == Point::default() {
if n == 1 {
return None;
} else {
return Some(sum / n as f32);
}
} else if Point::distance(c, center) > range as f32 / numOfRings as f32 / 2.0 {
return None;
}
sum += c;
n += 1;
}
Some(sum / n as f32)
}
pub fn CollectRingPoints(
image: &BitMatrix,
center: Point,
range: i32,
edgeIndex: i32,
backup: bool,
) -> Vec<Point> {
let centerI = center.floor();
let radius = range;
let mut cur = EdgeTracer::new(image, centerI, point_f(0.0, 1.0));
if cur.stepToEdge(Some(edgeIndex), Some(radius), Some(backup)) == 0 {
return Vec::default();
}
cur.turnRight(); let edgeDir = if backup {
Direction::Left
} else {
Direction::Right
};
let mut neighbourMask = 0;
let start = cur.p();
let mut points = Vec::<Point>::with_capacity(4 * range as usize);
loop {
points.push(cur.p().centered());
neighbourMask |= 1
<< (4.0
+ Point::dot(
Point::round(Point::bresenhamDirection(cur.p - centerI)),
point_f(1.0, 3.0),
)) as u32;
if !cur.stepAlongEdge(edgeDir, None) {
return Vec::default();
}
if Point::maxAbsComponent(cur.p - centerI) > radius as f32
|| centerI == cur.p
|| (points).len() > 4 * 2 * range as usize
{
return Vec::default();
}
if !(cur.p != start) {
break;
}
}
if neighbourMask != 0b111101111 {
return Vec::default();
}
points
}
pub fn FitQadrilateralToPoints(center: Point, points: &mut [Point]) -> Option<Quadrilateral> {
let max_by_pred = |a: &&Point, b: &&Point| {
let da = Point::distance(**a, center);
let db = Point::distance(**b, center);
da.partial_cmp(&db).unwrap()
};
let max = points.iter().max_by(max_by_pred)?;
let pos = points.iter().position(|e| e == max)?;
points.rotate_left(pos);
let mut corners = [Point::default(); 4];
corners[0] = points[0];
corners[2] = *points[(points.len() * 3 / 8)..=(points.len() * 5 / 8)]
.iter()
.max_by(max_by_pred)?;
let l = RegressionLine::with_two_points(corners[0], corners[2]);
let diagonal_max_by_pred = |p1: &Point, p2: &Point| {
let d1 = l.distance_single(*p1);
let d2 = l.distance_single(*p2);
d1.partial_cmp(&d2).unwrap()
};
corners[1] = points[(points.len() / 8)..=(points.len() * 3 / 8)]
.iter()
.copied()
.max_by(diagonal_max_by_pred)?;
corners[3] = points[(points.len() * 5 / 8)..=(points.len() * 7 / 8)]
.iter()
.copied()
.max_by(diagonal_max_by_pred)?;
let corner_positions = [
0,
points.iter().position(|p| *p == corners[1])?,
points.iter().position(|p| *p == corners[2])?,
points.iter().position(|p| *p == corners[3])?,
];
let try_get_range = |a: usize, b: usize| -> Option<&[Point]> {
if a > b {
None
}
else if a + 1 >= points.len() || b >= points.len() {
if a + 1 >= points.len() {
None
} else {
Some(&points[a..])
}
}
else if a == b {
Some(&points[a..b])
} else {
Some(&points[a + 1..b])
}
};
let lines = [
RegressionLine::with_point_slice(try_get_range(corner_positions[0], corner_positions[1])?),
RegressionLine::with_point_slice(try_get_range(corner_positions[1], corner_positions[2])?),
RegressionLine::with_point_slice(try_get_range(corner_positions[2], corner_positions[3])?),
RegressionLine::with_point_slice(try_get_range(corner_positions[3], points.len())?),
];
if lines.iter().any(|line| !line.isValid()) {
return None;
}
let beg: [usize; 4] = [
corner_positions[0] + 1,
corner_positions[1] + 1,
corner_positions[2] + 1,
corner_positions[3] + 1,
];
let end: [usize; 4] = [
corner_positions[1],
corner_positions[2],
corner_positions[3],
points.len(),
];
for i in 0..4 {
for p in &points[beg[i]..end[i]] {
let len = (end[i] - beg[i]) as f64; if len > 3.0
&& (lines[i].distance_single(*p) as f64) > f64::max(1.0, f64::min(8.0, len / 8.0))
{
return None;
}
}
}
let mut res = Quadrilateral::default();
for i in 0..4 {
res[i] = RegressionLine::intersect(&lines[i], &lines[(i + 1) % 4])?;
}
Some(res)
}
pub fn QuadrilateralIsPlausibleSquare(q: &Quadrilateral, lineIndex: usize) -> bool {
let mut m;
m = Point::distance(q[0], q[3]) as f64; let mut M = m;
for i in 1..4 {
UpdateMinMaxFloat(&mut m, &mut M, Point::distance(q[i - 1], q[i]) as f64);
}
m >= (lineIndex * 2) as f64 && m > M / 3.0
}
pub fn FitSquareToPoints(
image: &BitMatrix,
center: Point,
range: i32,
lineIndex: i32,
backup: bool,
) -> Option<Quadrilateral> {
let mut points = CollectRingPoints(image, center, range, lineIndex, backup);
if points.is_empty() {
return None;
}
let res = FitQadrilateralToPoints(center, &mut points)?;
if !QuadrilateralIsPlausibleSquare(&res, (lineIndex - i32::from(backup)) as usize) {
return None;
}
Some(res)
}
pub fn FindConcentricPatternCorners(
image: &BitMatrix,
center: Point,
range: i32,
lineIndex: i32,
) -> Option<Quadrilateral> {
let innerCorners = FitSquareToPoints(image, center, range, lineIndex, false)?;
let outerCorners = FitSquareToPoints(image, center, range, lineIndex + 1, true)?;
let res = Quadrilateral::blend(&innerCorners, &outerCorners);
Some(res)
}
#[derive(Default, Copy, Clone, Eq, PartialEq, Debug)]
pub struct ConcentricPattern {
pub p: Point,
pub size: i32,
}
impl std::ops::Sub for ConcentricPattern {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
let new_p = self.p - rhs.p;
Self {
p: new_p,
size: self.size,
}
}
}
impl std::ops::Add for ConcentricPattern {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
let new_p = self.p + rhs.p;
Self {
p: new_p,
size: self.size,
}
}
}
impl From<Point> for ConcentricPattern {
fn from(value: Point) -> Self {
Self { p: value, size: 0 }
}
}
impl ConcentricPattern {
pub fn dot(self, other: ConcentricPattern) -> f32 {
Point::dot(self.p, other.p)
}
pub fn cross(self, other: ConcentricPattern) -> f32 {
Point::cross(self.p, other.p)
}
pub fn distance(self, other: ConcentricPattern) -> f32 {
Point::distance(self.p, other.p)
}
}
pub fn LocateConcentricPattern<const E2E: bool, const LEN: usize, const SUM: usize>(
image: &BitMatrix,
pattern: &Pattern<LEN>,
center: Point,
range: i32,
) -> Option<ConcentricPattern> {
let mut cur = EdgeTracer::new(image, center.floor(), Point::default());
let mut minSpread = image.getWidth() as i32;
let mut maxSpread = 0_i32;
let mut maxError = 0;
for d in [point_f(0.0, 1.0), point_f(1.0, 0.0)] {
cur.setDirection(d);
let spread = CheckSymmetricPattern::<E2E, LEN, SUM, _>(&mut cur, pattern, range, true);
if spread != 0 {
UpdateMinMax(&mut minSpread, &mut maxSpread, spread);
} else {
maxError -= 1;
if maxError < 0 {
return None;
}
}
}
for d in [point_f(1.0, 1.0), point_f(1.0, -1.0)] {
cur.setDirection(d); let spread = CheckSymmetricPattern::<E2E, LEN, SUM, _>(&mut cur, pattern, range * 2, false);
if spread != 0 {
UpdateMinMax(&mut minSpread, &mut maxSpread, spread);
} else {
maxError -= 1;
if maxError < 0 {
return None;
}
}
}
if maxSpread > 5 * minSpread {
return None;
}
let newCenter = FinetuneConcentricPatternCenter(image, cur.p(), range, pattern.len() as u32)?;
Some(ConcentricPattern {
p: newCenter,
size: (maxSpread + minSpread) / 2,
})
}
pub fn FinetuneConcentricPatternCenter(
image: &BitMatrix,
center: Point,
range: i32,
finderPatternSize: u32,
) -> Option<Point> {
if let Some(res1) = CenterOfRing(image, center.floor(), range, 1, true) {
if !image.get_point(res1) {
return None;
}
if let Some(res2) = CenterOfRings(image, res1, range, finderPatternSize / 2) {
return if image.get_point(res2) {
Some(res2)
} else {
None
};
}
if FitSquareToPoints(image, res1, range, 1, false).is_some() {
return Some(res1);
}
if let Some(res2) =
CenterOfDoubleCross(image, res1.floor(), range, finderPatternSize / 2 + 1)
{
return if image.get_point(res2) {
Some(res2)
} else {
None
};
}
}
None
}