wip: inop dx_film_edge port and filtered reader

This commit is contained in:
Henry Schimke
2024-01-30 13:19:53 -06:00
parent 9e684b2b66
commit d508253aa9
23 changed files with 421 additions and 36 deletions

View File

@@ -119,6 +119,7 @@ impl Display for BarcodeFormat {
BarcodeFormat::UPC_A => "upc a",
BarcodeFormat::UPC_E => "upc e",
BarcodeFormat::UPC_EAN_EXTENSION => "upc/ean extension",
BarcodeFormat::DXFilmEdge => "DXFilmEdge",
_ => "unsuported",
}
)
@@ -166,6 +167,7 @@ impl From<&str> for BarcodeFormat {
"upc e" | "upc_e" | "upce" => BarcodeFormat::UPC_E,
"upc ean extension" | "upc extension" | "ean extension" | "upc/ean extension"
| "upc_ean_extension" => BarcodeFormat::UPC_EAN_EXTENSION,
"DXFilmEdge" | "dxfilmedge" | "dx film edge" => BarcodeFormat::DXFilmEdge,
_ => BarcodeFormat::UNSUPORTED_FORMAT,
}
}

View File

@@ -32,7 +32,7 @@ use crate::{
pub struct BinaryBitmap<B: Binarizer> {
binarizer: B,
matrix: Option<BitMatrix>,
pub(crate) matrix: Option<BitMatrix>,
}
impl<B: Binarizer> BinaryBitmap<B> {

View File

@@ -16,7 +16,7 @@
use std::rc::Rc;
use image::{DynamicImage, ImageBuffer, Luma};
use image::{DynamicImage, GenericImageView, ImageBuffer, Luma, Pixel};
use imageproc::geometric_transformations::rotate_about_center;
use crate::common::Result;
@@ -108,6 +108,33 @@ impl LuminanceSource for BufferedImageLuminanceSource {
pixels
}
fn get_column(&self, x: usize) -> Vec<u8> {
let width = self.get_height(); // - self.left as usize;
let pixels: Vec<u8> = || -> Option<Vec<u8>> {
Some(
self.image
.as_luma8()?
.rows()
.skip(self.top as usize)
.fold(Vec::default(), |mut acc, e| {
acc.push(
e.into_iter()
.nth(self.left as usize + x)
.unwrap_or(&Luma([0_u8])),
);
acc
})
.iter()
.map(|&p| p.0[0])
.collect(),
)
}()
.unwrap_or_default();
pixels
}
fn get_matrix(&self) -> Vec<u8> {
if self.height == self.image.height() as usize && self.width == self.image.width() as usize
{
@@ -201,4 +228,8 @@ impl LuminanceSource for BufferedImageLuminanceSource {
top: 0,
})
}
fn get_luma8_point(&self, x: usize, y: usize) -> u8 {
self.image.get_pixel(x as u32, y as u32).to_luma().0[0]
}
}

View File

@@ -217,7 +217,7 @@ impl<'a> PatternView<'a> {
pub fn isValidWithN(&self, n: usize) -> bool {
!self.data.0.is_empty()
&& self.start <= self.current + self.start
&& self.current + n <= (self.data.0.len())
&& self.current + n < (self.data.0.len())
/*return _data && _data >= _base && _data + n <= _end;*/
}
pub fn isValid(&self) -> bool {
@@ -271,7 +271,10 @@ impl<'a> PatternView<'a> {
}
pub fn extend(&mut self) {
self.count = std::cmp::max(0, self.data.len() - (self.current + self.start))
self.count = std::cmp::max(
0,
self.data.len() as isize - (self.current + self.start) as isize,
) as usize
}
fn try_get_index(&self, index: isize) -> Option<PatternType> {

View File

@@ -27,7 +27,7 @@ use once_cell::unsync::OnceCell;
use crate::common::Result;
use crate::{Binarizer, Exceptions, LuminanceSource};
use super::{BitArray, BitMatrix};
use super::{BitArray, BitMatrix, LineOrientation};
const LUMINANCE_BITS: usize = 5;
const LUMINANCE_SHIFT: usize = 8 - LUMINANCE_BITS;
@@ -107,8 +107,55 @@ impl<LS: LuminanceSource> Binarizer for GlobalHistogramBinarizer<LS> {
Ok(Cow::Borrowed(row))
}
fn get_black_line(&self, l: usize, lt: super::LineOrientation) -> Result<Cow<BitArray>> {
unimplemented!()
fn get_black_line(&self, l: usize, lt: LineOrientation) -> Result<Cow<BitArray>> {
if lt == LineOrientation::Row {
self.get_black_row(l)
} else {
let col = self.black_column_cache[l].get_or_try_init(|| {
let source = self.get_luminance_source();
let width = source.get_height();
let mut col = BitArray::with_size(width);
// self.initArrays(width);
let localLuminances = source.get_column(l);
let mut localBuckets = [0; LUMINANCE_BUCKETS]; //self.buckets.clone();
for x in 0..width {
// for (int x = 0; x < width; x++) {
localBuckets[((localLuminances[x]) >> LUMINANCE_SHIFT) as usize] += 1;
}
let blackPoint = Self::estimateBlackPoint(&localBuckets)?;
if width < 3 {
// Special case for very small images
for (x, lum) in localLuminances.iter().enumerate().take(width) {
// for x in 0..width {
// for (int x = 0; x < width; x++) {
if (*lum as u32) < blackPoint {
col.set(x);
}
}
} else {
let mut left = localLuminances[0]; // & 0xff;
let mut center = localLuminances[1]; // & 0xff;
for x in 1..width - 1 {
// for (int x = 1; x < width - 1; x++) {
let right = localLuminances[x + 1];
// A simple -1 4 -1 box filter with a weight of 2.
if ((center as i64 * 4) - left as i64 - right as i64) / 2
< blackPoint as i64
{
col.set(x);
}
left = center;
center = right;
}
}
Ok(col)
})?;
Ok(Cow::Borrowed(col))
}
}
// Does not sharpen the data, as this call is intended to only be used by 2D Readers.

View File

@@ -0,0 +1,216 @@
use std::collections::HashMap;
use crate::{Binarizer, BinaryBitmap, Exceptions, Luma8LuminanceSource, LuminanceSource, Reader};
use crate::common::{BitMatrix, Result};
pub const DEFAULT_DOWNSCALE_THRESHHOLD: usize = 500;
pub const DEFAULT_DOWNSCALE_FACTOR: usize = 3;
/// Passed image data is ignored, only the image data
pub struct FilteredImageReader<R: Reader, B: Binarizer<Source = Luma8LuminanceSource>> {
reader: R,
source: Luma8LuminanceSource,
binarizer: B,
}
impl<R: Reader, B: Binarizer<Source = Luma8LuminanceSource>> FilteredImageReader<R, B> {
pub fn new<I: LuminanceSource + Clone + Into<Luma8LuminanceSource>>(
reader: R,
source: I,
binarizer: B,
) -> Self {
Self {
reader,
source: source.into(),
binarizer,
}
}
}
impl<R: Reader, B1: Binarizer<Source = Luma8LuminanceSource>> Reader
for FilteredImageReader<R, B1>
{
fn decode<B: crate::Binarizer>(
&mut self,
_image: &mut crate::BinaryBitmap<B>,
) -> crate::common::Result<crate::RXingResult> {
self.decode_with_hints(_image, &HashMap::default())
}
fn decode_with_hints<B: crate::Binarizer>(
&mut self,
_image: &mut crate::BinaryBitmap<B>,
hints: &crate::DecodingHintDictionary,
) -> crate::common::Result<crate::RXingResult> {
let pyramids = LumImagePyramid::new(
self.source.clone(),
DEFAULT_DOWNSCALE_THRESHHOLD,
DEFAULT_DOWNSCALE_FACTOR,
)
.ok_or(Exceptions::ILLEGAL_ARGUMENT)?;
for layer in pyramids.layers {
let mut b = BinaryBitmap::new(self.binarizer.create_binarizer(layer));
for close in [false, true] {
if close {
let Ok(_) = b.close() else {
continue;
};
}
if let Ok(res) = self.reader.decode_with_hints(&mut b, hints) {
return Ok(res);
} else {
continue;
}
}
}
Err(Exceptions::NOT_FOUND)
}
}
#[derive(Debug, Clone)]
struct LumImagePyramid {
buffers: Vec<Luma8LuminanceSource>,
pub layers: Vec<Luma8LuminanceSource>,
}
impl Default for LumImagePyramid {
fn default() -> Self {
Self {
buffers: Default::default(),
layers: Default::default(),
}
}
}
impl LumImagePyramid {
pub fn new(image: Luma8LuminanceSource, threshold: usize, factor: usize) -> Option<Self> {
let mut new_self = Self::default();
new_self.layers.push(image);
// TODO: if only matrix codes were considered, then using std::min would be sufficient (see #425)
while threshold > 0
&& std::cmp::max(
new_self.layers.last()?.get_width(),
new_self.layers.last()?.get_height(),
) > threshold
&& std::cmp::min(
new_self.layers.last()?.get_width(),
new_self.layers.last()?.get_height(),
) >= factor
{
new_self.add_layer_with_factor(factor).ok()?;
}
if false {
// Reversing the layers means we'd start with the smallest. that can make sense if we are only looking for a
// single symbol. If we start with the higher resolution, we get better (high res) position information.
// TODO: see if masking out higher res layers based on found symbols in lower res helps overall performance.
new_self.layers.reverse();
}
Some(new_self)
}
fn add_layer<const N: usize>(&mut self) -> Result<()> {
let siv = self.layers.last().ok_or(Exceptions::ILLEGAL_ARGUMENT)?;
self.buffers.push(Luma8LuminanceSource::with_empty_image(
siv.get_width() / N,
siv.get_height() / N,
));
let div = self
.buffers
.last_mut()
.ok_or(Exceptions::ILLEGAL_ARGUMENT)?;
let div_height = div.get_height();
let div_width = div.get_width();
let d_vec = div.get_matrix_mut();
for d in d_vec.iter_mut() {
for dy in 0..div_height {
// for (int dy = 0; dy < div.height(); ++dy){
for dx in 0..div_width {
// for (int dx = 0; dx < div.width(); ++dx) {
let mut sum = (N * N) as u8 / 2;
for ty in 0..N {
// for (int ty = 0; ty < N; ++ty){
for tx in 0..N {
// for (int tx = 0; tx < N; ++tx) {
sum += siv.get_luma8_point(dx * N + tx, dy * N + ty);
}
}
*d = sum / (N * N) as u8;
}
}
}
self.layers.push(
self.buffers
.last()
.ok_or(Exceptions::ILLEGAL_ARGUMENT)?
.clone(),
);
Ok(())
}
fn add_layer_with_factor(&mut self, factor: usize) -> Result<()> {
// help the compiler's auto-vectorizer by hard-coding the scale factor
match factor {
2 => self.add_layer::<2>(),
3 => self.add_layer::<3>(),
4 => self.add_layer::<4>(),
_ => Err(Exceptions::illegal_argument_with(
"Invalid ReaderOptions::downscaleFactor",
)),
}
}
}
const SET_V: u32 = 0xff; // allows playing with SIMD binarization
impl<B: Binarizer> BinaryBitmap<B> {
pub fn close(&mut self) -> Result<()> {
if let Some(mut matrix) = self.matrix.as_mut() {
// if (_cache->matrix) {
// auto& matrix = *const_cast<BitMatrix*>(_cache->matrix.get());
let mut tmp = BitMatrix::new(matrix.width(), matrix.height())?;
// dilate
SumFilter(matrix, &mut tmp, |sum| {
return u32::from(sum > 0 * SET_V) * SET_V;
});
// erode
SumFilter(&tmp, &mut matrix, |sum| {
return u32::from(sum == 9 * SET_V) * SET_V;
});
}
Ok(())
// _closed = true;
}
}
fn SumFilter<F>(input: &BitMatrix, output: &mut BitMatrix, func: F)
where
F: Fn(u32) -> u32,
{
for row in 1..output.height() {
for col in 0..output.width() - 1 {
let in0 = input.getRow(row); //.row(0).begin();
let in1 = input.getRow(row + 1); //.row(1).begin();
let in2 = input.getRow(row + 2); //.row(2).begin();
let mut sum = 0;
for j in 0..3 {
// for (int j = 0; j < 3; ++j){
sum += in0.get(j) as u32 + in1.get(j) as u32 + in2.get(j) as u32;
}
output.set_bool(row, col, func(sum) != 0);
}
}
}

View File

@@ -104,6 +104,9 @@ pub mod helpers;
mod luma_luma_source;
pub use luma_luma_source::*;
mod filtered_image_reader;
pub use filtered_image_reader::*;
#[cfg(feature = "svg_read")]
mod svg_luminance_source;
#[cfg(feature = "svg_read")]

View File

@@ -2,6 +2,7 @@ use crate::common::Result;
use crate::LuminanceSource;
/// A simple luma8 source for bytes, supports cropping but not rotation
#[derive(Debug, Clone)]
pub struct Luma8LuminanceSource {
/// image dimension in form (x,y)
dimensions: (u32, u32),
@@ -27,6 +28,19 @@ impl LuminanceSource for Luma8LuminanceSource {
.collect()
}
fn get_column(&self, x: usize) -> Vec<u8> {
self.data
.chunks_exact(self.original_dimension.0 as usize)
.skip(self.origin.1 as usize)
.fold(Vec::default(), |mut acc, e| {
acc.push(e[self.origin.0 as usize + x as usize]);
acc
})
.iter()
.map(|byte| Self::invert_if_should(*byte, self.inverted))
.collect()
}
fn get_matrix(&self) -> Vec<u8> {
self.data
.iter()
@@ -93,6 +107,10 @@ impl LuminanceSource for Luma8LuminanceSource {
"This luminance source does not support rotation by 45 degrees.",
))
}
fn get_luma8_point(&self, x: usize, y: usize) -> u8 {
todo!()
}
}
impl Luma8LuminanceSource {
@@ -159,6 +177,20 @@ impl Luma8LuminanceSource {
}
}
pub fn with_empty_image(width: usize, height: usize) -> Self {
Self {
dimensions: (width as u32, height as u32),
origin: (0, 0),
data: vec![0u8; width * height],
inverted: false,
original_dimension: (width as u32, height as u32),
}
}
pub fn get_matrix_mut(&mut self) -> &mut Vec<u8> {
&mut self.data
}
#[inline(always)]
fn invert_if_should(byte: u8, invert: bool) -> u8 {
if invert {

View File

@@ -48,6 +48,9 @@ pub trait LuminanceSource {
*/
fn get_row(&self, y: usize) -> Vec<u8>;
/// Get a column of of the image
fn get_column(&self, x: usize) -> Vec<u8>;
/**
* Fetches luminance data for the underlying bitmap. Values should be fetched using:
* {@code int luminance = array[y * width + x] & 0xff}
@@ -149,6 +152,8 @@ pub trait LuminanceSource {
iv
}
fn get_luma8_point(&self, x: usize, y: usize) -> u8;
/*
@Override
public final String toString() {

View File

@@ -17,6 +17,7 @@
use std::collections::{HashMap, HashSet};
use crate::common::Result;
use crate::oned::cpp::ODReader;
use crate::qrcode::cpp_port::QrReader;
use crate::{
aztec::AztecReader, datamatrix::DataMatrixReader, maxicode::MaxiCodeReader,
@@ -194,6 +195,9 @@ impl MultiFormatReader {
BarcodeFormat::MAXICODE => {
MaxiCodeReader::default().decode_with_hints(image, &self.hints)
}
BarcodeFormat::DXFilmEdge => {
ODReader::new(&self.hints).decode_with_hints(image, &self.hints)
}
_ => Err(Exceptions::UNSUPPORTED_OPERATION),
};
if res.is_ok() {
@@ -230,6 +234,9 @@ impl MultiFormatReader {
if let Ok(res) = MaxiCodeReader::default().decode_with_hints(image, &self.hints) {
return Ok(res);
}
if let Ok(res) = ODReader::new(&self.hints).decode_with_hints(image, &self.hints) {
return Ok(res);
}
if self.try_harder {
if let Ok(res) = self.one_d_reader.decode_with_hints(image, &self.hints) {

View File

@@ -71,7 +71,7 @@ fn DistIsBelowThreshold(a: PointI, b: PointI, threshold: PointI) -> bool {
}
// DX Film Edge clock track found on 35mm films.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub(super) struct Clock {
hasFrameNr: bool, // = false; // Clock track (thus data track) with frame number (longer version)
rowNumber: u32, // = 0,

View File

@@ -17,7 +17,7 @@ use crate::{Binarizer, DecodeHintType, DecodeHintValue};
use crate::common::{LineOrientation, Quadrilateral, Result};
use super::dxfilm_edge_reader::DXFilmEdgeReader;
use super::row_reader::RowReader;
use super::row_reader::{DecodingState, RowReader};
pub struct ODReader<'a> {
reader: DXFilmEdgeReader<'a>, // THIS IS WRONG, SEE BELOW ONLY DOES ONE
@@ -47,11 +47,11 @@ impl<'a> ODReader<'_> {
isPure: bool,
maxSymbols: u32,
minLineCount: u32,
returnErrors: bool,
_returnErrors: bool,
) -> Vec<RXingResult> {
let mut res: Vec<Option<RXingResult>> = Vec::new();
let mut decodingState: Vec<&mut Option<super::row_reader::DecodingState>> = Vec::new();
let mut decodingState: Vec<Option<DecodingState>> = vec![Some(DecodingState::default()); 1];
// std::vector<std::unique_ptr<RowReader::DecodingState>> decodingState(readers.size());
let mut minLineCount = minLineCount;
@@ -120,6 +120,7 @@ impl<'a> ODReader<'_> {
checkRows.pop();
isCheckRow = true;
if (rowNumber < 0 || rowNumber >= height) {
i += 1;
continue;
}
}
@@ -134,6 +135,7 @@ impl<'a> ODReader<'_> {
) {
r.as_ref().into()
} else {
i += 1;
continue;
};
// let img = if rotate {let a = image.rotate_counter_clockwise(); &a} else {image};
@@ -182,7 +184,7 @@ impl<'a> ODReader<'_> {
let mut next = PatternView::new(&bars);
loop {
let mut result_hld = readers[r]
.decodePattern(rowNumber as u32, &mut next, decodingState[r])
.decodePattern(rowNumber as u32, &mut next, &mut decodingState[r])
.ok();
if result_hld.is_some()
/*|| (returnErrors && result.is_none())*/
@@ -277,6 +279,7 @@ impl<'a> ODReader<'_> {
// 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() > 0) {
break;
}
@@ -371,29 +374,7 @@ impl<'a> ODReader<'_> {
_hints: &DecodingHintDictionary,
image: &BinaryBitmap<B>,
) -> Result<RXingResult> {
let mut 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,
);
}
let result = self.decode_with_max_symbols(_hints, image, u32::MAX)?;
result.first().cloned().ok_or(Exceptions::NOT_FOUND)
// return FirstOrDefault(std::move(result));

View File

@@ -41,7 +41,7 @@ type Alphabet = Vec<char>;
// // virtual ~DecodingState() = default;
// }
#[derive(Default, Debug)]
#[derive(Default, Debug, Clone)]
pub struct DecodingState {
// DXO
pub centerRow: u32,

View File

@@ -256,6 +256,10 @@ impl LuminanceSource for PlanarYUVLuminanceSource {
row
}
fn get_column(&self, x: usize) -> Vec<u8> {
unimplemented!()
}
fn get_matrix(&self) -> Vec<u8> {
let width = self.get_width();
let height = self.get_height();
@@ -332,4 +336,8 @@ impl LuminanceSource for PlanarYUVLuminanceSource {
fn invert(&mut self) {
self.invert = !self.invert;
}
fn get_luma8_point(&self, x: usize, y: usize) -> u8 {
unimplemented!()
}
}

View File

@@ -58,6 +58,10 @@ impl LuminanceSource for RGBLuminanceSource {
row
}
fn get_column(&self, x: usize) -> Vec<u8> {
unimplemented!()
}
fn get_matrix(&self) -> Vec<u8> {
let width = self.get_width();
let height = self.get_height();
@@ -127,6 +131,14 @@ impl LuminanceSource for RGBLuminanceSource {
fn invert(&mut self) {
self.invert = !self.invert;
}
fn get_luma8_point(&self, x: usize, y: usize) -> u8 {
let width = self.get_width();
let row_offset = (y + self.top) * self.dataWidth + self.left;
let col_offset = (x + self.left);
self.luminances[row_offset + col_offset]
}
}
impl RGBLuminanceSource {

View File

@@ -11,6 +11,10 @@ impl LuminanceSource for SVGLuminanceSource {
self.0.get_row(y)
}
fn get_column(&self, x: usize) -> Vec<u8> {
self.0.get_column(x)
}
fn get_matrix(&self) -> Vec<u8> {
self.0.get_matrix()
}
@@ -46,6 +50,10 @@ impl LuminanceSource for SVGLuminanceSource {
fn rotate_counter_clockwise_45(&self) -> Result<Self> {
self.0.rotate_counter_clockwise_45().map(Self)
}
fn get_luma8_point(&self, x: usize, y: usize) -> u8 {
self.0.get_luma8_point(x, y)
}
}
impl SVGLuminanceSource {