mirror of
https://github.com/starovoid/rxing.git
synced 2026-07-26 12:22:34 +00:00
move ported cpp_shared resources
This commit is contained in:
179
src/common/cpp_essentials/bitmatrix_cursor.rs
Normal file
179
src/common/cpp_essentials/bitmatrix_cursor.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
use crate::Point;
|
||||
|
||||
use super::{util::opposite, Direction, Value};
|
||||
|
||||
/**
|
||||
* @brief The BitMatrixCursor represents a current position inside an image and current direction it can advance towards.
|
||||
*
|
||||
* The current position and direction is a PointT<T>. So depending on the type it can be used to traverse the image
|
||||
* in a Bresenham style (PointF) or in a discrete way (step only horizontal/vertical/diagonal (PointI)).
|
||||
*/
|
||||
pub trait BitMatrixCursor {
|
||||
// const BitMatrix* img;
|
||||
|
||||
// POINT p; // current position
|
||||
// POINT d; // current direction
|
||||
|
||||
// BitMatrixCursor(const BitMatrix& image, POINT p, POINT d) : img(&image), p(p) { setDirection(d); }
|
||||
|
||||
fn testAt(&self, p: Point) -> Value; //const
|
||||
// {
|
||||
// return img->isIn(p) ? Value{img->get(p)} : Value{};
|
||||
// }
|
||||
|
||||
fn blackAt(&self, pos: Point) -> bool {
|
||||
self.testAt(pos).isBlack()
|
||||
}
|
||||
fn whiteAt(&self, pos: Point) -> bool {
|
||||
self.testAt(pos).isWhite()
|
||||
}
|
||||
|
||||
fn isIn(&self, p: Point) -> bool; // { return img->isIn(p); }
|
||||
fn isInSelf(&self) -> bool; // { return self.isIn(p); }
|
||||
fn isBlack(&self) -> bool; // { return blackAt(p); }
|
||||
fn isWhite(&self) -> bool; // { return whiteAt(p); }
|
||||
|
||||
fn front(&self) -> &Point; //{ return d; }
|
||||
fn back(&self) -> Point; // { return {-d.x, -d.y}; }
|
||||
fn left(&self) -> Point; //{ return {d.y, -d.x}; }
|
||||
fn right(&self) -> Point; //{ return {-d.y, d.x}; }
|
||||
fn direction(&self, dir: Direction) -> Point {
|
||||
self.right() * Into::<i32>::into(dir)
|
||||
}
|
||||
|
||||
fn turnBack(&mut self); // noexcept { d = back(); }
|
||||
fn turnLeft(&mut self); //noexcept { d = left(); }
|
||||
fn turnRight(&mut self); //noexcept { d = right(); }
|
||||
fn turn(&mut self, dir: Direction); //noexcept { d = direction(dir); }
|
||||
|
||||
fn edgeAt_point(&self, d: Point) -> Value;
|
||||
// {
|
||||
// Value v = testAt(p);
|
||||
// return testAt(p + d) != v ? v : Value();
|
||||
// }
|
||||
|
||||
fn edgeAtFront(&self) -> Value {
|
||||
return self.edgeAt_point(*self.front());
|
||||
}
|
||||
fn edgeAtBack(&self) -> Value {
|
||||
self.edgeAt_point(self.back())
|
||||
}
|
||||
fn edgeAtLeft(&self) -> Value {
|
||||
self.edgeAt_point(self.left())
|
||||
}
|
||||
fn edgeAtRight(&self) -> Value {
|
||||
self.edgeAt_point(self.right())
|
||||
}
|
||||
fn edgeAt_direction(&self, dir: Direction) -> Value {
|
||||
self.edgeAt_point(self.direction(dir))
|
||||
}
|
||||
|
||||
fn setDirection(&mut self, dir: Point); // { d = bresenhamDirection(dir); }
|
||||
// fn setDirection(&self, dir: Point);// { d = dir; }
|
||||
|
||||
fn step(&mut self, s: Option<f32>) -> bool; // DEF to 1
|
||||
// {
|
||||
// p += s * d;
|
||||
// return isIn(p);
|
||||
// }
|
||||
|
||||
fn movedBy<T: BitMatrixCursor>(self, d: Point) -> Self;
|
||||
// {
|
||||
// auto res = *this;
|
||||
// res.p += d;
|
||||
// return res;
|
||||
// }
|
||||
|
||||
/**
|
||||
* @brief stepToEdge advances cursor to one step behind the next (or n-th) edge.
|
||||
* @param nth number of edges to pass
|
||||
* @param range max number of steps to take
|
||||
* @param backup whether or not to backup one step so we land in front of the edge
|
||||
* @return number of steps taken or 0 if moved outside of range/image
|
||||
*/
|
||||
fn stepToEdge(&mut self, nth: Option<i32>, range: Option<i32>, backup: Option<bool>) -> i32;
|
||||
// fn stepToEdge(&self, int nth = 1, int range = 0, bool backup = false) -> i32
|
||||
// {
|
||||
// // TODO: provide an alternative and faster out-of-bounds check than isIn() inside testAt()
|
||||
// int steps = 0;
|
||||
// auto lv = testAt(p);
|
||||
|
||||
// while (nth && (!range || steps < range) && lv.isValid()) {
|
||||
// ++steps;
|
||||
// auto v = testAt(p + steps * d);
|
||||
// if (lv != v) {
|
||||
// lv = v;
|
||||
// --nth;
|
||||
// }
|
||||
// }
|
||||
// if (backup)
|
||||
// --steps;
|
||||
// p += steps * d;
|
||||
// return steps * (nth == 0);
|
||||
// }
|
||||
|
||||
fn stepAlongEdge(&mut self, dir: Direction, skipCorner: Option<bool>) -> bool
|
||||
// fn stepAlongEdge(&self, dir:Direction, skipCorner:Option<bool> = false) -> bool
|
||||
{
|
||||
let skipCorner = if let Some(sc) = skipCorner { sc } else { false };
|
||||
|
||||
if !self.edgeAt_direction(dir).isValid() {
|
||||
self.turn(dir);
|
||||
} else if self.edgeAtFront().isValid() {
|
||||
self.turn(opposite(dir));
|
||||
if self.edgeAtFront().isValid() {
|
||||
self.turn(opposite(dir));
|
||||
if self.edgeAtFront().isValid() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut ret = self.step(None);
|
||||
|
||||
if ret && skipCorner && !self.edgeAt_direction(dir).isValid() {
|
||||
self.turn(dir);
|
||||
ret = self.step(None);
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
fn countEdges(&mut self, range: i32) -> i32 {
|
||||
let mut res = 0;
|
||||
let mut range = range;
|
||||
|
||||
let mut steps;
|
||||
|
||||
while {
|
||||
steps = if range == 0 {
|
||||
0
|
||||
} else {
|
||||
self.stepToEdge(Some(1), Some(range), None)
|
||||
};
|
||||
steps > 0
|
||||
} {
|
||||
range -= steps;
|
||||
res += 1;
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
// template<typename ARRAY>
|
||||
// ARRAY readPattern(int range = 0)
|
||||
// {
|
||||
// ARRAY res;
|
||||
// for (auto& i : res)
|
||||
// i = stepToEdge(1, range);
|
||||
// return res;
|
||||
// }
|
||||
|
||||
// template<typename ARRAY>
|
||||
// ARRAY readPatternFromBlack(int maxWhitePrefix, int range = 0)
|
||||
// {
|
||||
// if (maxWhitePrefix && isWhite() && !stepToEdge(1, maxWhitePrefix))
|
||||
// return {};
|
||||
// return readPattern<ARRAY>(range);
|
||||
// }
|
||||
}
|
||||
14
src/common/cpp_essentials/direction.rs
Normal file
14
src/common/cpp_essentials/direction.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum Direction {
|
||||
Left = -1,
|
||||
Right = 1,
|
||||
}
|
||||
|
||||
impl From<Direction> for i32 {
|
||||
fn from(value: Direction) -> Self {
|
||||
match value {
|
||||
Direction::Left => -1,
|
||||
Direction::Right => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
322
src/common/cpp_essentials/dm_regression_line.rs
Normal file
322
src/common/cpp_essentials/dm_regression_line.rs
Normal file
@@ -0,0 +1,322 @@
|
||||
use crate::common::Result;
|
||||
use crate::{Exceptions, Point};
|
||||
|
||||
use super::{
|
||||
util::{float_max, float_min},
|
||||
RegressionLine,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DMRegressionLine {
|
||||
points: Vec<Point>,
|
||||
direction_inward: Point,
|
||||
pub(super) a: f32,
|
||||
pub(super) b: f32,
|
||||
pub(super) c: f32,
|
||||
// std::vector<PointF> _points;
|
||||
// PointF _directionInward;
|
||||
// PointF::value_t a = NAN, b = NAN, c = NAN;
|
||||
}
|
||||
|
||||
impl Default for DMRegressionLine {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
points: Default::default(),
|
||||
direction_inward: Default::default(),
|
||||
a: f32::NAN,
|
||||
b: f32::NAN,
|
||||
c: f32::NAN,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RegressionLine for DMRegressionLine {
|
||||
fn points(&self) -> &[Point] {
|
||||
&self.points
|
||||
}
|
||||
|
||||
fn length(&self) -> u32 {
|
||||
if self.points.len() >= 2 {
|
||||
Point::distance(*self.points.first().unwrap(), *self.points.last().unwrap()) as u32
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn isValid(&self) -> bool {
|
||||
!self.a.is_nan()
|
||||
}
|
||||
|
||||
fn normal(&self) -> Point {
|
||||
if self.isValid() {
|
||||
Point {
|
||||
x: self.a,
|
||||
y: self.b,
|
||||
}
|
||||
} else {
|
||||
self.direction_inward
|
||||
}
|
||||
}
|
||||
|
||||
fn signedDistance(&self, p: Point) -> f32 {
|
||||
Point::dot(self.normal(), p) - self.c
|
||||
}
|
||||
|
||||
fn distance_single(&self, p: Point) -> f32 {
|
||||
(self.signedDistance(p)).abs()
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.points.clear();
|
||||
self.direction_inward = Point { x: 0.0, y: 0.0 };
|
||||
self.a = f32::NAN;
|
||||
self.b = f32::NAN;
|
||||
self.c = f32::NAN;
|
||||
}
|
||||
|
||||
fn add(&mut self, p: Point) -> Result<()> {
|
||||
if self.direction_inward == Point::default() {
|
||||
return Err(Exceptions::ILLEGAL_STATE);
|
||||
}
|
||||
self.points.push(p);
|
||||
if self.points.len() == 1 {
|
||||
self.c = Point::dot(self.normal(), p);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pop_back(&mut self) {
|
||||
self.points.pop();
|
||||
}
|
||||
|
||||
fn setDirectionInward(&mut self, d: Point) {
|
||||
self.direction_inward = Point::normalized(d);
|
||||
}
|
||||
|
||||
fn evaluate_max_distance(
|
||||
&mut self,
|
||||
maxSignedDist: Option<f64>,
|
||||
updatePoints: Option<bool>,
|
||||
) -> bool {
|
||||
let maxSignedDist = if let Some(m) = maxSignedDist { m } else { -1.0 };
|
||||
let updatePoints = if let Some(u) = updatePoints { u } else { false };
|
||||
|
||||
let mut ret = self.evaluateSelf();
|
||||
if maxSignedDist > 0.0 {
|
||||
let mut points = self.points.clone();
|
||||
loop {
|
||||
let old_points_size = points.len();
|
||||
// remove points that are further 'inside' than maxSignedDist or further 'outside' than 2 x maxSignedDist
|
||||
// auto end = std::remove_if(points.begin(), points.end(), [this, maxSignedDist](auto p) {
|
||||
// auto sd = this->signedDistance(p);
|
||||
// return sd > maxSignedDist || sd < -2 * maxSignedDist;
|
||||
// });
|
||||
// points.erase(end, points.end());
|
||||
points.retain(|&p| {
|
||||
let sd = self.signedDistance(p) as f64;
|
||||
!(sd > maxSignedDist || sd < -2.0 * maxSignedDist)
|
||||
});
|
||||
if old_points_size == points.len() {
|
||||
break;
|
||||
}
|
||||
// #ifdef PRINT_DEBUG
|
||||
// printf("removed %zu points\n", old_points_size - points.size());
|
||||
// #endif
|
||||
ret = self.evaluate(&points);
|
||||
}
|
||||
|
||||
if updatePoints {
|
||||
self.points = points;
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn isHighRes(&self) -> bool {
|
||||
let Some(mut min) = self.points.first().copied() else { return false };
|
||||
let Some(mut max) = self.points.first().copied() else { return false };
|
||||
for p in &self.points {
|
||||
min.x = float_min(min.x, p.x);
|
||||
min.y = float_min(min.y, p.y);
|
||||
max.x = float_max(max.x, p.x);
|
||||
max.y = float_max(max.y, p.y);
|
||||
}
|
||||
let diff = max - min;
|
||||
let len = diff.maxAbsComponent();
|
||||
let steps = float_min(diff.x.abs(), diff.y.abs());
|
||||
// due to aliasing we get bad extrapolations if the line is short and too close to vertical/horizontal
|
||||
steps > 2.0 || len > 50.0
|
||||
}
|
||||
|
||||
fn evaluate(&mut self, points: &[Point]) -> bool {
|
||||
let mean = points.iter().sum::<Point>() / points.len() as f32;
|
||||
|
||||
let mut sumXX = 0.0;
|
||||
let mut sumYY = 0.0;
|
||||
let mut sumXY = 0.0;
|
||||
for p in points {
|
||||
// for (auto p = begin; p != end; ++p) {
|
||||
let d = *p - mean;
|
||||
sumXX += d.x * d.x;
|
||||
sumYY += d.y * d.y;
|
||||
sumXY += d.x * d.y;
|
||||
}
|
||||
if sumYY >= sumXX {
|
||||
let l = (sumYY * sumYY + sumXY * sumXY).sqrt();
|
||||
self.a = sumYY / l;
|
||||
self.b = -sumXY / l;
|
||||
} else {
|
||||
let l = (sumXX * sumXX + sumXY * sumXY).sqrt();
|
||||
self.a = sumXY / l;
|
||||
self.b = -sumXX / l;
|
||||
}
|
||||
if Point::dot(self.direction_inward, self.normal()) < 0.0 {
|
||||
// if (dot(_directionInward, normal()) < 0) {
|
||||
self.a = -self.a;
|
||||
self.b = -self.b;
|
||||
}
|
||||
self.c = Point::dot(self.normal(), mean); // (a*mean.x + b*mean.y);
|
||||
Point::dot(self.direction_inward, self.normal()) > 0.5
|
||||
// angle between original and new direction is at most 60 degree
|
||||
}
|
||||
|
||||
fn evaluateSelf(&mut self) -> bool {
|
||||
let mean = self.points.iter().sum::<Point>() / self.points.len() as f32;
|
||||
|
||||
let mut sumXX = 0.0;
|
||||
let mut sumYY = 0.0;
|
||||
let mut sumXY = 0.0;
|
||||
for p in &self.points {
|
||||
// for (auto p = begin; p != end; ++p) {
|
||||
let d = *p - mean;
|
||||
sumXX += d.x * d.x;
|
||||
sumYY += d.y * d.y;
|
||||
sumXY += d.x * d.y;
|
||||
}
|
||||
if sumYY >= sumXX {
|
||||
let l = (sumYY * sumYY + sumXY * sumXY).sqrt();
|
||||
self.a = sumYY / l;
|
||||
self.b = -sumXY / l;
|
||||
} else {
|
||||
let l = (sumXX * sumXX + sumXY * sumXY).sqrt();
|
||||
self.a = sumXY / l;
|
||||
self.b = -sumXX / l;
|
||||
}
|
||||
if Point::dot(self.direction_inward, self.normal()) < 0.0 {
|
||||
// if (dot(_directionInward, normal()) < 0) {
|
||||
self.a = -self.a;
|
||||
self.b = -self.b;
|
||||
}
|
||||
self.c = Point::dot(self.normal(), mean); // (a*mean.x + b*mean.y);
|
||||
Point::dot(self.direction_inward, self.normal()) > 0.5
|
||||
// angle between original and new direction is at most 60 degree
|
||||
}
|
||||
}
|
||||
|
||||
impl DMRegressionLine {
|
||||
// template <typename Container, typename Filter>
|
||||
fn average<T>(c: &[f64], f: T) -> f64
|
||||
where
|
||||
T: Fn(f64) -> bool,
|
||||
{
|
||||
let mut sum: f64 = 0.0;
|
||||
let mut num = 0;
|
||||
for v in c {
|
||||
// for (const auto& v : c)
|
||||
if f(*v) {
|
||||
sum += *v;
|
||||
num += 1;
|
||||
}
|
||||
}
|
||||
sum / num as f64
|
||||
}
|
||||
|
||||
pub fn reverse(&mut self) {
|
||||
self.points.reverse();
|
||||
}
|
||||
|
||||
pub fn modules(&mut self, beg: Point, end: Point) -> Result<f64> {
|
||||
if self.points.len() <= 3 {
|
||||
return Err(Exceptions::ILLEGAL_STATE);
|
||||
}
|
||||
|
||||
// re-evaluate and filter out all points too far away. required for the gapSizes calculation.
|
||||
self.evaluate_max_distance(Some(1.0), Some(true));
|
||||
|
||||
// std::vector<double> gapSizes, modSizes;
|
||||
let mut gapSizes: Vec<f64> = Vec::new();
|
||||
let mut modSizes = Vec::new();
|
||||
|
||||
gapSizes.reserve(self.points.len());
|
||||
|
||||
// calculate the distance between the points projected onto the regression line
|
||||
for i in 1..self.points.len() {
|
||||
// for (size_t i = 1; i < _points.size(); ++i)
|
||||
gapSizes.push(Point::distance(
|
||||
self.project(self.points[i]),
|
||||
self.project(self.points[i - 1]),
|
||||
) as f64);
|
||||
}
|
||||
|
||||
// calculate the (expected average) distance of two adjacent pixels
|
||||
let unitPixelDist = Point::length(Point::bresenhamDirection(
|
||||
self.points
|
||||
.last()
|
||||
.copied()
|
||||
.ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?
|
||||
- self
|
||||
.points
|
||||
.first()
|
||||
.copied()
|
||||
.ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?,
|
||||
)) as f64;
|
||||
|
||||
// calculate the width of 2 modules (first black pixel to first black pixel)
|
||||
let mut sumFront: f64 =
|
||||
Point::distance(beg, self.project(self.points[0])) as f64 - unitPixelDist;
|
||||
let mut sumBack: f64 = 0.0; // (last black pixel to last black pixel)
|
||||
for dist in gapSizes {
|
||||
// for (auto dist : gapSizes) {
|
||||
if dist > 1.9 * unitPixelDist {
|
||||
modSizes.push(std::mem::take(&mut sumBack));
|
||||
}
|
||||
sumFront += dist;
|
||||
sumBack += dist;
|
||||
if dist > 1.9 * unitPixelDist {
|
||||
modSizes.push(std::mem::take(&mut sumFront));
|
||||
}
|
||||
}
|
||||
|
||||
modSizes.push(
|
||||
sumFront
|
||||
+ Point::distance(
|
||||
end,
|
||||
self.project(
|
||||
self.points
|
||||
.last()
|
||||
.copied()
|
||||
.ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?,
|
||||
),
|
||||
) as f64,
|
||||
);
|
||||
modSizes[0] = 0.0; // the first element is an invalid sumBack value, would be pop_front() if vector supported this
|
||||
let lineLength = Point::distance(beg, end) as f64 - unitPixelDist;
|
||||
let mut meanModSize = Self::average(&modSizes, |_: f64| true);
|
||||
// let meanModSize = average(modSizes, [](double){ return true; });
|
||||
// #ifdef PRINT_DEBUG
|
||||
// printf("unit pixel dist: %.1f\n", unitPixelDist);
|
||||
// printf("lineLength: %.1f, meanModSize: %.1f, gaps: %lu\n", lineLength, meanModSize, modSizes.size());
|
||||
// #endif
|
||||
for i in 0..2 {
|
||||
// for (int i = 0; i < 2; ++i)
|
||||
meanModSize = Self::average(&modSizes, |dist: f64| {
|
||||
(dist - meanModSize).abs() < meanModSize / (2 + i) as f64
|
||||
});
|
||||
// meanModSize = average(modSizes, [=](double dist) { return std::abs(dist - meanModSize) < meanModSize / (2 + i); });
|
||||
}
|
||||
// #ifdef PRINT_DEBUG
|
||||
// printf("post filter meanModSize: %.1f\n", meanModSize);
|
||||
// #endif
|
||||
Ok(lineLength / meanModSize)
|
||||
}
|
||||
}
|
||||
444
src/common/cpp_essentials/edge_tracer.rs
Normal file
444
src/common/cpp_essentials/edge_tracer.rs
Normal file
@@ -0,0 +1,444 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
common::{BitMatrix, Result},
|
||||
qrcode::encoder::ByteMatrix,
|
||||
Exceptions, Point,
|
||||
};
|
||||
|
||||
use super::{BitMatrixCursor, Direction, RegressionLine, StepResult, Value};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EdgeTracer<'a> {
|
||||
pub(crate) img: &'a BitMatrix,
|
||||
|
||||
pub(crate) p: Point, // current position
|
||||
d: Point, // current direction
|
||||
|
||||
// pub history: Option<&'a mut ByteMatrix>, // = nullptr;
|
||||
pub history: Option<Rc<RefCell<ByteMatrix>>>,
|
||||
pub state: i32,
|
||||
// const BitMatrix* img;
|
||||
|
||||
// POINT p; // current position
|
||||
// POINT d; // current direction
|
||||
}
|
||||
|
||||
// impl<'a> Clone for EdgeTracer<'_> {
|
||||
// fn clone(&self) -> Self {
|
||||
// if let Some(history) = self.history {
|
||||
// Self { img: self.img, p: self.p.clone(), d: self.d.clone(), history: Some(history), state: self.state.clone() }
|
||||
// }else {
|
||||
// Self { img: self.img, p: self.p.clone(), d: self.d.clone(), history: None, state: self.state.clone() }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
impl BitMatrixCursor for EdgeTracer<'_> {
|
||||
fn testAt(&self, p: Point) -> Value {
|
||||
if self.img.isIn(p, 0) {
|
||||
Value::from(self.img.get_point(p))
|
||||
} else {
|
||||
Value::Invalid
|
||||
}
|
||||
}
|
||||
|
||||
fn isIn(&self, p: Point) -> bool {
|
||||
self.img.isIn(p, 0)
|
||||
}
|
||||
|
||||
fn isInSelf(&self) -> bool {
|
||||
self.isIn(self.p)
|
||||
}
|
||||
|
||||
fn isBlack(&self) -> bool {
|
||||
self.blackAt(self.p)
|
||||
}
|
||||
|
||||
fn isWhite(&self) -> bool {
|
||||
self.whiteAt(self.p)
|
||||
}
|
||||
|
||||
fn front(&self) -> &Point {
|
||||
&self.d
|
||||
}
|
||||
|
||||
fn back(&self) -> Point {
|
||||
Point {
|
||||
x: -self.d.x,
|
||||
y: -self.d.y,
|
||||
}
|
||||
}
|
||||
|
||||
fn left(&self) -> Point {
|
||||
Point {
|
||||
x: self.d.y,
|
||||
y: -self.d.x,
|
||||
}
|
||||
}
|
||||
|
||||
fn right(&self) -> Point {
|
||||
Point {
|
||||
x: -self.d.y,
|
||||
y: self.d.x,
|
||||
}
|
||||
}
|
||||
|
||||
fn turnBack(&mut self) {
|
||||
self.d = self.back()
|
||||
}
|
||||
|
||||
fn turnLeft(&mut self) {
|
||||
self.d = self.left()
|
||||
}
|
||||
|
||||
fn turnRight(&mut self) {
|
||||
self.d = self.right()
|
||||
}
|
||||
|
||||
fn turn(&mut self, dir: Direction) {
|
||||
self.d = self.direction(dir)
|
||||
}
|
||||
|
||||
fn edgeAt_point(&self, d: Point) -> Value {
|
||||
let v = self.testAt(self.p);
|
||||
if self.testAt(self.p + d) != v {
|
||||
v
|
||||
} else {
|
||||
Value::Invalid
|
||||
}
|
||||
}
|
||||
|
||||
fn setDirection(&mut self, dir: Point) {
|
||||
self.d = dir.bresenhamDirection();
|
||||
}
|
||||
|
||||
fn step(&mut self, s: Option<f32>) -> bool {
|
||||
let s = if let Some(s) = s { s } else { 1.0 };
|
||||
self.p += self.d * s;
|
||||
self.isIn(self.p)
|
||||
}
|
||||
|
||||
fn movedBy<T: BitMatrixCursor>(self, d: Point) -> Self {
|
||||
let mut res = self;
|
||||
res.p += d;
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief stepToEdge advances cursor to one step behind the next (or n-th) edge.
|
||||
* @param nth number of edges to pass
|
||||
* @param range max number of steps to take
|
||||
* @param backup whether or not to backup one step so we land in front of the edge
|
||||
* @return number of steps taken or 0 if moved outside of range/image
|
||||
*/
|
||||
fn stepToEdge(&mut self, nth: Option<i32>, range: Option<i32>, backup: Option<bool>) -> i32 {
|
||||
let mut nth = if let Some(nth) = nth { nth } else { 1 };
|
||||
let range = if let Some(r) = range { r } else { 0 };
|
||||
let backup = if let Some(b) = backup { b } else { false };
|
||||
// TODO: provide an alternative and faster out-of-bounds check than isIn() inside testAt()
|
||||
let mut steps = 0;
|
||||
let mut lv = self.testAt(self.p);
|
||||
|
||||
while nth > 0 && (range <= 0 || steps < range) && lv.isValid() {
|
||||
steps += 1;
|
||||
let v = self.testAt(self.p + steps * self.d);
|
||||
if lv != v {
|
||||
lv = v;
|
||||
nth -= 1;
|
||||
}
|
||||
}
|
||||
if backup {
|
||||
steps -= 1;
|
||||
}
|
||||
self.p += self.d * steps;
|
||||
steps * i32::from(nth == 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EdgeTracer<'_> {
|
||||
pub fn new(image: &'a BitMatrix, p: Point, d: Point) -> EdgeTracer<'a> {
|
||||
// : img(&image), p(p) { setDirection(d); }
|
||||
EdgeTracer {
|
||||
img: image,
|
||||
p,
|
||||
d,
|
||||
history: None,
|
||||
state: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn traceStep(
|
||||
&mut self,
|
||||
dEdge: Point,
|
||||
maxStepSize: i32,
|
||||
goodDirection: bool,
|
||||
) -> Result<StepResult> {
|
||||
let dEdge = Point::mainDirection(dEdge);
|
||||
for breadth in 1..=(if maxStepSize == 1 {
|
||||
2
|
||||
} else if goodDirection {
|
||||
1
|
||||
} else {
|
||||
3
|
||||
}) {
|
||||
// for (int breadth = 1; breadth <= (maxStepSize == 1 ? 2 : (goodDirection ? 1 : 3)); ++breadth)
|
||||
for step in 1..=maxStepSize {
|
||||
// for (int step = 1; step <= maxStepSize; ++step)
|
||||
for i in 0..=(2 * (step / 4 + 1) * breadth) {
|
||||
// for (int i = 0; i <= 2*(step/4+1) * breadth; ++i) {
|
||||
let mut pEdge = self.p
|
||||
+ step * self.d
|
||||
+ (if i & 1 > 0 { (i + 1) / 2 } else { -i / 2 }) * dEdge;
|
||||
// dbg!(pEdge);
|
||||
|
||||
if !self.blackAt(pEdge + dEdge) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// found black pixel -> go 'outward' until we hit the b/w border
|
||||
for _j in 0..(std::cmp::max(maxStepSize, 3)) {
|
||||
// for (int j = 0; j < std::max(maxStepSize, 3) && isIn(pEdge); ++j) {
|
||||
if self.whiteAt(pEdge) {
|
||||
// if we are not making any progress, we still have another endless loop bug
|
||||
if self.p == pEdge.centered() {
|
||||
return Err(Exceptions::ILLEGAL_STATE);
|
||||
}
|
||||
self.p = pEdge.centered();
|
||||
|
||||
// if (self.history && maxStepSize == 1) {
|
||||
if let Some(history) = &self.history {
|
||||
if maxStepSize == 1 {
|
||||
if history.borrow().get(self.p.x as u32, self.p.y as u32)
|
||||
== self.state as u8
|
||||
{
|
||||
return Ok(StepResult::ClosedEnd);
|
||||
}
|
||||
history.borrow_mut().set(
|
||||
self.p.x as u32,
|
||||
self.p.y as u32,
|
||||
self.state as u8,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(StepResult::Found);
|
||||
}
|
||||
pEdge -= dEdge;
|
||||
if self.blackAt(pEdge - self.d) {
|
||||
pEdge -= self.d;
|
||||
}
|
||||
// dbg!(pEdge);
|
||||
|
||||
if !self.isIn(pEdge) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// no valid b/w border found within reasonable range
|
||||
return Ok(StepResult::ClosedEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(StepResult::OpenEnd)
|
||||
}
|
||||
|
||||
pub fn updateDirectionFromOrigin(&mut self, origin: Point) -> bool {
|
||||
let old_d = self.d;
|
||||
self.setDirection(self.p - origin);
|
||||
// if the new direction is pointing "backward", i.e. angle(new, old) > 90 deg -> break
|
||||
if Point::dot(self.d, old_d) < 0.0 {
|
||||
return false;
|
||||
}
|
||||
// make sure d stays in the same quadrant to prevent an infinite loop
|
||||
if (self.d.x).abs() == (self.d.y).abs() {
|
||||
self.d = Point::mainDirection(old_d) + 0.99 * (self.d - Point::mainDirection(old_d));
|
||||
} else if Point::mainDirection(self.d) != Point::mainDirection(old_d) {
|
||||
self.d = Point::mainDirection(old_d) + 0.99 * Point::mainDirection(self.d);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn traceLine<T: RegressionLine>(&mut self, dEdge: Point, line: &mut T) -> Result<bool> {
|
||||
line.setDirectionInward(dEdge);
|
||||
loop {
|
||||
// log(self.p);
|
||||
line.add(self.p)?;
|
||||
if line.points().len() % 50 == 10 {
|
||||
if !line.evaluate_max_distance(None, None) {
|
||||
return Ok(false);
|
||||
}
|
||||
if !self.updateDirectionFromOrigin(
|
||||
self.p - line.project(self.p)
|
||||
+ **line
|
||||
.points()
|
||||
.first()
|
||||
.as_ref()
|
||||
.ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?,
|
||||
) {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
let stepResult = self.traceStep(dEdge, 1, line.isValid())?;
|
||||
if stepResult != StepResult::Found {
|
||||
return Ok(stepResult == StepResult::OpenEnd && line.points().len() > 1);
|
||||
}
|
||||
} // while (true);
|
||||
}
|
||||
|
||||
pub fn traceGaps<T: RegressionLine>(
|
||||
&mut self,
|
||||
dEdge: Point,
|
||||
line: &mut T,
|
||||
maxStepSize: i32,
|
||||
finishLine: &mut T,
|
||||
) -> Result<bool> {
|
||||
let mut maxStepSize = maxStepSize;
|
||||
line.setDirectionInward(dEdge);
|
||||
let mut gaps = 0;
|
||||
loop {
|
||||
// detect an endless loop (lack of progress). if encountered, please report.
|
||||
if !(line.points().is_empty()
|
||||
|| &&self.p
|
||||
!= line
|
||||
.points()
|
||||
.last()
|
||||
.as_ref()
|
||||
.ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?)
|
||||
{
|
||||
return Err(Exceptions::ILLEGAL_STATE);
|
||||
}
|
||||
if !line.points().is_empty()
|
||||
&& &&self.p
|
||||
== line
|
||||
.points()
|
||||
.last()
|
||||
.as_ref()
|
||||
.ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
// log(p);
|
||||
|
||||
// if we drifted too far outside of the code, break
|
||||
if line.isValid()
|
||||
&& line.signedDistance(self.p) < -5.0
|
||||
&& (!line.evaluate_max_distance(None, None) || line.signedDistance(self.p) < -5.0)
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// if we are drifting towards the inside of the code, pull the current position back out onto the line
|
||||
if line.isValid() && line.signedDistance(self.p) > 3.0 {
|
||||
// The current direction d and the line we are tracing are supposed to be roughly parallel.
|
||||
// In case the 'go outward' step in traceStep lead us astray, we might end up with a line
|
||||
// that is almost perpendicular to d. Then the back-projection below can result in an
|
||||
// endless loop. Break if the angle between d and line is greater than 45 deg.
|
||||
if (Point::dot(Point::normalized(self.d), line.normal())).abs() > 0.7
|
||||
// thresh is approx. sin(45 deg)
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// re-evaluate line with all the points up to here before projecting
|
||||
if !line.evaluate_max_distance(Some(1.5), None) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let mut np = line.project(self.p);
|
||||
// make sure we are making progress even when back-projecting:
|
||||
// consider a 90deg corner, rotated 45deg. we step away perpendicular from the line and get
|
||||
// back projected where we left off the line.
|
||||
// The 'while' instead of 'if' was introduced to fix the issue with #245. It turns out that
|
||||
// np can actually be behind the projection of the last line point and we need 2 steps in d
|
||||
// to prevent a dead lock. see #245.png
|
||||
while Point::distance(
|
||||
np,
|
||||
line.project(
|
||||
line.points()
|
||||
.last()
|
||||
.copied()
|
||||
.ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?,
|
||||
),
|
||||
) < 1.0
|
||||
{
|
||||
np += self.d;
|
||||
}
|
||||
self.p = Point::centered(np);
|
||||
} else {
|
||||
let stepLengthInMainDir = if line.points().is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
Point::dot(
|
||||
Point::mainDirection(self.d),
|
||||
self.p
|
||||
- line
|
||||
.points()
|
||||
.last()
|
||||
.copied()
|
||||
.ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?,
|
||||
)
|
||||
};
|
||||
line.add(self.p)?;
|
||||
|
||||
if stepLengthInMainDir > 1.0 {
|
||||
gaps += 1;
|
||||
if gaps >= 2 || line.points().len() > 5 {
|
||||
if !line.evaluate_max_distance(Some(1.5), None) {
|
||||
return Ok(false);
|
||||
}
|
||||
if !self.updateDirectionFromOrigin(
|
||||
self.p - line.project(self.p)
|
||||
+ line
|
||||
.points()
|
||||
.first()
|
||||
.copied()
|
||||
.ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?,
|
||||
) {
|
||||
return Ok(false);
|
||||
}
|
||||
// check if the first half of the top-line trace is complete.
|
||||
// the minimum code size is 10x10 -> every code has at least 4 gaps
|
||||
//TODO: maybe switch to termination condition based on bottom line length to get a better
|
||||
// finishLine for the right line trace
|
||||
if !finishLine.isValid() && gaps == 4 {
|
||||
// undo the last insert, it will be inserted again after the restart
|
||||
line.pop_back();
|
||||
// gaps -= 1;
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
} else if gaps == 0 && line.points().len() >= (2 * maxStepSize) as usize {
|
||||
return Ok(false);
|
||||
} // no point in following a line that has no gaps
|
||||
}
|
||||
|
||||
if finishLine.isValid() {
|
||||
maxStepSize =
|
||||
std::cmp::min(maxStepSize, (finishLine.signedDistance(self.p)) as i32);
|
||||
}
|
||||
|
||||
let stepResult = self.traceStep(dEdge, maxStepSize, line.isValid())?;
|
||||
|
||||
if stepResult != StepResult::Found
|
||||
// we are successful iff we found an open end across a valid finishLine
|
||||
{
|
||||
return Ok(stepResult == StepResult::OpenEnd
|
||||
&& finishLine.isValid()
|
||||
&& (finishLine.signedDistance(self.p)) as i32 <= maxStepSize + 1);
|
||||
}
|
||||
} //while (true);
|
||||
}
|
||||
|
||||
pub fn traceCorner(&mut self, dir: &mut Point, corner: &mut Point) -> Result<bool> {
|
||||
self.step(None);
|
||||
// log(p);
|
||||
*corner = self.p;
|
||||
std::mem::swap(&mut self.d, dir);
|
||||
self.traceStep(-1.0 * (*dir), 2, false)?;
|
||||
// #ifdef PRINT_DEBUG
|
||||
// printf("turn: %.0f x %.0f -> %.2f, %.2f\n", p.x, p.y, d.x, d.y);
|
||||
// #endif
|
||||
Ok(self.isIn(*corner) && self.isIn(self.p))
|
||||
}
|
||||
}
|
||||
22
src/common/cpp_essentials/mod.rs
Normal file
22
src/common/cpp_essentials/mod.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
pub mod bitmatrix_cursor;
|
||||
pub mod direction;
|
||||
pub mod dm_regression_line;
|
||||
pub mod edge_tracer;
|
||||
pub mod regression_line;
|
||||
pub mod step_result;
|
||||
pub mod util;
|
||||
pub mod value;
|
||||
pub mod concentric_finder;
|
||||
pub mod pattern;
|
||||
|
||||
|
||||
pub use bitmatrix_cursor::*;
|
||||
pub use direction::*;
|
||||
pub use dm_regression_line::*;
|
||||
pub use edge_tracer::*;
|
||||
pub use regression_line::*;
|
||||
pub use step_result::*;
|
||||
pub use util::*;
|
||||
pub use value::*;
|
||||
pub use concentric_finder::*;
|
||||
pub use pattern::*;
|
||||
134
src/common/cpp_essentials/regression_line.rs
Normal file
134
src/common/cpp_essentials/regression_line.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
use crate::common::Result;
|
||||
use crate::Point;
|
||||
|
||||
pub trait RegressionLine {
|
||||
// points: Vec<Point>,
|
||||
// direction_inward: Point,
|
||||
|
||||
// }
|
||||
// impl RegressionLine {
|
||||
// std::vector<PointF> _points;
|
||||
// PointF _directionInward;
|
||||
// PointF::value_t a = NAN, b = NAN, c = NAN;
|
||||
|
||||
// fn intersect<T: RegressionLine, T2: RegressionLine>(&self, l1: &T, l2: &T2)
|
||||
// -> Point;
|
||||
|
||||
// fn evaluate_begin_end(&self, begin: Point, end: Point) -> bool;// {
|
||||
// {
|
||||
// let mean = std::accumulate(begin, end, PointF()) / std::distance(begin, end);
|
||||
// PointF::value_t sumXX = 0, sumYY = 0, sumXY = 0;
|
||||
// for (auto p = begin; p != end; ++p) {
|
||||
// auto d = *p - mean;
|
||||
// sumXX += d.x * d.x;
|
||||
// sumYY += d.y * d.y;
|
||||
// sumXY += d.x * d.y;
|
||||
// }
|
||||
// if (sumYY >= sumXX) {
|
||||
// auto l = std::sqrt(sumYY * sumYY + sumXY * sumXY);
|
||||
// a = +sumYY / l;
|
||||
// b = -sumXY / l;
|
||||
// } else {
|
||||
// auto l = std::sqrt(sumXX * sumXX + sumXY * sumXY);
|
||||
// a = +sumXY / l;
|
||||
// b = -sumXX / l;
|
||||
// }
|
||||
// if (dot(_directionInward, normal()) < 0) {
|
||||
// a = -a;
|
||||
// b = -b;
|
||||
// }
|
||||
// c = dot(normal(), mean); // (a*mean.x + b*mean.y);
|
||||
// return dot(_directionInward, normal()) > 0.5f; // angle between original and new direction is at most 60 degree
|
||||
// }
|
||||
|
||||
fn evaluate(&mut self, points: &[Point]) -> bool; // { return self.evaluate_begin_end(&points.front(), &points.back() + 1); }
|
||||
fn evaluateSelf(&mut self) -> bool;
|
||||
|
||||
// RegressionLine() { _points.reserve(16); } // arbitrary but plausible start size (tiny performance improvement)
|
||||
|
||||
// template<typename T> RegressionLine(PointT<T> a, PointT<T> b)
|
||||
// {
|
||||
// evaluate(std::vector{a, b});
|
||||
// }
|
||||
|
||||
// template<typename T> RegressionLine(const PointT<T>* b, const PointT<T>* e)
|
||||
// {
|
||||
// evaluate(b, e);
|
||||
// }
|
||||
|
||||
fn points(&self) -> &[Point]; //const { return _points; }
|
||||
fn length(&self) -> u32; //const { return _points.size() >= 2 ? int(distance(_points.front(), _points.back())) : 0; }
|
||||
fn isValid(&self) -> bool; //const { return !std::isnan(a); }
|
||||
fn normal(&self) -> Point; //const { return isValid() ? PointF(a, b) : _directionInward; }
|
||||
fn signedDistance(&self, p: Point) -> f32; //const { return dot(normal(), p) - c; }
|
||||
fn distance_single(&self, p: Point) -> f32; //const { return std::abs(signedDistance(PointF(p))); }
|
||||
fn project(&self, p: Point) -> Point {
|
||||
p - self.normal() * self.signedDistance(p)
|
||||
}
|
||||
|
||||
fn reset(&mut self);
|
||||
// {
|
||||
// _points.clear();
|
||||
// _directionInward = {};
|
||||
// a = b = c = NAN;
|
||||
// }
|
||||
|
||||
fn add(&mut self, p: Point) -> Result<()>; //{
|
||||
// assert(_directionInward != PointF());
|
||||
// _points.push_back(p);
|
||||
// if (_points.size() == 1)
|
||||
// c = dot(normal(), p);
|
||||
// }
|
||||
|
||||
fn pop_back(&mut self); // { _points.pop_back(); }
|
||||
|
||||
fn setDirectionInward(&mut self, d: Point); //{ _directionInward = normalized(d); }
|
||||
|
||||
// fn evaluate(&self, double maxSignedDist = -1, bool updatePoints = false) -> bool
|
||||
fn evaluate_max_distance(
|
||||
&mut self,
|
||||
maxSignedDist: Option<f64>,
|
||||
updatePoints: Option<bool>,
|
||||
) -> bool;
|
||||
// {
|
||||
// bool ret = evaluate(_points);
|
||||
// if (maxSignedDist > 0) {
|
||||
// auto points = _points;
|
||||
// while (true) {
|
||||
// auto old_points_size = points.size();
|
||||
// // remove points that are further 'inside' than maxSignedDist or further 'outside' than 2 x maxSignedDist
|
||||
// auto end = std::remove_if(points.begin(), points.end(), [this, maxSignedDist](auto p) {
|
||||
// auto sd = this->signedDistance(p);
|
||||
// return sd > maxSignedDist || sd < -2 * maxSignedDist;
|
||||
// });
|
||||
// points.erase(end, points.end());
|
||||
// if (old_points_size == points.size())
|
||||
// break;
|
||||
// // #ifdef PRINT_DEBUG
|
||||
// // printf("removed %zu points\n", old_points_size - points.size());
|
||||
// // #endif
|
||||
// ret = evaluate(points);
|
||||
// }
|
||||
|
||||
// if (updatePoints)
|
||||
// _points = std::move(points);
|
||||
// }
|
||||
// return ret;
|
||||
// }
|
||||
|
||||
fn isHighRes(&self) -> bool; //const
|
||||
// {
|
||||
// PointF min = _points.front(), max = _points.front();
|
||||
// for (auto p : _points) {
|
||||
// min.x = std::min(min.x, p.x);
|
||||
// min.y = std::min(min.y, p.y);
|
||||
// max.x = std::max(max.x, p.x);
|
||||
// max.y = std::max(max.y, p.y);
|
||||
// }
|
||||
// auto diff = max - min;
|
||||
// auto len = maxAbsComponent(diff);
|
||||
// auto steps = std::min(std::abs(diff.x), std::abs(diff.y));
|
||||
// // due to aliasing we get bad extrapolations if the line is short and too close to vertical/horizontal
|
||||
// return steps > 2 || len > 50;
|
||||
// }
|
||||
}
|
||||
6
src/common/cpp_essentials/step_result.rs
Normal file
6
src/common/cpp_essentials/step_result.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum StepResult {
|
||||
Found,
|
||||
OpenEnd,
|
||||
ClosedEnd,
|
||||
}
|
||||
43
src/common/cpp_essentials/util.rs
Normal file
43
src/common/cpp_essentials/util.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use crate::common::Result;
|
||||
use crate::{Exceptions, Point};
|
||||
|
||||
use super::{DMRegressionLine, Direction, RegressionLine};
|
||||
|
||||
#[inline(always)]
|
||||
pub fn float_min<T: PartialOrd>(a: T, b: T) -> T {
|
||||
if a > b {
|
||||
b
|
||||
} else {
|
||||
a
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn float_max<T: PartialOrd>(a: T, b: T) -> T {
|
||||
if a < b {
|
||||
b
|
||||
} else {
|
||||
a
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn intersect(l1: &DMRegressionLine, l2: &DMRegressionLine) -> Result<Point> {
|
||||
if !(l1.isValid() && l2.isValid()) {
|
||||
return Err(Exceptions::ILLEGAL_STATE);
|
||||
}
|
||||
let d = l1.a * l2.b - l1.b * l2.a;
|
||||
let x = (l1.c * l2.b - l1.b * l2.c) / d;
|
||||
let y = (l1.a * l2.c - l1.c * l2.a) / d;
|
||||
Ok(Point { x, y })
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[inline(always)]
|
||||
pub fn opposite(dir: Direction) -> Direction {
|
||||
if dir == Direction::Left {
|
||||
Direction::Right
|
||||
} else {
|
||||
Direction::Left
|
||||
}
|
||||
}
|
||||
36
src/common/cpp_essentials/value.rs
Normal file
36
src/common/cpp_essentials/value.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Value {
|
||||
Invalid = -1,
|
||||
White = 0,
|
||||
Black = 1,
|
||||
}
|
||||
impl Value {
|
||||
pub fn isBlack(&self) -> bool {
|
||||
self == &Value::Black
|
||||
}
|
||||
pub fn isWhite(&self) -> bool {
|
||||
self == &Value::White
|
||||
}
|
||||
pub fn isValid(&self) -> bool {
|
||||
self != &Value::Invalid
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Value {
|
||||
fn from(value: bool) -> Self {
|
||||
match value {
|
||||
true => Value::Black,
|
||||
false => Value::White,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Value> for bool {
|
||||
fn from(value: Value) -> Self {
|
||||
match value {
|
||||
Value::Invalid => false,
|
||||
Value::White => true,
|
||||
Value::Black => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,6 +112,8 @@ pub use eci::*;
|
||||
mod quad;
|
||||
pub use quad::*;
|
||||
|
||||
pub mod cpp_essentials;
|
||||
|
||||
#[cfg(feature = "otsu_level")]
|
||||
mod otsu_level_binarizer;
|
||||
#[cfg(feature = "otsu_level")]
|
||||
|
||||
Reference in New Issue
Block a user