wip: add dxo edge barcode (inop)

This commit is contained in:
Henry Schimke
2024-01-16 12:25:44 -06:00
parent 13c5bb5916
commit e01e392a4a
8 changed files with 1055 additions and 2 deletions

View File

@@ -87,6 +87,8 @@ pub enum BarcodeFormat {
/** UPC/EAN extension format. Not a stand-alone format. */ /** UPC/EAN extension format. Not a stand-alone format. */
UPC_EAN_EXTENSION, UPC_EAN_EXTENSION,
DXFilmEdge,
/// ///
UNSUPORTED_FORMAT, UNSUPORTED_FORMAT,
} }

View File

@@ -47,6 +47,10 @@ impl PatternRow {
pub fn into_pattern_view(&self) -> PatternView { pub fn into_pattern_view(&self) -> PatternView {
PatternView::new(self) PatternView::new(self)
} }
pub fn sum(&self) -> PatternType {
self.0.iter().sum()
}
} }
impl IntoIterator for PatternRow { impl IntoIterator for PatternRow {
@@ -363,12 +367,16 @@ impl<'a> From<&PatternView<'a>> for &'a [PatternType] {
* *
* The operator[](int) can be used in combination with a PatternView * The operator[](int) can be used in combination with a PatternView
*/ */
#[derive(Default)] #[derive(Default, Clone)]
struct BarAndSpace<T: Default + std::cmp::PartialEq> { pub struct BarAndSpace<T: Default + std::cmp::PartialEq> {
bar: T, bar: T,
space: T, space: T,
} }
impl<T: Default + std::cmp::PartialEq> BarAndSpace<T> { impl<T: Default + std::cmp::PartialEq> BarAndSpace<T> {
pub fn new(bar: T, space: T) -> BarAndSpace<T> {
Self { bar, space }
}
#[allow(dead_code)] #[allow(dead_code)]
pub fn isValid(&self) -> bool { pub fn isValid(&self) -> bool {
self.bar != T::default() && self.space != T::default() self.bar != T::default() && self.space != T::default()

View File

@@ -1,4 +1,8 @@
use std::iter::Sum;
use std::ops::Shl;
use crate::common::Result; use crate::common::Result;
use crate::qrcode::cpp_port::detector::AppendBit;
use crate::{Exceptions, Point}; use crate::{Exceptions, Point};
use super::{Direction, RegressionLineTrait}; use super::{Direction, RegressionLineTrait};
@@ -66,3 +70,38 @@ pub fn ToString<T: Into<usize>>(val: T, len: usize) -> Result<String> {
Ok(result.iter().collect()) Ok(result.iter().collect())
} }
pub fn ToInt(a: &[u32]) -> Option<u32> {
if a.iter().sum::<u32>() <= 32 {
return None;
}
// assert(Reduce(a) <= 32);
let mut pattern = 0;
for (i, element) in a.iter().copied().enumerate() {
// for (int i = 0; i < Size(a); i++)
pattern = (pattern << element) | !(0xffffffff << element) * (!i & 1) as u32;
}
return Some(pattern);
}
pub fn ToIntPos(
bits: &[u8],
pos: usize, /* = 0 */
count: usize, /* = 8 * sizeof(T)*/
) -> Option<u32> {
// assert(0 <= count && count <= 8 * (int)sizeof(T));
// assert(0 <= pos && pos + count <= bits.size());
let count = std::cmp::min(count as usize, bits.len());
let mut res = 0;
for bit in bits.iter().skip(pos).take(count) {
AppendBit(&mut res, bit == &0);
}
// let it = bits.iterAt(pos);
// for (int i = 0; i < count; ++i, ++it)
// {AppendBit(res, *it);}
return Some(res as u32);
}

View File

@@ -0,0 +1,341 @@
/*
* Copyright 2023 Antoine Mérino
* Copyright 2023 Axel Waggershauser
*/
// SPDX-License-Identifier: Apache-2.0
use crate::{
common::cpp_essentials::{
FindLeftGuardBy, FixedPattern, IsRightGuard, PatternView, ToInt, ToIntPos,
},
point, point_i, BarcodeFormat, DecodeHintValue, DecodingHintDictionary, Exceptions, PointI,
RXingResult,
};
use super::row_reader::{DecodingState, RowReader};
use crate::common::Result;
// Detection is made from center outward.
// We ensure the clock track is decoded before the data track to avoid false positives.
// They are two version of a DX Edge codes : with and without frame number.
// The clock track is longer if the DX code contains the frame number (more recent version)
const CLOCK_LENGTH_FN: usize = 31;
const CLOCK_LENGTH_NO_FN: usize = 23;
// data track length, without the start and stop patterns
const DATA_LENGTH_FN: u32 = 23;
const DATA_LENGTH_NO_FN: u32 = 15;
const CLOCK_PATTERN_FN: FixedPattern<25, CLOCK_LENGTH_FN> = FixedPattern::new([
5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3,
]);
const CLOCK_PATTERN_NO_FN: FixedPattern<17, CLOCK_LENGTH_NO_FN> =
FixedPattern::new([5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3]);
const DATA_START_PATTERN: FixedPattern<5, 5> = FixedPattern::new([1, 1, 1, 1, 1]);
const DATA_STOP_PATTERN: FixedPattern<3, 3> = FixedPattern::new([1, 1, 1]);
pub struct DXFilmEdgeReader<'a> {
options: &'a DecodingHintDictionary,
}
fn IsPattern<const N: usize, const SUM: usize>(
view: &PatternView,
pattern: &FixedPattern<N, SUM>,
minQuietZone: f32,
) -> bool {
const E2E: bool = false;
let view = view.subView(0, Some(N));
view.isValid()
&& crate::common::cpp_essentials::pattern::IsPattern::<E2E, N, SUM, false>(
&view,
pattern,
Some(if view.isAtFirstBar() {
u32::MAX as f32
} else {
view[-1] as f32
}),
minQuietZone,
0.0,
) != 0.0
}
fn DistIsBelowThreshold(a: PointI, b: PointI, threshold: PointI) -> bool {
(a.x - b.x).abs() < threshold.x && (a.y - b.y).abs() < threshold.y
}
// DX Film Edge clock track found on 35mm films.
#[derive(Debug)]
pub(super) struct Clock {
hasFrameNr: bool, // = false; // Clock track (thus data track) with frame number (longer version)
rowNumber: u32, // = 0,
xStart: u32, // = 0; // Beginning of the clock track on the X-axis, in pixels
xStop: u32, // = 0; // End of the clock track on the X-axis, in pixels
}
impl Default for Clock {
fn default() -> Self {
Self {
hasFrameNr: false,
rowNumber: 0,
xStart: 0,
xStop: 0,
}
}
}
impl Clock {
pub const fn dataLength(&self) -> u32 {
if self.hasFrameNr {
DATA_LENGTH_FN
} else {
DATA_LENGTH_NO_FN
}
}
pub fn moduleSize(&self) -> f32 {
(self.xStop as f32 - self.xStart as f32)
/ (if self.hasFrameNr {
CLOCK_LENGTH_FN
} else {
CLOCK_LENGTH_NO_FN
}) as f32
}
pub fn isCloseTo(&self, p: PointI, x: u32) -> bool {
return DistIsBelowThreshold(
p,
point(x as i32, self.rowNumber as i32),
(self.moduleSize() * point(0.5, 4.0)).into(),
);
}
pub fn isCloseToStart(&self, x: u32, y: u32) -> bool {
return self.isCloseTo(point(x as i32, y as i32), self.xStart);
}
pub fn isCloseToStop(&self, x: u32, y: u32) -> bool {
return self.isCloseTo(point(x as i32, y as i32), self.xStop);
}
}
impl DecodingState {
// see if we a clock that starts near {x, y}
pub fn findClock(&mut self, x: u32, y: u32) -> Option<&mut Clock> {
let start = point(x, y);
if let Some(i) = self
.clocks
.iter()
.position(|c| c.isCloseToStart(start.x, start.y))
{
self.clocks.get_mut(i) //self.clocks[i]
} else {
None
}
// let i = FindIf(clocks, [start = PointI{x, y}](auto& v) { return v.isCloseToStart(start.x, start.y); });
// return if i != clocks.end() {&(*i)} else {nullptr};
}
// add/update clock
pub fn addClock(&mut self, clock: Clock) {
if let Some(clockf) = self.findClock(clock.xStart, clock.rowNumber) {
*clockf = clock
} else {
self.clocks.push(clock)
}
// if (Clock* i = findClock(clock.xStart, clock.rowNumber))
// {*i = clock;}
// else
// {clocks.push_back(clock);}
}
}
fn CheckForClock(rowNumber: u32, view: &PatternView) -> Option<Clock> {
let mut clock = Clock::default();
if (IsPattern(view, &CLOCK_PATTERN_FN, 0.5))
// On FN versions, the decimal number can be really close to the clock
{
clock.hasFrameNr = true;
} else if (IsPattern(view, &CLOCK_PATTERN_NO_FN, 2.0)) {
clock.hasFrameNr = false;
} else {
return None;
}
clock.rowNumber = rowNumber;
clock.xStart = view.pixelsInFront() as u32;
clock.xStop = view.pixelsTillEnd() as u32;
return Some(clock);
}
impl<'a> RowReader for DXFilmEdgeReader<'_> {
fn decodePattern(
&self,
rowNumber: u32,
next: &mut PatternView,
state: &mut Option<DecodingState>,
) -> Result<RXingResult> {
// if (!state) {
// state.reset(new DXFEState);
// static_cast<DXFEState*>(state.get())->centerRow = rowNumber;
// }
if state.is_none() {
*state = Some(DecodingState::default())
};
let dxState = state.as_mut().unwrap();
// Only consider rows below the center row of the image
if (!matches!(
self.options.get(&crate::DecodeHintType::TRY_HARDER),
Some(DecodeHintValue::TryHarder(true))
) && rowNumber < dxState.centerRow)
{
return Err(Exceptions::NOT_FOUND);
}
// Look for a pattern that is part of both the clock as well as the data track (ommitting the first bar)
let Is4x1 = |view: &PatternView, spaceInPixel: Option<f32>| {
let spaceInPixel = spaceInPixel.unwrap_or_default();
// find min/max of 4 consecutive bars/spaces and make sure they are close together
let tmp_arr: [u16; 4] = [view[1], view[2], view[3], view[4]];
let m = *tmp_arr.iter().min().unwrap_or(&0);
let M = *tmp_arr.iter().max().unwrap_or(&0);
// let [m, M] = std::minmax({view[1], view[2], view[3], view[4]});
return M <= m * 4 / 3 + 1 && spaceInPixel > m as f32 / 2.0;
};
// 12 is the minimum size of the data track (at least one product class bit + one parity bit)
*next = FindLeftGuardBy::<12, _>(*next, 10, Is4x1)?; // THIS IS WRONG WRONG WRONG ISSUE
// next = FindLeftGuard<4>(next, 10, Is4x1);
if (!next.isValid()) {
return Err(Exceptions::NOT_FOUND);
}
// Check if the 4x1 pattern is part of a clock track
if let Some(clock) = CheckForClock(rowNumber, &next) {
dxState.addClock(clock);
next.skipSymbol();
return Err(Exceptions::NOT_FOUND);
}
// if (auto clock = CheckForClock(rowNumber, next)) {
// dxState->addClock(*clock);
// next.skipSymbol();
// return {};
// }
// Without at least one clock track, we stop here
if (dxState.clocks.is_empty()) {
return Err(Exceptions::NOT_FOUND);
}
let minDataQuietZone: f32 = 0.5;
if (!IsPattern(&next, &DATA_START_PATTERN, minDataQuietZone)) {
return Err(Exceptions::NOT_FOUND);
}
let xStart = next.pixelsInFront();
// Only consider data tracks that are next to a clock track
let Some(clock) = dxState.findClock(xStart as u32, rowNumber) else {
return Err(Exceptions::NOT_FOUND);
};
// Skip the data start pattern (black, white, black, white, black)
// The first signal bar is always white: this is the
// separation between the start pattern and the product number
next.skipSymbol();
// Read the data bits
let mut dataBits: Vec<u8> = Vec::default();
while (next.isValidWithN(1) && dataBits.len() < clock.dataLength() as usize) {
let modules = (next[0] as f32 / clock.moduleSize() + 0.5) as u32;
// even index means we are at a bar, otherwise at a space
// dataBits.appendBits(if next.index() % 2 == 0 {0xFFFFFFFF} else {0x0}, modules);
for i in 0..modules {
dataBits.push((if next.index() % 2 == 0 { 0xFF } else { 0x0 } >> (i - 1)) & 1);
// should it be 0xFFFFFFFF
}
next.shift(1);
}
// Check the data track length
if (dataBits.len() != clock.dataLength() as usize) {
return Err(Exceptions::NOT_FOUND);
}
*next = next.subView(0, Some(DATA_STOP_PATTERN.size()));
// Check there is the Stop pattern at the end of the data track
if (!next.isValid() || !IsRightGuard(&next, &DATA_STOP_PATTERN, minDataQuietZone, 0.0)) {
return Err(Exceptions::NOT_FOUND);
}
// The following bits are always white (=false), they are separators.
if (dataBits[0] != 0
|| dataBits[8] != 0
|| (if clock.hasFrameNr {
(dataBits[20] != 0 || dataBits[22] != 0)
} else {
dataBits[14] != 0
}))
{
return Err(Exceptions::NOT_FOUND);
}
// Check the parity bit
let signalSum = dataBits.iter().rev().skip(2).sum::<u8>(); //Reduce(dataBits.begin(), dataBits.end() - 2, 0);
let parityBit = *(dataBits.last().unwrap_or(&0));
if (signalSum % 2 != parityBit) {
return Err(Exceptions::NOT_FOUND);
}
// Compute the DX 1 number (product number)
let Some(productNumber) = ToIntPos(&dataBits, 1, 7) else {
return Err(Exceptions::NOT_FOUND);
};
// Compute the DX 2 number (generation number)
let Some(generationNumber) = ToIntPos(&dataBits, 9, 4) else {
return Err(Exceptions::NOT_FOUND);
};
// Generate the textual representation.
// Eg: 115-10/11A means: DX1 = 115, DX2 = 10, Frame number = 11A
let mut txt = String::with_capacity(10);
// txt.reserve(10);
txt = (productNumber.to_string()) + "-" + (&generationNumber.to_string());
if (clock.hasFrameNr) {
let frameNr = ToIntPos(&dataBits, 13, 6).unwrap_or(0);
txt += &("/".to_owned() + &(frameNr.to_string()));
if (dataBits[19] != 0) {
txt += "A";
}
}
let xStop = next.pixelsTillEnd();
// The found data track must end near the clock track
if (!clock.isCloseToStop(xStop as u32, rowNumber)) {
return Err(Exceptions::NOT_FOUND);
}
// Update the clock coordinates with the latest corresponding data track
// This may improve signal detection for next row iterations
clock.xStart = xStart as u32;
clock.xStop = xStop as u32;
Ok(RXingResult::new(
&txt,
dataBits,
Vec::new(),
BarcodeFormat::DXFilmEdge,
))
// return RXingResult(txt, rowNumber, xStart, xStop, BarcodeFormat::DXFilmEdge, {});
}
}

4
src/oned/cpp/mod.rs Normal file
View File

@@ -0,0 +1,4 @@
mod dxfilm_edge_reader;
mod one_d_reader;
mod row_reader;
pub use one_d_reader::ODReader;

View File

@@ -0,0 +1,403 @@
/*
* Copyright 2016 Nu-book Inc.
* Copyright 2016 ZXing authors
* Copyright 2020 Axel Waggershauser
*/
// SPDX-License-Identifier: Apache-2.0
use std::any::Any;
use std::collections::HashMap;
use crate::common::cpp_essentials::{PatternRow, PatternView};
use crate::Binarizer;
use crate::{multi::MultipleBarcodeReader, RXingResult, Reader};
use crate::{
point, BarcodeFormat, BinaryBitmap, DecodingHintDictionary, Exceptions, PointT, ResultPoint,
};
use crate::common::Result;
use super::dxfilm_edge_reader::DXFilmEdgeReader;
use super::row_reader::RowReader;
pub struct ODReader<'a> {
reader: DXFilmEdgeReader<'a>, // THIS IS WRONG, SEE BELOW ONLY DOES ONE
// readers: Vec<dyn RowReader>,
try_harder: bool,
is_pure: bool,
min_line_count: u32,
return_errors: bool,
try_rotate: bool,
}
impl<'a> ODReader<'_> {
/**
* We're going to examine rows from the middle outward, searching alternately above and below the
* middle, and farther out each time. rowStep is the number of rows between each successive
* attempt above and below the middle. So we'd scan row middle, then middle - rowStep, then
* middle + rowStep, then middle - (2 * rowStep), etc.
* rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily
* decided that moving up and down by about 1/16 of the image is pretty good; we try more of the
* image if "trying harder".
*/
pub fn DoDecode<B: Binarizer>(
reader: &DXFilmEdgeReader,
image: &BinaryBitmap<B>,
tryHarder: bool,
rotate: bool,
isPure: bool,
maxSymbols: u32,
minLineCount: u32,
returnErrors: bool,
) -> Vec<RXingResult> {
let res: Vec<RXingResult> = Vec::new();
let decodingState = Vec::new();
// std::vector<std::unique_ptr<RowReader::DecodingState>> decodingState(readers.size());
let width: i32 = image.get_width() as i32;
let height: i32 = image.get_height() as i32;
if (rotate) {
std::mem::swap(&mut width, &mut height);
}
let middle: i32 = height / 2;
// TODO: find a better heuristic/parameterization if maxSymbols != 1
let rowStep: i32 = std::cmp::max(
1,
height
/ (if (tryHarder && !isPure) {
(if maxSymbols == 1 { 256 } else { 512 })
} else {
32
}),
);
let maxLines: i32 = if tryHarder
{height} else // Look at the whole image, not just the center
{15}; // 15 rows spaced 1/32 apart is roughly the middle half of the image
if (isPure) {
minLineCount = 1;
}
let checkRows = Vec::new();
let bars: PatternRow = PatternRow::new(vec![0; 128]); // e.g. EAN-13 has 59 bars/spaces
// bars.reserve(128); // e.g. EAN-13 has 59 bars/spaces
// #ifdef PRINT_DEBUG
// BitMatrix dbg(width, height);
// #endif
'outer: for i in 0..maxLines {
// for (int i = 0; i < maxLines; i++) {
// Scanning from the middle out. Determine which row we're looking at next:
let rowStepsAboveOrBelow: i32 = (i + 1) / 2;
let isAbove: bool = (i & 0x01) == 0; // i.e. is x even?
let rowNumber: i32 = middle
+ rowStep
* (if isAbove {
rowStepsAboveOrBelow
} else {
-rowStepsAboveOrBelow
});
let isCheckRow: bool = false;
if (rowNumber < 0 || rowNumber >= height) {
// Oops, if we run off the top or bottom, stop
break;
}
// See if we have additional check rows (see below) to process
if (!checkRows.is_empty()) {
--i;
rowNumber = checkRows.back();
checkRows.pop_back();
isCheckRow = true;
if (rowNumber < 0 || rowNumber >= height) {
continue;
}
}
if (!image.getPatternRow(rowNumber, if rotate { 90 } else { 0 }, bars)) {
continue;
}
// #ifdef PRINT_DEBUG
// bool val = false;
// int x = 0;
// for (auto b : bars) {
// for(int j = 0; j < b; ++j)
// dbg.set(x++, rowNumber, val);
// val = !val;
// }
// #endif
// While we have the image data in a PatternRow, it's fairly cheap to reverse it in place to
// handle decoding upside down barcodes.
// TODO: the DataBarExpanded (stacked) decoder depends on seeing each line from both directions. This
// 'surprising' and inconsistent. It also requires the decoderState to be shared between normal and reversed
// scans, which makes no sense in general because it would mix partial detection data from two codes of the same
// type next to each other. See also https://github.com/zxing-cpp/zxing-cpp/issues/87
for upsideDown in [false, true] {
// for (bool upsideDown : {false, true}) {
// trying again?
if (upsideDown) {
// reverse the row and continue
// std::reverse(bars.begin(), bars.end());
bars.reverse();
}
let readers = vec![reader];
// Look for a barcode
for r in 0..readers.len() {
// for (size_t r = 0; r < readers.size(); ++r) {
// If this is a pure symbol, then checking a single non-empty line is sufficient for all but the stacked
// DataBar codes. They are the only ones using the decodingState, which we can use as a flag here.
if (isPure && i && !decodingState[r]) {
continue;
}
let next = PatternView::from(bars);
loop {
let result = readers[r]
.decodePattern(rowNumber, &mut next, decodingState[r])
.ok();
if (result.isValid() || (returnErrors && result.error())) {
IncrementLineCount(&result);
if (upsideDown) {
// update position (flip horizontally).
let points = result.position();
for p in points {
// for (auto& p : points) {
p = point(width - p.getX() - 1, p.getY());
}
result.addPoints(points);
// result.setPosition(std::move(points));
}
if (rotate) {
let points = result.position();
for p in points {
// for (auto& p : points) {
p = point(p.getY(), width - p.getX() - 1);
}
result.addPoints(points);
// result.setPosition(std::move(points));
}
// check if we know this code already
for other in res {
// for (auto& other : res) {
if (result == other) {
// merge the position information
let dTop = PointT::maxAbsComponent(
other.position().topLeft() - result.position().topLeft(),
);
let dBot = PointT::maxAbsComponent(
other.position().bottomLeft() - result.position().topLeft(),
);
let points = other.position();
if (dTop < dBot
|| (dTop == dBot
&& rotate
^ (PointT::sumAbsComponent(points[0])
> PointT::sumAbsComponent(
result.position()[0],
))))
{
points[0] = result.position()[0];
points[1] = result.position()[1];
} else {
points[2] = result.position()[2];
points[3] = result.position()[3];
}
other.setPosition(points);
IncrementLineCount(&other);
// clear the result, so we don't insert it again below
result = None; //Result();
break;
}
}
if (result.format() != BarcodeFormat::UNSUPORTED_FORMAT) {
res.push(result);
// res.push_back(std::move(result));
// if we found a valid code we have not seen before but a minLineCount > 1,
// add additional check rows above and below the current one
if (!isCheckRow && minLineCount > 1 && rowStep > 1) {
checkRows = vec![rowNumber - 1, rowNumber + 1];
if (rowStep > 2) {
checkRows.push(rowNumber - 2);
checkRows.push(rowNumber + 2);
// checkRows.insert(checkRows.end(), {rowNumber - 2, rowNumber + 2});
}
}
}
if (maxSymbols
&& res.iter().fold(0, |acc, e| {
acc + i32::from((r.lineCount() >= minLineCount))
}) == maxSymbols)
{
break 'outer;
}
}
// make sure we make progress and we start the next try on a bar
next.shift(2 - (next.index() % 2));
next.extend();
if !(tryHarder && next.size()) {
break;
}
} //while (tryHarder && next.size());
}
}
}
// out:
// remove all symbols with insufficient line count
let it = res.iter().filter(|e| e.lineCount() < minLineCount);
// let it = std::remove_if(res.begin(), res.end(), [&](auto&& r) { return r.lineCount() < minLineCount; });
res.erase(it, res.end());
// if symbols overlap, remove the one with a lower line count
for (i, a) in res.iter().enumerate() {
// for (auto a = res.begin(); a != res.end(); ++a){
for b in res.iter().skip(i) {
// for (auto b = std::next(a); b != res.end(); ++b){
if (PointT::HaveIntersectingBoundingBoxes(a.position(), b.position())) {
*(if a.lineCount() < b.lineCount() { a } else { b }) = None;
}
}
}
//TODO: C++20 res.erase_if()
it = res
.iter()
.filter(|r| r.getBarcodeFormat() == BarcodeFormat::None);
// it = std::remove_if(res.begin(), res.end(), [](auto&& r) { return r.format() == BarcodeFormat::None; });
res.erase(it, res.end());
// #ifdef PRINT_DEBUG
// SaveAsPBM(dbg, rotate ? "od-log-r.pnm" : "od-log.pnm");
// #endif
res
}
}
impl<'a> ODReader<'_> {
pub fn decode_single<B: crate::Binarizer>(
&self,
hints: &DecodingHintDictionary,
image: &BinaryBitmap<B>,
) -> Result<RXingResult> {
let result = Self::DoDecode(
&self.reader,
image,
self.try_harder,
false,
self.is_pure,
1,
self.min_line_count,
self.return_errors,
);
if (result.is_empty() && self.try_rotate) {
result = Self::DoDecode(
&self.reader,
image,
self.try_harder,
true,
self.is_pure,
1,
self.min_line_count,
self.return_errors,
);
}
result.first().ok_or(Exceptions::NOT_FOUND)
// return FirstOrDefault(std::move(result));
}
pub fn decode_with_max_symbols<B: crate::Binarizer>(
&self,
hints: &DecodingHintDictionary,
image: &BinaryBitmap<B>,
maxSymbols: u32,
) -> Result<Vec<RXingResult>> {
let resH = Self::DoDecode(
&self.reader,
image,
self.try_harder,
false,
self.is_pure,
maxSymbols,
self.min_line_count,
self.return_errors,
);
if ((!maxSymbols || (resH) < maxSymbols) && self.try_rotate) {
let resV = Self::DoDecode(
&self.reader,
image,
self.try_harder,
true,
self.is_pure,
maxSymbols - resH.len() as u32,
self.min_line_count,
self.return_errors,
);
// resH.insert(resH.end(), resV.begin(), resV.end());
resH.append(&mut resV);
}
if resH.is_empty() {
Err(Exceptions::NOT_FOUND)
} else {
Ok(resH)
}
}
}
impl<'a> Reader for ODReader<'_> {
fn decode<B: crate::Binarizer>(
&mut self,
image: &mut crate::BinaryBitmap<B>,
) -> crate::common::Result<crate::RXingResult> {
self.decode_with_hints(image, &HashMap::new())
}
fn decode_with_hints<B: crate::Binarizer>(
&mut self,
image: &mut crate::BinaryBitmap<B>,
hints: &crate::DecodingHintDictionary,
) -> crate::common::Result<crate::RXingResult> {
self.decode_single(hints, image)
}
}
impl<'a> MultipleBarcodeReader for ODReader<'_> {
fn decode_multiple<B: crate::Binarizer>(
&mut self,
image: &mut crate::BinaryBitmap<B>,
) -> crate::common::Result<Vec<crate::RXingResult>> {
self.decode_multiple_with_hints(image, &HashMap::new())
}
fn decode_multiple_with_hints<B: crate::Binarizer>(
&mut self,
image: &mut crate::BinaryBitmap<B>,
hints: &crate::DecodingHintDictionary,
) -> crate::common::Result<Vec<crate::RXingResult>> {
self.decode_with_max_symbols(hints, image, u32::MAX)
}
}
impl<'a> ODReader<'_> {
pub fn new(hints: &DecodingHintDictionary) -> Self {
unimplemented!()
}
}
fn IncrementLineCount(r: &RXingResult) {
unimplemented!()
// ++r._lineCount;
}

254
src/oned/cpp/row_reader.rs Normal file
View File

@@ -0,0 +1,254 @@
use crate::common::cpp_essentials::{
BarAndSpace, GetPatternRow, NormalizedPattern, PatternRow, PatternType, ToInt, UpdateMinMax,
};
use crate::common::Result;
use crate::qrcode::cpp_port::detector::AppendBit;
use crate::{common::cpp_essentials::PatternView, RXingResult};
use super::dxfilm_edge_reader::Clock;
/*
* Copyright 2016 Nu-book Inc.
* Copyright 2016 ZXing authors
* Copyright 2020 Axel Waggershauser
*/
// SPDX-License-Identifier: Apache-2.0
/*
Code39 : 1:2/3, 5+4+1 (0x3|2x1 wide) -> 12-15 mods, v1-? | ToNarrowWide(OMG 1) == *
Codabar: 1:2/3, 4+3+1 (1x1|1x2|3x0 wide) -> 9-13 mods, v1-? | ToNarrowWide(OMG 2) == ABCD
ITF : 1:2/3, 5+5 (2x2 wide) -> mods, v6-?| .5, .38 == * | qz:10
Code93 : 1-4, 3+3 -> 9 mods v1-? | round to 1-4 == *
Code128: 1-4, 3+3 -> 11 mods v1-? | .7, .25 == ABC | qz:10
UPC/EAN: 1-4, 2+2 -> 7 mods f | .7, .48 == *
UPC-A: 11d 95m = 3 + 6*4 + 5 + 6*4 + 3 = 59 | qz:3
EAN-13: 12d 95m
UPC-E: 6d, 3 + 6*4 + 6 = 33
EAN-8: 8d, 3 + 4*4 + 5 + 4*4 + 3 = 43
RSS14 : 1-8, finder: (15,2+3), symbol: (15/16,4+4) | .45, .2 (finder only), 14d
code = 2xguard + 2xfinder + 4xsymbol = (96,23), stacked = 2x50 mods
RSSExp.: v?-74d/?-41c
*/
type Pattern = PatternRow;
type Counter = PatternRow;
type Index = Vec<u32>;
type Alphabet = Vec<char>;
// pub trait DecodingState: Default {
// // virtual ~DecodingState() = default;
// }
#[derive(Default, Debug)]
pub struct DecodingState {
// DXO
pub centerRow: u32,
pub clocks: Vec<Clock>,
}
/**
* Encapsulates functionality and implementation that is common to all families
* of one-dimensional barcodes.
*/
pub trait RowReader {
// type DS: DecodingState;
fn decodePattern(
&self,
rowNumber: u32,
next: &mut PatternView,
state: &mut Option<DecodingState>,
) -> Result<RXingResult>;
/**
* Determines how closely a set of observed counts of runs of black/white values matches a given
* target pattern. This is reported as the ratio of the total variance from the expected pattern
* proportions across all pattern elements, to the length of the pattern.
*
* @param counters observed counters
* @param pattern expected pattern
* @param maxIndividualVariance The most any counter can differ before we give up
* @return ratio of total variance between counters and pattern compared to total pattern size
*/
fn PatternMatchVariance(
counters: &Counter,
pattern: &Pattern,
length: usize,
maxIndividualVariance: f32,
) -> f32 {
let mut maxIndividualVariance = maxIndividualVariance;
let total: PatternType = counters.sum(); //counters.into_iter().take(length).copied().reduce(|acc,e| {acc + e} ).unwrap_or_default().into(); //Reduce(counters, counters + length, 0);
let patternLength: PatternType = pattern.sum(); //pattern.into().take(length).copied().reduce(|acc, e| {acc + e}).unwrap_or_default().into(); //Reduce(pattern, pattern + length, 0);
if (total < patternLength) {
// If we don't even have one pixel per unit of bar width, assume this is too small
// to reliably match, so fail:
return f32::MAX;
// return std::numeric_limits<float>::max();
}
let unitBarWidth: f32 = total as f32 / patternLength as f32;
maxIndividualVariance *= unitBarWidth;
let mut totalVariance: f32 = 0.0;
for x in 0..length {
// for (size_t x = 0; x < length; ++x) {
let variance: f32 = (counters[x] as f32 - pattern[x] as f32 * unitBarWidth).abs();
if (variance > maxIndividualVariance) {
return f32::MAX;
}
totalVariance += variance;
}
return totalVariance / total as f32;
}
fn PatternMatchVarianceNoLength(
counters: &Counter,
pattern: &Pattern,
maxIndividualVariance: f32,
) -> f32 {
assert!(counters.len() == pattern.len());
return Self::PatternMatchVariance(
counters,
pattern,
counters.len(),
maxIndividualVariance,
);
}
/**
* Attempts to decode a sequence of black/white lines into single
* digit.
*
* @param counters the counts of runs of observed black/white/black/... values
* @param patterns the list of patterns to compare the contents of counters to
* @param requireUnambiguousMatch the 'best match' must be better than all other matches
* @return The decoded digit index, -1 if no pattern matched
*/
fn DecodeDigit(
counters: &Counter,
patterns: Vec<Pattern>,
maxAvgVariance: f32,
maxIndividualVariance: f32,
requireUnambiguousMatch: Option<bool>,
) -> i32 {
let requireUnambiguousMatch = requireUnambiguousMatch.unwrap_or(true);
let mut bestVariance: f32 = maxAvgVariance; // worst variance we'll accept
const INVALID_MATCH: i32 = -1;
let mut bestMatch = INVALID_MATCH;
for i in 0..patterns.len() {
// for (int i = 0; i < Size(patterns); i++) {
let variance: f32 =
Self::PatternMatchVarianceNoLength(counters, &patterns[i], maxIndividualVariance);
if (variance < bestVariance) {
bestVariance = variance;
bestMatch = i as i32;
} else if (requireUnambiguousMatch && variance == bestVariance) {
// if we find a second 'best match' with the same variance, we can not reliably report to have a suitable match
bestMatch = INVALID_MATCH;
}
}
return bestMatch;
}
/**
* @brief NarrowWideThreshold calculates width thresholds to separate narrow and wide bars and spaces.
*
* This is useful for codes like Codabar, Code39 and ITF which distinguish between narrow and wide
* bars/spaces. Where wide ones are between 2 and 3 times as wide as the narrow ones.
*
* @param view containing one character
* @return threshold value for bars and spaces
*/
fn NarrowWideThreshold(view: &PatternView) -> BarAndSpace<i32> {
let mut m: BarAndSpace<i32> = BarAndSpace::new(view[0] as i32, view[1] as i32);
let mut M: BarAndSpace<i32> = m.clone();
for i in 0..view.size() {
// for (int i = 2; i < view.size(); ++i)
UpdateMinMax(&mut m[i], &mut M[i], view[i] as i32);
}
let mut res = BarAndSpace::default();
for i in 0..2 {
// for (int i = 0; i < 2; ++i) {
// check that
// a) wide <= 4 * narrow
// b) bars and spaces are not more than a factor of 2 (or 3 for the max) apart from each other
if (M[i] > 4 * (m[i] + 1) || M[i] > 3 * M[i + 1] || m[i] > 2 * (m[i + 1] + 1)) {
return BarAndSpace::default();
}
// the threshold is the average of min and max but at least 1.5 * min
res[i] = std::cmp::max((m[i] + M[i]) / 2, m[i] * 3 / 2);
}
return res;
}
/**
* @brief ToNarrowWidePattern takes a PatternView, calculates a NarrowWideThreshold and returns int where a '0' bit
* means narrow and a '1' bit means 'wide'.
*/
fn NarrowWideBitPattern(view: &PatternView) -> i32 {
let threshold = Self::NarrowWideThreshold(view);
if (!threshold.isValid()) {
return -1;
}
let mut pattern: i32 = 0;
for i in 0..view.size() {
// for (int i = 0; i < view.size(); ++i) {
if (view[i] as i32 > threshold[i] * 2) {
return -1;
}
AppendBit(&mut pattern, view[i] as i32 > threshold[i]);
}
return pattern;
}
/**
* @brief each bar/space is 1-4 modules wide, we have N bars/spaces, they are SUM modules wide in total
*/
fn OneToFourBitPattern<const LEN: usize, const SUM: usize>(view: &PatternView) -> Option<u32> {
// TODO: make sure none of the elements in the normalized pattern exceeds 4
ToInt(&NormalizedPattern::<LEN, SUM>(view).ok()?.map(|x| x as u32))
// ToInt(NormalizedPattern::<LEN, SUM>(view).unwrap_or_default()).unwrap_or(-1)
}
/**
* @brief Lookup the pattern in the table and return the character in alphabet at the same index.
* @returns 0 if pattern is not found. Used to be -1 but that fails on systems where char is unsigned.
*/
fn LookupBitPattern(pattern: u32, table: &Index, alphabet: &Alphabet) -> char {
if let Some(i) = table.iter().position(|e| *e == pattern) {
alphabet[i]
} else {
char::from(0)
}
// let i :i32 = IndexOf(table, pattern);
// return if i == -1 {0} else {alphabet[i]};
}
fn DecodeNarrowWidePattern(view: &PatternView, table: &Index, alphabet: &Alphabet) -> char {
return Self::LookupBitPattern(Self::NarrowWideBitPattern(view) as u32, table, alphabet);
}
}
fn DecodeSingleRow<Range, RR>(reader: &RR, range: &[Range]) -> Result<RXingResult>
where
Range: Into<PatternType> + Copy + Default + From<Range>,
RR: RowReader,
{
let mut row = PatternRow::default();
GetPatternRow(range, &mut row);
let mut view = PatternView::new(&row);
let state = DecodingState::default();
// std::unique_ptr<RowReader::DecodingState> state;
reader.decodePattern(0, &mut view, &mut Some(state))
}

View File

@@ -93,3 +93,5 @@ mod upc_e_writer;
pub use upc_e_writer::*; pub use upc_e_writer::*;
mod telepen_common; mod telepen_common;
pub mod cpp;