From ec0512722de04fb98c673f8dd8eb3e40d1784570 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Thu, 12 Jan 2023 13:46:39 -0600 Subject: [PATCH] add svg_read support (basic) --- Cargo.toml | 10 +- src/helpers.rs | 104 ++++++++++++++++++ src/lib.rs | 7 +- ...e.rs => rgb_luminance_source_test_case.rs} | 0 src/svg_luminance_source.rs | 85 ++++++++++++++ 5 files changed, 201 insertions(+), 5 deletions(-) rename src/{RGBLuminanceSourceTestCase.rs => rgb_luminance_source_test_case.rs} (100%) create mode 100644 src/svg_luminance_source.rs diff --git a/Cargo.toml b/Cargo.toml index f9155ac..156ba16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,20 +23,22 @@ uriparse = "0.6.4" chrono = "0.4.23" chrono-tz = "0.8" image = {version = "0.24", optional = true} -imageproc = {version = "0.23.0", optional = true} -unicode-segmentation = "1.10.0" +imageproc = {version = "0.23", optional = true} +unicode-segmentation = "1.10" codepage-437 = "0.1.0" -rxing-one-d-proc-derive = "0.3.0" +rxing-one-d-proc-derive = "0.3" #rxing-one-d-proc-derive = {path ="../rxing-one-d-proc-derive"} num = "0.4.0" svg = {version = "0.13", optional = true} +resvg = {version = "0.28.0", optional = true, default-features=false} [dev-dependencies] java-properties = "1.4.1" java-rand = "0.2.0" [features] -default = ["image", "svg_write"] +default = ["image", "svg_write", "svg_read"] image = ["dep:image", "dep:imageproc"] allow_forced_iso_ied_18004_compliance = [] svg_write = ["dep:svg"] +svg_read = ["dep:resvg", "image"] diff --git a/src/helpers.rs b/src/helpers.rs index b84f45e..9fa4982 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -13,6 +13,110 @@ use crate::{ Reader, }; +#[cfg(feature = "svg_read")] +pub fn detect_in_svg( + file_name: &str, + barcode_type: Option, +) -> Result { + detect_in_svg_with_hints(file_name, barcode_type, &mut HashMap::new()) +} + +#[cfg(feature = "svg_read")] +pub fn detect_in_svg_with_hints( + file_name: &str, + barcode_type: Option, + hints: &mut DecodingHintDictionary, +) -> Result { + use std::{fs::File, io::Read}; + + use crate::SVGLuminanceSource; + + let path = PathBuf::from(file_name); + if !path.exists() { + return Err(Exceptions::IllegalArgumentException(Some( + "file does not exist".to_owned(), + ))); + } + + let Ok(mut file) = File::open(path) else { + return Err(Exceptions::IllegalArgumentException(Some("file cannot be opened".to_owned()))); + }; + + let mut svg_data = Vec::new(); + if file.read_to_end(&mut svg_data).is_err() { + return Err(Exceptions::IllegalArgumentException(Some( + "file cannot be read".to_owned(), + ))); + } + + let mut multi_format_reader = MultiFormatReader::default(); + + if let Some(bc_type) = barcode_type { + hints.insert( + DecodeHintType::POSSIBLE_FORMATS, + DecodeHintValue::PossibleFormats(HashSet::from([bc_type])), + ); + } + + hints + .entry(DecodeHintType::TRY_HARDER) + .or_insert(DecodeHintValue::TryHarder(true)); + + multi_format_reader.decode_with_hints( + &mut BinaryBitmap::new(Rc::new(HybridBinarizer::new(Box::new( + SVGLuminanceSource::new(&svg_data)?, + )))), + hints, + ) +} + +#[cfg(feature = "svg_read")] +pub fn detect_multiple_in_svg(file_name: &str) -> Result, Exceptions> { + detect_multiple_in_svg_with_hints(file_name, &mut HashMap::new()) +} + +#[cfg(feature = "svg_read")] +pub fn detect_multiple_in_svg_with_hints( + file_name: &str, + hints: &mut DecodingHintDictionary, +) -> Result, Exceptions> { + use std::{fs::File, io::Read}; + + use crate::SVGLuminanceSource; + + let path = PathBuf::from(file_name); + if !path.exists() { + return Err(Exceptions::IllegalArgumentException(Some( + "file does not exist".to_owned(), + ))); + } + + let Ok(mut file) = File::open(path) else { + return Err(Exceptions::IllegalArgumentException(Some("file cannot be opened".to_owned()))); + }; + + let mut svg_data = Vec::new(); + if file.read_to_end(&mut svg_data).is_err() { + return Err(Exceptions::IllegalArgumentException(Some( + "file cannot be read".to_owned(), + ))); + } + + let multi_format_reader = MultiFormatReader::default(); + let mut scanner = GenericMultipleBarcodeReader::new(multi_format_reader); + + hints + .entry(DecodeHintType::TRY_HARDER) + .or_insert(DecodeHintValue::TryHarder(true)); + + scanner.decode_multiple_with_hints( + &mut BinaryBitmap::new(Rc::new(HybridBinarizer::new(Box::new( + SVGLuminanceSource::new(&svg_data)?, + )))), + hints, + ) +} + #[cfg(feature = "image")] pub fn detect_in_file( file_name: &str, diff --git a/src/lib.rs b/src/lib.rs index 36ece51..8bfe758 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ pub use buffered_image_luminance_source::*; mod PlanarYUVLuminanceSourceTestCase; #[cfg(test)] -mod RGBLuminanceSourceTestCase; +mod rgb_luminance_source_test_case; pub type EncodingHintDictionary = HashMap; pub type DecodingHintDictionary = HashMap; @@ -99,3 +99,8 @@ pub mod helpers; mod luma_luma_source; pub use luma_luma_source::*; + +#[cfg(feature = "svg_read")] +mod svg_luminance_source; +#[cfg(feature = "svg_read")] +pub use svg_luminance_source::*; diff --git a/src/RGBLuminanceSourceTestCase.rs b/src/rgb_luminance_source_test_case.rs similarity index 100% rename from src/RGBLuminanceSourceTestCase.rs rename to src/rgb_luminance_source_test_case.rs diff --git a/src/svg_luminance_source.rs b/src/svg_luminance_source.rs new file mode 100644 index 0000000..f0c31ed --- /dev/null +++ b/src/svg_luminance_source.rs @@ -0,0 +1,85 @@ +use crate::{BufferedImageLuminanceSource, Exceptions, LuminanceSource}; +use image::{DynamicImage, RgbaImage}; +use resvg::{self, usvg::Options}; + +pub struct SVGLuminanceSource(BufferedImageLuminanceSource); + +impl LuminanceSource for SVGLuminanceSource { + fn getRow(&self, y: usize) -> Vec { + self.0.getRow(y) + } + + fn getMatrix(&self) -> Vec { + self.0.getMatrix() + } + + fn getWidth(&self) -> usize { + self.0.getWidth() + } + + fn getHeight(&self) -> usize { + self.0.getHeight() + } + + fn invert(&mut self) { + self.0.invert() + } + + fn isCropSupported(&self) -> bool { + self.0.isCropSupported() + } + + fn crop( + &self, + left: usize, + top: usize, + width: usize, + height: usize, + ) -> Result, Exceptions> { + self.0.crop(left, top, width, height) + } + + fn isRotateSupported(&self) -> bool { + self.0.isRotateSupported() + } + + fn rotateCounterClockwise(&self) -> Result, Exceptions> { + self.0.rotateCounterClockwise() + } + + fn rotateCounterClockwise45(&self) -> Result, Exceptions> { + self.0.rotateCounterClockwise45() + } +} + +impl SVGLuminanceSource { + pub fn new(svg_data: &[u8]) -> Result { + // Load the SVG file + let Ok(tree) = resvg::usvg::Tree::from_data(svg_data, &Options::default()) else { + return Err(Exceptions::FormatException(Some(format!("could not parse svg data: {}", "err")))); + }; + + let Some(mut pixmap) = resvg::tiny_skia::Pixmap::new(tree.size.width() as u32, tree.size.height() as u32) else { + return Err(Exceptions::FormatException(Some("could not create pixmap".to_owned()))); + }; + + resvg::render( + &tree, + resvg::usvg::FitTo::Original, + resvg::tiny_skia::Transform::default(), + pixmap.as_mut(), + ); + + let Some(buffer) = RgbaImage::from_raw(tree.size.width() as u32, tree.size.height() as u32, pixmap.data().to_vec()) else { + return Err(Exceptions::FormatException(Some("could not create image buffer".to_owned()))); + }; + + // let Ok(image) = image::load_from_memory_with_format(pixmap.data(), image::ImageFormat::Bmp) else { + // return Err(Exceptions::FormatException(Some("could not generate image".to_owned()))); + // }; + + Ok(Self(BufferedImageLuminanceSource::new(DynamicImage::from( + buffer, + )))) + } +}