From b4b55ebb6614f42d901443eb27b5f7155839dc26 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Tue, 16 Jan 2024 12:25:44 -0600 Subject: [PATCH] wip: add dxo edge barcode (inop) --- src/barcode_format.rs | 2 + src/common/cpp_essentials/pattern.rs | 12 +- src/common/cpp_essentials/util.rs | 39 +++ src/oned/cpp/dxfilm_edge_reader.rs | 341 +++++++++++++++++++++++ src/oned/cpp/mod.rs | 4 + src/oned/cpp/one_d_reader.rs | 403 +++++++++++++++++++++++++++ src/oned/cpp/row_reader.rs | 254 +++++++++++++++++ src/oned/mod.rs | 2 + 8 files changed, 1055 insertions(+), 2 deletions(-) create mode 100644 src/oned/cpp/dxfilm_edge_reader.rs create mode 100644 src/oned/cpp/mod.rs create mode 100644 src/oned/cpp/one_d_reader.rs create mode 100644 src/oned/cpp/row_reader.rs diff --git a/src/barcode_format.rs b/src/barcode_format.rs index 70fd962..5ca636c 100644 --- a/src/barcode_format.rs +++ b/src/barcode_format.rs @@ -87,6 +87,8 @@ pub enum BarcodeFormat { /** UPC/EAN extension format. Not a stand-alone format. */ UPC_EAN_EXTENSION, + DXFilmEdge, + /// UNSUPORTED_FORMAT, } diff --git a/src/common/cpp_essentials/pattern.rs b/src/common/cpp_essentials/pattern.rs index 2ec70bc..7fc7bb5 100644 --- a/src/common/cpp_essentials/pattern.rs +++ b/src/common/cpp_essentials/pattern.rs @@ -47,6 +47,10 @@ impl PatternRow { pub fn into_pattern_view(&self) -> PatternView { PatternView::new(self) } + + pub fn sum(&self) -> PatternType { + self.0.iter().sum() + } } 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 */ -#[derive(Default)] -struct BarAndSpace { +#[derive(Default, Clone)] +pub struct BarAndSpace { bar: T, space: T, } impl BarAndSpace { + pub fn new(bar: T, space: T) -> BarAndSpace { + Self { bar, space } + } + #[allow(dead_code)] pub fn isValid(&self) -> bool { self.bar != T::default() && self.space != T::default() diff --git a/src/common/cpp_essentials/util.rs b/src/common/cpp_essentials/util.rs index 97ddde1..970ecf5 100644 --- a/src/common/cpp_essentials/util.rs +++ b/src/common/cpp_essentials/util.rs @@ -1,4 +1,8 @@ +use std::iter::Sum; +use std::ops::Shl; + use crate::common::Result; +use crate::qrcode::cpp_port::detector::AppendBit; use crate::{Exceptions, Point}; use super::{Direction, RegressionLineTrait}; @@ -66,3 +70,38 @@ pub fn ToString>(val: T, len: usize) -> Result { Ok(result.iter().collect()) } + +pub fn ToInt(a: &[u32]) -> Option { + if a.iter().sum::() <= 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 { + // 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); +} diff --git a/src/oned/cpp/dxfilm_edge_reader.rs b/src/oned/cpp/dxfilm_edge_reader.rs new file mode 100644 index 0000000..39d07b8 --- /dev/null +++ b/src/oned/cpp/dxfilm_edge_reader.rs @@ -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( + view: &PatternView, + pattern: &FixedPattern, + minQuietZone: f32, +) -> bool { + const E2E: bool = false; + let view = view.subView(0, Some(N)); + view.isValid() + && crate::common::cpp_essentials::pattern::IsPattern::( + &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 { + 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, + ) -> Result { + // if (!state) { + // state.reset(new DXFEState); + // static_cast(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| { + 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 = 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::(); //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, {}); + } +} diff --git a/src/oned/cpp/mod.rs b/src/oned/cpp/mod.rs new file mode 100644 index 0000000..807f6d6 --- /dev/null +++ b/src/oned/cpp/mod.rs @@ -0,0 +1,4 @@ +mod dxfilm_edge_reader; +mod one_d_reader; +mod row_reader; +pub use one_d_reader::ODReader; diff --git a/src/oned/cpp/one_d_reader.rs b/src/oned/cpp/one_d_reader.rs new file mode 100644 index 0000000..da9fa2d --- /dev/null +++ b/src/oned/cpp/one_d_reader.rs @@ -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, + 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( + reader: &DXFilmEdgeReader, + image: &BinaryBitmap, + tryHarder: bool, + rotate: bool, + isPure: bool, + maxSymbols: u32, + minLineCount: u32, + returnErrors: bool, + ) -> Vec { + let res: Vec = Vec::new(); + + let decodingState = Vec::new(); + // std::vector> 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( + &self, + hints: &DecodingHintDictionary, + image: &BinaryBitmap, + ) -> Result { + 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( + &self, + hints: &DecodingHintDictionary, + image: &BinaryBitmap, + maxSymbols: u32, + ) -> Result> { + 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( + &mut self, + image: &mut crate::BinaryBitmap, + ) -> crate::common::Result { + self.decode_with_hints(image, &HashMap::new()) + } + + fn decode_with_hints( + &mut self, + image: &mut crate::BinaryBitmap, + hints: &crate::DecodingHintDictionary, + ) -> crate::common::Result { + self.decode_single(hints, image) + } +} + +impl<'a> MultipleBarcodeReader for ODReader<'_> { + fn decode_multiple( + &mut self, + image: &mut crate::BinaryBitmap, + ) -> crate::common::Result> { + self.decode_multiple_with_hints(image, &HashMap::new()) + } + + fn decode_multiple_with_hints( + &mut self, + image: &mut crate::BinaryBitmap, + hints: &crate::DecodingHintDictionary, + ) -> crate::common::Result> { + 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; +} diff --git a/src/oned/cpp/row_reader.rs b/src/oned/cpp/row_reader.rs new file mode 100644 index 0000000..69260c3 --- /dev/null +++ b/src/oned/cpp/row_reader.rs @@ -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; +type Alphabet = Vec; + +// pub trait DecodingState: Default { +// // virtual ~DecodingState() = default; +// } + +#[derive(Default, Debug)] +pub struct DecodingState { + // DXO + pub centerRow: u32, + pub clocks: Vec, +} + +/** +* 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, + ) -> Result; + + /** + * 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::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, + maxAvgVariance: f32, + maxIndividualVariance: f32, + requireUnambiguousMatch: Option, + ) -> 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 { + let mut m: BarAndSpace = BarAndSpace::new(view[0] as i32, view[1] as i32); + let mut M: BarAndSpace = 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(view: &PatternView) -> Option { + // TODO: make sure none of the elements in the normalized pattern exceeds 4 + ToInt(&NormalizedPattern::(view).ok()?.map(|x| x as u32)) + // ToInt(NormalizedPattern::(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(reader: &RR, range: &[Range]) -> Result +where + Range: Into + Copy + Default + From, + RR: RowReader, +{ + let mut row = PatternRow::default(); + GetPatternRow(range, &mut row); + let mut view = PatternView::new(&row); + + let state = DecodingState::default(); + + // std::unique_ptr state; + reader.decodePattern(0, &mut view, &mut Some(state)) +} diff --git a/src/oned/mod.rs b/src/oned/mod.rs index ed6d468..6588870 100644 --- a/src/oned/mod.rs +++ b/src/oned/mod.rs @@ -93,3 +93,5 @@ mod upc_e_writer; pub use upc_e_writer::*; mod telepen_common; + +pub mod cpp;