#![allow(dead_code)]
use num::integer::Roots;
use crate::{
common::{
BitMatrix, DefaultGridSampler, DetectorRXingResult, GridSampler, Quadrilateral, Result,
},
point, point_f, Exceptions, Point, PointU,
};
use super::MaxiCodeReader;
const ROW_SCAN_SKIP: u32 = 2;
#[derive(Debug)]
pub struct MaxicodeDetectionResult {
bits: BitMatrix,
points: Vec<Point>,
rotation: f32,
}
impl MaxicodeDetectionResult {
pub fn rotation(&self) -> f32 {
self.rotation
}
}
impl DetectorRXingResult for MaxicodeDetectionResult {
fn getBits(&self) -> &BitMatrix {
&self.bits
}
fn getPoints(&self) -> &[Point] {
&self.points
}
}
struct Circle<'a> {
center: PointU,
radius: u32,
horizontal_buckets: [u32; 11],
vertical_buckets: [u32; 11],
image: &'a BitMatrix,
}
impl Circle<'_> {
pub fn calculate_circle_variance(&self) -> f32 {
let total_width_even = self
.horizontal_buckets
.iter()
.zip(self.vertical_buckets.iter())
.enumerate()
.filter_map(|e| {
if e.0 != 5 && (e.0 == 0 || e.0 % 2 == 0) {
Some(*e.1 .0 + *e.1 .1)
} else {
None
}
})
.sum::<u32>() as f32;
let total_width_odd = self
.horizontal_buckets
.iter()
.zip(self.vertical_buckets.iter())
.enumerate()
.filter_map(|e| {
if e.0 != 5 && (e.0 != 0 && e.0 % 2 != 0) {
Some(*e.1 .0 + *e.1 .1)
} else {
None
}
})
.sum::<u32>() as f32;
let estimated_module_size_even = total_width_even / 10.0;
let estimated_module_size_odd = total_width_odd / 10.0;
let total_variance_even = self
.horizontal_buckets
.iter()
.enumerate()
.filter(|p| p.0 != 5 && (p.0 == 0 || p.0 % 2 == 0))
.fold(0.0, |acc, (_, module_size)| {
acc + (estimated_module_size_even - *module_size as f32).abs()
});
let total_variance_odd = self
.horizontal_buckets
.iter()
.enumerate()
.filter(|p| p.0 != 5 && (p.0 != 0 || p.0 % 2 != 0))
.fold(0.0, |acc, (_, module_size)| {
acc + (estimated_module_size_odd - *module_size as f32).abs()
});
let expected_area_vertical =
(self.horizontal_buckets[5] / 2).pow(2) as f32 * std::f32::consts::PI;
let expected_area_horizontal =
(self.vertical_buckets[5] / 2).pow(2) as f32 * std::f32::consts::PI;
let circle_area_average = (expected_area_horizontal + expected_area_vertical) / 2.0;
let circle_area_variance = (expected_area_horizontal - circle_area_average).abs()
+ (expected_area_vertical - circle_area_average).abs();
(total_variance_even + total_variance_odd + circle_area_variance) / 3.0
}
pub fn calculate_center_point_std_dev(circles: &[Self]) -> ((u32, u32), (u32, u32)) {
let (x_total, y_total) = circles.iter().fold((0, 0), |(x_acc, y_acc), c| {
(x_acc + c.center.x, y_acc + c.center.y)
});
let x_mean = x_total as f64 / circles.len() as f64;
let y_mean = y_total as f64 / circles.len() as f64;
let (x_squared_variances, y_squared_variances) =
circles.iter().fold((0.0, 0.0), |(x_acc, y_acc), c| {
(
x_acc + (c.center.x as f64 - x_mean).powf(2.0),
y_acc + (c.center.y as f64 - y_mean).powf(2.0),
)
});
let x_squared_variance_mean = x_squared_variances / circles.len() as f64;
let y_squared_variance_mean = y_squared_variances / circles.len() as f64;
let x_standard_deviation = x_squared_variance_mean.sqrt();
let y_standard_deviation = y_squared_variance_mean.sqrt();
(
(x_standard_deviation as u32, y_standard_deviation as u32),
(x_mean as u32, y_mean as u32),
)
}
pub fn calculate_high_accuracy_center(&mut self) {
let [point_1, point_2] = self.find_width_at_degree(7.0).1;
let point_3 = self.find_width_at_degree(97.0).1[0];
let guessed_center_point = Self::find_center(point_1, point_2, point_3);
self.center = guessed_center_point.round().into();
}
pub fn detect_ellipse(&self) -> (bool, PointU, u32, u32, u32) {
let mut lengths = [(0, 0.0, [Point::default(); 2]); 72];
let mut circle_points = Vec::new();
for (i_rotation, length_set) in lengths.iter_mut().enumerate() {
let rotation = i_rotation as f32 * 5.0;
let (length, points) = self.find_width_at_degree(rotation);
circle_points.extend_from_slice(&points);
*length_set = (length, rotation, points);
}
lengths.sort_by_key(|e| e.0);
let Some(major_axis) = lengths.last() else {
return (false, PointU::default(), 0, 0, 0);
};
let Some(minor_axis) = lengths.first() else {
return (false, PointU::default(), 0, 0, 0);
};
let linear_eccentricity = ((major_axis.0 / 2).pow(2) - (minor_axis.0 / 2).pow(2)).sqrt();
if linear_eccentricity == 0 {
(false, self.center, self.radius, self.radius, 0)
} else {
let mut good_points = 0;
let mut bad_points = 0;
let mut found_all_on_ellipse = true;
for point in circle_points {
let check_result = Self::check_ellipse_point(
self.center,
point,
major_axis.0 / 2,
minor_axis.0 / 2,
);
if check_result > 1.0 {
bad_points += 1;
found_all_on_ellipse = false;
} else {
good_points += 1;
}
}
if !found_all_on_ellipse
&& (good_points as f32 / (good_points + bad_points) as f32) < 0.8
{
let [point_1, point_2] = self.find_width_at_degree(0.0).1;
let point_3 = self.find_width_at_degree(90.0).1[0];
let guessed_center_point = Self::find_center(point_1, point_2, point_3);
(
false,
(guessed_center_point.x as u32, guessed_center_point.y as u32).into(),
self.radius,
self.radius,
0,
)
} else {
let [point_1, point_2] = self.find_width_at_degree(0.0).1;
let point_3 = self.find_width_at_degree(90.0).1[0];
let ellipse_center = Self::calculate_ellipse_center(
major_axis.0 as f32,
minor_axis.0 as f32,
point_1,
point_2,
point_3,
);
(
true,
(ellipse_center.x as u32, ellipse_center.y as u32).into(),
major_axis.0 / 2,
minor_axis.0 / 2,
linear_eccentricity,
)
}
}
}
fn find_center(p1: Point, p2: Point, p3: Point) -> Point {
let Point { x: x1, y: y1 } = p1;
let Point { x: x2, y: y2 } = p2;
let Point { x: x3, y: y3 } = p3;
let a = x1 * (y2 - y3) - y1 * (x2 - x3) + (x2 * y3 - x3 * y2);
let bx = (x1 * x1 + y1 * y1) * (y3 - y2)
+ (x2 * x2 + y2 * y2) * (y1 - y3)
+ (x3 * x3 + y3 * y3) * (y2 - y1);
let by = (x1 * x1 + y1 * y1) * (x2 - x3)
+ (x2 * x2 + y2 * y2) * (x3 - x1)
+ (x3 * x3 + y3 * y3) * (x1 - x2);
let x = bx / (2.0 * a);
let y = by / (2.0 * a);
(x.abs(), y.abs()).into()
}
fn calculate_ellipse_center(a: f32, _b: f32, p1: Point, p2: Point, p3: Point) -> Point {
let Point { x: x1, y: y1 } = p1;
let Point { x: x2, y: y2 } = p2;
let Point { x: x3, y: y3 } = p3;
let ma = (x1 * x1 + y1 * y1 - a * a) / 2.0;
let mb = (x2 * x2 + y2 * y2 - a * a) / 2.0;
let mc = (x3 * x3 + y3 * y3 - a * a) / 2.0;
let determinant = (x1 * y2 + x2 * y3 + x3 * y1) - (y1 * x2 + y2 * x3 + y3 * x1);
let x = (ma * y2 + mb * y3 + mc * y1) / determinant;
let y = (x1 * mb + x2 * mc + x3 * ma) / determinant;
(x, y).into()
}
fn check_ellipse_point(
center: PointU,
point: Point,
semi_major_axis: u32,
semi_minor_axis: u32,
) -> f64 {
((point.x as f64 - center.x as f64).powf(2.0) / (semi_major_axis as f64).powf(2.0))
+ ((point.y as f64 - center.y as f64).powf(2.0) / (semi_minor_axis as f64).powf(2.0))
}
fn find_width_at_degree(&self, rotation: f32) -> (u32, [Point; 2]) {
let mut x = self.center.x;
let y = self.center.y;
let mut length = 0;
while {
let point = get_point(self.center, (x, y).into(), rotation);
!self.image.check_point_in_bounds(point) && !self.image.get_point(point) && x > 0
} {
x -= 1;
length += 1;
}
let x_left = x;
x = self.center.x + 1;
while {
let point = get_point(self.center, (x, y).into(), rotation);
!self.image.check_point_in_bounds(point) && !self.image.get_point(point)
} {
x += 1;
length += 1;
}
(
length,
[
get_point(self.center, (x_left, y).into(), rotation),
get_point(self.center, (x, y).into(), rotation),
],
)
}
}
pub fn detect(image: &BitMatrix, try_harder: bool) -> Result<MaxicodeDetectionResult> {
let Some(mut circles) = find_concentric_circles(image) else {
return Err(Exceptions::NOT_FOUND);
};
let center_point_std_dev = Circle::calculate_center_point_std_dev(&circles);
circles.retain(|c| {
(c.center.x as i32 - center_point_std_dev.1 .0 as i32).unsigned_abs()
<= center_point_std_dev.0 .0
&& (c.center.y as i32 - center_point_std_dev.1 .1 as i32).unsigned_abs()
<= center_point_std_dev.0 .1
});
circles.sort_by(compare_circle);
for circle in circles.iter_mut() {
let Ok(symbol_box) = box_symbol(image, circle) else {
if try_harder {
continue;
} else {
return Err(Exceptions::NOT_FOUND);
}
};
let grid_sampler = DefaultGridSampler;
let [tl, bl, tr, br] = symbol_box.0;
let target_width = Point::distance(tl, tr);
let target_height = Point::distance(br, tr);
let dst = Quadrilateral::new(
point_f(0.0, 0.0),
point_f(target_width, 0.0),
point_f(target_width, target_height),
point_f(0.0, target_height),
);
let src = Quadrilateral::new(tl, tr, br, bl);
let Ok((bits, _)) = grid_sampler.sample_grid_detailed(
image,
target_width.round() as u32,
target_height.round() as u32,
dst,
src,
) else {
if try_harder {
continue;
} else {
return Err(Exceptions::NOT_FOUND);
}
};
return Ok(MaxicodeDetectionResult {
bits,
points: symbol_box.0.to_vec(),
rotation: symbol_box.1,
});
}
Err(Exceptions::NOT_FOUND)
}
fn find_concentric_circles(image: &BitMatrix) -> Option<Vec<Circle>> {
let mut bullseyes = Vec::new();
let mut row = 6;
while row < image.getHeight() - 6 {
let mut current_column = 6;
while current_column < image.getWidth() - 6 {
if let Some((center, radius, horizontal_buckets)) =
find_next_bullseye_horizontal(image, row, current_column)
{
let (target_good, vertical_buckets) = verify_bullseye_vertical(image, row, center);
if target_good {
bullseyes.push(Circle {
center: (center, row).into(),
radius,
horizontal_buckets,
vertical_buckets,
image,
});
current_column = center + radius;
continue;
} else {
let new_column = center - radius + (radius / 4);
if new_column == current_column {
row += ROW_SCAN_SKIP;
break;
}
current_column = new_column;
continue;
}
} else {
row += ROW_SCAN_SKIP;
break;
}
}
}
if bullseyes.is_empty() {
None
} else {
Some(bullseyes)
}
}
#[derive(PartialEq, Eq, Clone, Copy)]
enum Color {
Black,
White,
}
impl std::ops::Not for Color {
type Output = Self;
fn not(self) -> Self::Output {
match self {
Color::Black => Color::White,
Color::White => Color::Black,
}
}
}
impl From<bool> for Color {
fn from(value: bool) -> Self {
match value {
true => Color::Black,
false => Color::White,
}
}
}
fn find_next_bullseye_horizontal(
image: &BitMatrix,
row: u32,
start_column: u32,
) -> Option<(u32, u32, [u32; 11])> {
let mut buckets = [0_u32; 11];
let mut column = start_column;
let mut last_color = Color::Black;
let mut pointer = 0;
while !image.get(column, row) && column < image.getWidth() - 6 {
column += 1;
}
while column < image.getWidth() - 6 {
let local_bit = image.get(column, row);
if Color::from(local_bit) != last_color {
last_color = !last_color;
pointer += 1;
if pointer == 11 {
if validate_bullseye_widths(&buckets) && last_color == Color::White {
return Some(get_bullseye_metadata(&buckets, column));
} else {
pointer -= 1;
buckets.copy_within(1.., 0);
buckets[10] = 0;
}
}
}
buckets[pointer] += 1;
column += 1;
}
None
}
fn verify_bullseye_vertical(image: &BitMatrix, row: u32, column: u32) -> (bool, [u32; 11]) {
let up_vector = get_column_vector(image, column, row, true);
let down_vector = get_column_vector(image, column, row, false);
let potential_bullseye = build_potential_bullsey_array(&down_vector, &up_vector);
if validate_bullseye_widths(&potential_bullseye) {
(
validate_bullseye_widths(&potential_bullseye),
potential_bullseye,
)
} else {
let up_vector = get_column_vector(image, column + 1, row, true);
let down_vector = get_column_vector(image, column + 1, row, false);
let potential_bullseye = build_potential_bullsey_array(&down_vector, &up_vector);
if validate_bullseye_widths(&potential_bullseye) {
(
validate_bullseye_widths(&potential_bullseye),
potential_bullseye,
)
} else {
let up_vector = get_column_vector(image, column - 1, row, true);
let down_vector = get_column_vector(image, column - 1, row, false);
let potential_bullseye = build_potential_bullsey_array(&down_vector, &up_vector);
if validate_bullseye_widths(&potential_bullseye) {
(
validate_bullseye_widths(&potential_bullseye),
potential_bullseye,
)
} else {
(false, potential_bullseye)
}
}
}
}
fn build_potential_bullsey_array<T: std::ops::Add<Output = T> + Copy>(
vector_1: &[T; 6],
vector_2: &[T; 6],
) -> [T; 11] {
[
vector_1[5],
vector_1[4],
vector_1[3],
vector_1[2],
vector_1[1],
vector_1[0] + vector_2[0],
vector_2[1],
vector_2[2],
vector_2[3],
vector_2[4],
vector_2[5],
]
}
fn get_column_vector(image: &BitMatrix, column: u32, start_row: u32, looking_up: bool) -> [u32; 6] {
let mut buckets = [0_u32; 6];
let mut row = start_row;
let mut last_color = Color::White;
let mut pointer = 0;
while row > 0 && row < image.getHeight() {
let local_bit = image.get(column, row);
if Color::from(local_bit) != last_color {
last_color = !last_color;
pointer += 1;
}
if pointer > 5 {
break;
}
buckets[pointer] += 1;
row = if looking_up { row + 1 } else { row - 1 };
}
buckets
}
fn validate_bullseye_widths(buckets: &[u32; 11]) -> bool {
let total_width_even = buckets
.iter()
.enumerate()
.filter_map(|e| {
if e.0 != 5 && (e.0 == 0 || e.0 % 2 == 0) {
Some(*e.1)
} else {
None
}
})
.sum::<u32>() as f32;
let total_width_odd = buckets
.iter()
.enumerate()
.filter_map(|e| {
if e.0 != 5 && (e.0 != 0 && e.0 % 2 != 0) {
Some(*e.1)
} else {
None
}
})
.sum::<u32>() as f32;
let estimated_module_size_even = total_width_even / 5.0;
let estimated_module_size_odd = total_width_odd / 5.0;
let max_variance_even = estimated_module_size_even / 2.0;
let max_variance_odd = estimated_module_size_odd / 2.0;
let b1 = (estimated_module_size_even - buckets[0] as f32).abs();
let b2 = (estimated_module_size_odd - buckets[1] as f32).abs();
let b3 = (estimated_module_size_even - buckets[2] as f32).abs();
let b4 = (estimated_module_size_odd - buckets[3] as f32).abs();
let b5 = (estimated_module_size_even - buckets[4] as f32).abs();
let b7 = (estimated_module_size_even - buckets[6] as f32).abs();
let b8 = (estimated_module_size_odd - buckets[7] as f32).abs();
let b9 = (estimated_module_size_even - buckets[8] as f32).abs();
let b10 = (estimated_module_size_odd - buckets[9] as f32).abs();
let b11 = (estimated_module_size_even - buckets[10] as f32).abs();
b1 < max_variance_even
&& b2 < max_variance_odd
&& b3 < max_variance_even
&& b4 < max_variance_odd
&& b5 < max_variance_even
&& b7 < max_variance_even
&& b8 < max_variance_odd
&& b9 < max_variance_even
&& b10 < max_variance_odd
&& b11 < max_variance_even
}
fn get_bullseye_metadata(buckets: &[u32; 11], column: u32) -> (u32, u32, [u32; 11]) {
let radius = ((buckets.iter().sum::<u32>() as f32) / 2.0).round() as u32;
let center = column - radius;
(center, radius, *buckets)
}
const LEFT_SHIFT_PERCENT_ADJUST: f32 = 0.03;
const RIGHT_SHIFT_PERCENT_ADJUST: f32 = 0.03;
const ACCEPTED_SCALES: [f64; 5] = [0.065, 0.069, 0.07, 0.075, 0.08];
fn box_symbol(image: &BitMatrix, circle: &mut Circle) -> Result<([Point; 4], f32)> {
let (left_boundary, right_boundary, top_boundary, bottom_boundary) =
calculate_simple_boundary(circle, Some(image), None, false);
let naive_box = [
point_f(left_boundary as f32, bottom_boundary as f32),
point_f(left_boundary as f32, top_boundary as f32),
point_f(right_boundary as f32, bottom_boundary as f32),
point_f(right_boundary as f32, top_boundary as f32),
];
#[allow(unused_mut)]
let mut result_box = naive_box;
#[cfg(feature = "experimental_features")]
let (is_ellipse, _, _, _, _) = circle.detect_ellipse();
#[cfg(feature = "experimental_features")]
if is_ellipse {
return Err(Exceptions::NOT_FOUND);
}
let mut final_rotation = 0.0;
for scale in ACCEPTED_SCALES {
if let Some(found_rotation) = attempt_rotation_box(image, circle, &naive_box, scale) {
(result_box, final_rotation) = found_rotation;
break;
}
}
Ok((
[
(result_box[0].x, result_box[0].y).into(),
(result_box[1].x, result_box[1].y).into(),
(result_box[2].x, result_box[2].y).into(),
(result_box[3].x, result_box[3].y).into(),
],
final_rotation,
))
}
fn calculate_simple_boundary(
circle: &Circle,
image: Option<&BitMatrix>,
center_scale: Option<f64>,
tight: bool,
) -> (u32, u32, u32, u32) {
let (symbol_width, symbol_height) = if !tight {
guess_barcode_size(circle)
} else if let Some(s) = center_scale {
guess_barcode_size_general(circle, 0.05, s, 0.95)
} else {
guess_barcode_size_tighter(circle)
};
let (image_width, image_height) = if let Some(i) = image {
(i.getWidth(), i.getHeight())
} else {
(symbol_width, symbol_height)
};
let up_down_shift = symbol_height as i32 / 2;
let left_shift =
((symbol_width as f32 / 2.0) - (symbol_width as f32 * LEFT_SHIFT_PERCENT_ADJUST)) as i32;
let right_shift =
((symbol_width as f32 / 2.0) + (symbol_width as f32 * RIGHT_SHIFT_PERCENT_ADJUST)) as i32;
let left_boundary =
(circle.center.x as i32 - left_shift).clamp(0, image_width as i32 - 33) as u32;
let right_boundary =
(circle.center.x as i32 + right_shift).clamp(33, image_width as i32) as u32;
let top_boundary =
(circle.center.y as i32 + up_down_shift).clamp(33, image_height as i32) as u32;
let bottom_boundary =
(circle.center.y as i32 - up_down_shift).clamp(0, image_height as i32 - 30) as u32;
(left_boundary, right_boundary, top_boundary, bottom_boundary)
}
const TOP_LEFT_ORIENTATION_POS: (PointU, PointU, PointU) =
(point(10, 9), point(11, 9), point(11, 10));
const TOP_RIGHT_ORIENTATION_POS: (PointU, PointU, PointU) =
(point(17, 9), point(17, 10), point(18, 10));
const LEFT_ORIENTATION_POS: (PointU, PointU, PointU) = (point(7, 15), point(7, 16), point(8, 16));
const RIGHT_ORIENTATION_POS: (PointU, PointU, PointU) =
(point(20, 16), point(21, 16), point(20, 17));
const BOTTOM_LEFT_ORIENTATION_POS: (PointU, PointU, PointU) =
(point(10, 22), point(11, 22), point(10, 23));
const BOTTOM_RIGHT_ORIENTATION_POS: (PointU, PointU, PointU) =
(point(17, 22), point(16, 23), point(17, 23));
fn attempt_rotation_box(
image: &BitMatrix,
circle: &mut Circle,
naive_box: &[Point; 4],
center_scale: f64,
) -> Option<([Point; 4], f32)> {
circle.calculate_high_accuracy_center();
let (topl_p1, topl_p2, topl_p3) =
get_adjusted_points(TOP_LEFT_ORIENTATION_POS, circle, center_scale);
let (topr_p1, topr_p2, topr_p3) =
get_adjusted_points(TOP_RIGHT_ORIENTATION_POS, circle, center_scale);
let (l_p1, l_p2, l_p3) = get_adjusted_points(LEFT_ORIENTATION_POS, circle, center_scale);
let (r_p1, r_p2, r_p3) = get_adjusted_points(RIGHT_ORIENTATION_POS, circle, center_scale);
let (bottoml_p1, bottoml_p2, bottoml_p3) =
get_adjusted_points(BOTTOM_LEFT_ORIENTATION_POS, circle, center_scale);
let (bottomr_p1, bottomr_p2, bottomr_p3) =
get_adjusted_points(BOTTOM_RIGHT_ORIENTATION_POS, circle, center_scale);
let mut found = false;
let mut final_rotation = 0.0;
for int_rotation in 0..175 {
let rotation = (int_rotation * 2) as f32;
let p1_rot = get_point(circle.center, topl_p1, rotation);
let p2_rot = get_point(circle.center, topl_p2, rotation);
let p3_rot = get_point(circle.center, topl_p3, rotation);
let found_tl = image.try_get_area(p1_rot.x as u32, p1_rot.y as u32, 3)?
&& image.try_get_area(p2_rot.x as u32, p2_rot.y as u32, 3)?
&& image.try_get_area(p3_rot.x as u32, p3_rot.y as u32, 3)?;
if !found_tl {
continue;
}
let p1_rot = get_point(circle.center, topr_p1, rotation);
let p2_rot = get_point(circle.center, topr_p2, rotation);
let p3_rot = get_point(circle.center, topr_p3, rotation);
let found_tr = !image.try_get_area(p1_rot.x as u32, p1_rot.y as u32, 3)?
&& !image.try_get_area(p2_rot.x as u32, p2_rot.y as u32, 3)?
&& !image.try_get_area(p3_rot.x as u32, p3_rot.y as u32, 3)?;
if !found_tr {
continue;
}
let p1_rot = get_point(circle.center, l_p1, rotation);
let p2_rot = get_point(circle.center, l_p2, rotation);
let p3_rot = get_point(circle.center, l_p3, rotation);
let found_l = image.try_get_area(p1_rot.x as u32, p1_rot.y as u32, 3)?
&& !image.try_get_area(p2_rot.x as u32, p2_rot.y as u32, 3)?
&& image.try_get_area(p3_rot.x as u32, p3_rot.y as u32, 3)?;
if !found_l {
continue;
}
let p1_rot = get_point(circle.center, r_p1, rotation);
let p2_rot = get_point(circle.center, r_p2, rotation);
let p3_rot = get_point(circle.center, r_p3, rotation);
let found_r = image.try_get_area(p1_rot.x as u32, p1_rot.y as u32, 3)?
&& !image.try_get_area(p2_rot.x as u32, p2_rot.y as u32, 3)?
&& image.try_get_area(p3_rot.x as u32, p3_rot.y as u32, 3)?;
if !found_r {
continue;
}
let p1_rot = get_point(circle.center, bottoml_p1, rotation);
let p2_rot = get_point(circle.center, bottoml_p2, rotation);
let p3_rot = get_point(circle.center, bottoml_p3, rotation);
let found_bl = image.try_get_area(p1_rot.x as u32, p1_rot.y as u32, 3)?
&& !image.try_get_area(p2_rot.x as u32, p2_rot.y as u32, 3)?
&& image.try_get_area(p3_rot.x as u32, p3_rot.y as u32, 3)?;
if !found_bl {
continue;
}
let p1_rot = get_point(circle.center, bottomr_p1, rotation);
let p2_rot = get_point(circle.center, bottomr_p2, rotation);
let p3_rot = get_point(circle.center, bottomr_p3, rotation);
let found_br = image.try_get_area(p1_rot.x as u32, p1_rot.y as u32, 3)?
&& !image.try_get_area(p2_rot.x as u32, p2_rot.y as u32, 3)?
&& image.try_get_area(p3_rot.x as u32, p3_rot.y as u32, 3)?;
if !found_br {
continue;
}
found = found_tl && found_tr && found_l && found_r && found_bl && found_br;
if found {
final_rotation = rotation;
break;
}
}
if found {
let new_1 = get_point(
circle.center,
(naive_box[0].x as u32, naive_box[0].y as u32).into(),
final_rotation,
);
let new_2 = get_point(
circle.center,
(naive_box[1].x as u32, naive_box[1].y as u32).into(),
final_rotation,
);
let new_3 = get_point(
circle.center,
(naive_box[2].x as u32, naive_box[2].y as u32).into(),
final_rotation,
);
let new_4 = get_point(
circle.center,
(naive_box[3].x as u32, naive_box[3].y as u32).into(),
final_rotation,
);
Some((
[
point_f(new_1.x, new_1.y),
point_f(new_2.x, new_2.y),
point_f(new_3.x, new_3.y),
point_f(new_4.x, new_4.y),
],
final_rotation,
))
} else {
None
}
}
fn get_adjusted_points(
origin: (PointU, PointU, PointU),
circle: &Circle,
center_scale: f64,
) -> (PointU, PointU, PointU) {
(
adjust_point_alternate(origin.0, circle, center_scale),
adjust_point_alternate(origin.1, circle, center_scale),
adjust_point_alternate(origin.2, circle, center_scale),
)
}
fn get_point(center: PointU, original: PointU, angle: f32) -> Point {
let radians = angle.to_radians();
let x = radians.cos() * (original.x as f32 - center.x as f32)
- radians.sin() * (original.y as f32 - center.y as f32)
+ center.x as f32;
let y = radians.sin() * (original.x as f32 - center.x as f32)
+ radians.cos() * (original.y as f32 - center.y as f32)
+ center.y as f32;
Point::new(x.abs(), y.abs())
}
fn adjust_point_alternate(point: PointU, circle: &Circle, center_scale: f64) -> PointU {
let (left_boundary, right_boundary, top_boundary, bottom_boundary) =
calculate_simple_boundary(circle, Some(circle.image), Some(center_scale), true);
let top = bottom_boundary;
let height = top_boundary - bottom_boundary;
let width = right_boundary - left_boundary;
let left = left_boundary;
let y = point.y;
let x = point.x;
let iy = (top + (y * height + height / 2) / MaxiCodeReader::MATRIX_HEIGHT).min(height - 1);
let ix = left
+ ((x * width + width / 2 + (y & 0x01) * width / 2) / MaxiCodeReader::MATRIX_WIDTH)
.min(width - 1);
crate::point(ix, iy)
}
fn guess_barcode_size(circle: &Circle) -> (u32, u32) {
guess_barcode_size_general(circle, 0.03, 0.066, 1.0)
}
fn guess_barcode_size_tighter(circle: &Circle) -> (u32, u32) {
guess_barcode_size_general(circle, 0.025, 0.0695, 0.97)
}
fn guess_barcode_size_general(
circle: &Circle,
height_adjust_percent: f64,
circle_area_percent: f64,
height_final_adjust_percent: f64,
) -> (u32, u32) {
let circle_area = std::f64::consts::PI * circle.radius.pow(2) as f64;
let ideal_symbol_area = (circle_area / circle_area_percent) / (1.0 - height_adjust_percent);
let ideal_symbol_side = ideal_symbol_area.sqrt();
(
ideal_symbol_side.floor() as u32,
(ideal_symbol_side * height_final_adjust_percent).floor() as u32,
)
}
fn compare_circle(a: &Circle, b: &Circle) -> std::cmp::Ordering {
let a_var = a.calculate_circle_variance();
let b_var = b.calculate_circle_variance();
a_var
.partial_cmp(&b_var)
.unwrap_or(std::cmp::Ordering::Equal)
}
pub fn read_bits(image: &BitMatrix) -> Result<BitMatrix> {
let enclosingRectangle = image.getEnclosingRectangle().ok_or(Exceptions::NOT_FOUND)?;
let left = enclosingRectangle[0];
let top = enclosingRectangle[1];
let width = enclosingRectangle[2];
let height = enclosingRectangle[3];
let mut bits = BitMatrix::new(MaxiCodeReader::MATRIX_WIDTH, MaxiCodeReader::MATRIX_HEIGHT)?;
for y in 0..MaxiCodeReader::MATRIX_HEIGHT {
let iy = (top + (y * height + height / 2) / MaxiCodeReader::MATRIX_HEIGHT).min(height - 1);
for x in 0..MaxiCodeReader::MATRIX_WIDTH {
let ix = left
+ ((x * width + width / 2 + (y & 0x01) * width / 2) / MaxiCodeReader::MATRIX_WIDTH)
.min(width - 1);
if image.get(ix, iy) {
bits.set(x, y);
}
}
}
Ok(bits)
}
#[cfg(feature = "image")]
#[cfg(test)]
mod detector_test {
use std::io::Read;
use crate::{
common::{DetectorRXingResult, HybridBinarizer},
maxicode::detector::read_bits,
Binarizer, BufferedImageLuminanceSource,
};
#[test]
fn mode_1() {
finder_test(
"test_resources/blackbox/maxicode-1/1.png",
"test_resources/blackbox/maxicode-1/1.txt",
)
}
#[test]
fn mode_2() {
finder_test(
"test_resources/blackbox/maxicode-1/MODE2.png",
"test_resources/blackbox/maxicode-1/MODE2.txt",
)
}
#[test]
fn mode_2_rot90() {
finder_test(
"test_resources/blackbox/maxicode-1/MODE2-rotate-90.png",
"test_resources/blackbox/maxicode-1/MODE2-rotate-90.txt",
)
}
#[test]
fn mode3() {
finder_test(
"test_resources/blackbox/maxicode-1/MODE3.png",
"test_resources/blackbox/maxicode-1/MODE3.txt",
)
}
#[test]
fn mixed_sets() {
finder_test(
"test_resources/blackbox/maxicode-1/mode4-mixed-sets.png",
"test_resources/blackbox/maxicode-1/mode4-mixed-sets.txt",
)
}
#[test]
fn mode4() {
finder_test(
"test_resources/blackbox/maxicode-1/MODE4.png",
"test_resources/blackbox/maxicode-1/MODE4.txt",
)
}
#[test]
fn mode5() {
finder_test(
"test_resources/blackbox/maxicode-1/MODE5.png",
"test_resources/blackbox/maxicode-1/MODE5.txt",
)
}
#[test]
fn mode6() {
finder_test(
"test_resources/blackbox/maxicode-1/MODE6.png",
"test_resources/blackbox/maxicode-1/MODE6.txt",
)
}
fn finder_test(image: &str, data: &str) {
let filename = image;
let img = image::open(filename).unwrap();
let lum_src = BufferedImageLuminanceSource::new(img);
let binarizer = HybridBinarizer::new(lum_src);
let bitmatrix = binarizer.get_black_matrix().unwrap();
let mut expected_result = String::new();
std::fs::File::open(data)
.unwrap()
.read_to_string(&mut expected_result)
.unwrap();
let detection = super::detect(bitmatrix, true).unwrap();
let bits = read_bits(detection.getBits()).expect("read bits");
let result = crate::maxicode::decoder::decode(&bits).expect("must decode");
assert_eq!(expected_result, result.getText());
}
}