From 946b89349f984245586f2ce7df76c64e9fe55053 Mon Sep 17 00:00:00 2001 From: ryan Date: Sat, 13 Jan 2024 02:52:43 -0500 Subject: [PATCH] keyfork-zbar: initial commit --- Cargo.lock | 20 ++++++++ Cargo.toml | 2 + keyfork-qrcode/Cargo.toml | 8 +++- keyfork-qrcode/src/lib.rs | 53 ++++++++++---------- keyfork-zbar-sys/Cargo.toml | 12 +++++ keyfork-zbar-sys/build.rs | 50 +++++++++++++++++++ keyfork-zbar-sys/src/lib.rs | 3 ++ keyfork-zbar-sys/zbar-wrapper.h | 1 + keyfork-zbar/Cargo.toml | 18 +++++++ keyfork-zbar/examples/v4l-scan.rs | 43 +++++++++++++++++ keyfork-zbar/src/image.rs | 76 +++++++++++++++++++++++++++++ keyfork-zbar/src/image_scanner.rs | 80 +++++++++++++++++++++++++++++++ keyfork-zbar/src/lib.rs | 16 +++++++ keyfork-zbar/src/symbol.rs | 31 ++++++++++++ keyfork/Cargo.toml | 5 +- 15 files changed, 391 insertions(+), 27 deletions(-) create mode 100644 keyfork-zbar-sys/Cargo.toml create mode 100644 keyfork-zbar-sys/build.rs create mode 100644 keyfork-zbar-sys/src/lib.rs create mode 100644 keyfork-zbar-sys/zbar-wrapper.h create mode 100644 keyfork-zbar/Cargo.toml create mode 100644 keyfork-zbar/examples/v4l-scan.rs create mode 100644 keyfork-zbar/src/image.rs create mode 100644 keyfork-zbar/src/image_scanner.rs create mode 100644 keyfork-zbar/src/lib.rs create mode 100644 keyfork-zbar/src/symbol.rs diff --git a/Cargo.lock b/Cargo.lock index c281cd9..a97ced3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1531,6 +1531,7 @@ dependencies = [ "keyfork-entropy", "keyfork-mnemonic-util", "keyfork-prompt", + "keyfork-qrcode", "keyfork-shard", "keyforkd", "keyforkd-client", @@ -1661,6 +1662,7 @@ name = "keyfork-qrcode" version = "0.1.0" dependencies = [ "image", + "keyfork-zbar", "rqrr", "thiserror", "v4l", @@ -1696,6 +1698,24 @@ dependencies = [ "hex", ] +[[package]] +name = "keyfork-zbar" +version = "0.1.0" +dependencies = [ + "image", + "keyfork-zbar-sys", + "thiserror", + "v4l", +] + +[[package]] +name = "keyfork-zbar-sys" +version = "0.1.0" +dependencies = [ + "bindgen 0.68.1", + "pkg-config", +] + [[package]] name = "keyforkd" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index e69d0af..7f2a8af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ members = [ "keyfork-shard", "keyfork-slip10-test-data", "keyfork-qrcode", + "keyfork-zbar", + "keyfork-zbar-sys", "keyforkd", "keyforkd-client", "keyforkd-models", diff --git a/keyfork-qrcode/Cargo.toml b/keyfork-qrcode/Cargo.toml index 6808922..50ec7cf 100644 --- a/keyfork-qrcode/Cargo.toml +++ b/keyfork-qrcode/Cargo.toml @@ -7,8 +7,14 @@ license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = [] +decode-backend-rqrr = ["dep:rqrr"] +decode-backend-zbar = ["dep:keyfork-zbar"] + [dependencies] image = { version = "0.24.7", default-features = false, features = ["jpeg"] } -rqrr = "0.6.0" +keyfork-zbar = { version = "0.1.0", path = "../keyfork-zbar", optional = true } +rqrr = { version = "0.6.0", optional = true } thiserror = "1.0.56" v4l = "0.14.0" diff --git a/keyfork-qrcode/src/lib.rs b/keyfork-qrcode/src/lib.rs index b97f694..e045d83 100644 --- a/keyfork-qrcode/src/lib.rs +++ b/keyfork-qrcode/src/lib.rs @@ -1,5 +1,4 @@ use image::io::Reader as ImageReader; -use rqrr::PreparedImage; use std::{ io::{Cursor, Write}, time::{Duration, SystemTime}, @@ -8,13 +7,9 @@ use std::{ use v4l::{ buffer::Type, io::{userptr::Stream, traits::CaptureStream}, - video::Capture, - Device, FourCC, + Device, }; -static MJPEG: &[u8; 4] = b"MJPG"; - - #[derive(thiserror::Error, Debug)] pub enum QRGenerationError { #[error("{0}")] @@ -37,9 +32,6 @@ pub enum QRCodeScanError { #[error("Could not decode image: {0}")] ImageDecode(#[from] image::ImageError), - - #[error("Could not format FourCC as string (this is a bug!): {0}")] - FourCC(#[from] std::string::FromUtf8Error), } #[derive(Default)] @@ -81,24 +73,10 @@ pub fn qrencode( Ok(result) } +#[cfg(feature = "decode-backend-rqrr")] pub fn scan_camera(timeout: Duration, index: usize) -> Result, QRCodeScanError> { let device = Device::new(index)?; - - let mut format = device.format()?; - format.width = 1280; - format.height = 720; - format.fourcc = FourCC::new(MJPEG); - let format = device.set_format(&format)?; - - if MJPEG != &format.fourcc.repr { - return Err(QRCodeScanError::CameraGaveBadFormat { - expected: String::from_utf8(MJPEG.to_vec())?, - actual: String::from_utf8(format.fourcc.repr.to_vec())?, - }) - } - let mut stream = Stream::with_buffers(&device, Type::VideoCapture, 4)?; - let start = SystemTime::now(); while SystemTime::now() @@ -111,7 +89,7 @@ pub fn scan_camera(timeout: Duration, index: usize) -> Result, QR .with_guessed_format()? .decode()? .to_luma8(); - let mut image = PreparedImage::prepare(image); + let mut image = rqrr::PreparedImage::prepare(image); for grid in image.detect_grids() { if let Ok((_, content)) = grid.decode() { return Ok(Some(content)) @@ -121,3 +99,28 @@ pub fn scan_camera(timeout: Duration, index: usize) -> Result, QR Ok(None) } + +#[cfg(feature = "decode-backend-zbar")] +pub fn scan_camera(timeout: Duration, index: usize) -> Result, QRCodeScanError> { + let device = Device::new(index)?; + let mut stream = Stream::with_buffers(&device, Type::VideoCapture, 4)?; + let start = SystemTime::now(); + let mut scanner = keyfork_zbar::image_scanner::ImageScanner::new(); + + while SystemTime::now() + .duration_since(start) + .unwrap_or(Duration::from_secs(0)) + < timeout + { + let (buffer, _) = stream.next()?; + let image = ImageReader::new(Cursor::new(buffer)) + .with_guessed_format()? + .decode()?; + let image = keyfork_zbar::image::Image::from(image); + for symbol in scanner.scan_image(&image) { + return Ok(Some(String::from_utf8_lossy(symbol.data()).to_string())); + } + } + + Ok(None) +} diff --git a/keyfork-zbar-sys/Cargo.toml b/keyfork-zbar-sys/Cargo.toml new file mode 100644 index 0000000..9ff1abf --- /dev/null +++ b/keyfork-zbar-sys/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "keyfork-zbar-sys" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[build-dependencies] +bindgen = { version = "0.68", default-features = false } +pkg-config = "0.3" diff --git a/keyfork-zbar-sys/build.rs b/keyfork-zbar-sys/build.rs new file mode 100644 index 0000000..003d22e --- /dev/null +++ b/keyfork-zbar-sys/build.rs @@ -0,0 +1,50 @@ +use std::{env::VarError, path::Path}; + +use pkg_config::Config; + +type Result> = std::result::Result; + +fn env_var(var: &str) -> Result { + println!("cargo:rerun-if-env-changed={var}"); + std::env::var(var) +} + +fn generate_bindings_file() -> Result<()> { + let zbar = Config::new().atleast_version("0.23.0").probe("zbar")?; + + if env_var("ZBAR_STATIC").is_ok() { + println!("cargo:rustc-link-lib=static=zbar"); + } + + let mut builder = bindgen::builder() + .rustified_enum("zbar_color_e") + .rustified_non_exhaustive_enum("zbar_symbol_type_e") + .rustified_enum("zbar_orientation_e") + .rustified_non_exhaustive_enum("zbar_error_e") + .rustified_non_exhaustive_enum("zbar_config_e") + .rustified_enum("zbar_modifier_e") + .rustified_enum("video_control_type_e"); + + for path in zbar.include_paths { + builder = builder.clang_arg(format!("-I{}", path.display())); + } + + let builder = builder.header("zbar-wrapper.h"); + let bindings = builder.generate()?; + + let out_path = Path::new(&env_var("OUT_DIR")?).join("bindings.rs"); + + bindings.write_to_file(out_path)?; + + Ok(()) +} + +fn main() -> Result<()> { + if let Err(e) = generate_bindings_file() { + eprintln!("Building zbar-sys failed: {e}"); + eprintln!("Ensure zbar headers, libclang, and pkg-config are installed"); + return Err(e) + } + + Ok(()) +} diff --git a/keyfork-zbar-sys/src/lib.rs b/keyfork-zbar-sys/src/lib.rs new file mode 100644 index 0000000..8c9010c --- /dev/null +++ b/keyfork-zbar-sys/src/lib.rs @@ -0,0 +1,3 @@ +#![allow(non_upper_case_globals, non_camel_case_types, non_snake_case)] + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/keyfork-zbar-sys/zbar-wrapper.h b/keyfork-zbar-sys/zbar-wrapper.h new file mode 100644 index 0000000..32174d7 --- /dev/null +++ b/keyfork-zbar-sys/zbar-wrapper.h @@ -0,0 +1 @@ +#include diff --git a/keyfork-zbar/Cargo.toml b/keyfork-zbar/Cargo.toml new file mode 100644 index 0000000..3e9f1f2 --- /dev/null +++ b/keyfork-zbar/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "keyfork-zbar" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["image"] +image = ["dep:image"] + +[dependencies] +image = { version = "0.24.7", default-features = false, optional = true } +keyfork-zbar-sys = { version = "0.1.0", path = "../keyfork-zbar-sys" } +thiserror = "1.0.56" + +[dev-dependencies] +v4l = "0.14.0" diff --git a/keyfork-zbar/examples/v4l-scan.rs b/keyfork-zbar/examples/v4l-scan.rs new file mode 100644 index 0000000..c92db3b --- /dev/null +++ b/keyfork-zbar/examples/v4l-scan.rs @@ -0,0 +1,43 @@ +use std::{ + io::Cursor, + time::{Duration, SystemTime}, +}; + +use keyfork_zbar::{image::Image, image_scanner::ImageScanner}; + +use image::io::Reader as ImageReader; +use v4l::{ + buffer::Type, + io::{traits::CaptureStream, userptr::Stream}, + Device, +}; + +fn main() -> Result<(), Box> { + let device = Device::new(0)?; + + let mut stream = Stream::with_buffers(&device, Type::VideoCapture, 4)?; + let start = SystemTime::now(); + let mut scanner = ImageScanner::new(); + + while SystemTime::now() + .duration_since(start) + .unwrap_or(Duration::from_secs(0)) + < Duration::from_secs(30) + { + let (buffer, _) = stream.next()?; + let image = Image::from( + ImageReader::new(Cursor::new(buffer)) + .with_guessed_format()? + .decode()?, + ); + + for symbol in scanner.scan_image(&image) { + println!("{}", String::from_utf8_lossy(symbol.data())); + return Ok(()); + } + } + + println!("Could not find a QR code"); + + Ok(()) +} diff --git a/keyfork-zbar/src/image.rs b/keyfork-zbar/src/image.rs new file mode 100644 index 0000000..2afaa4f --- /dev/null +++ b/keyfork-zbar/src/image.rs @@ -0,0 +1,76 @@ +use super::sys; + +pub struct Image { + pub(crate) inner: *mut sys::zbar_image_s, + /// Set to store the data of inner, as it will otherwise be freed when the data is dropped. + inner_data: Option>, +} + +impl Image { + /// Link: [`sys::zbar_image_create`] + pub(crate) fn alloc() -> Self { + Self { + inner: unsafe { sys::zbar_image_create() }, + inner_data: None, + } + } + + /// Link: [`sys::zbar_image_set_format`] + /// + /// A FourCC code can be given in the format: + /// + /// ```no_run + /// self.set_format(b"Y800") + /// ```` + pub(crate) fn set_format(&mut self, fourcc: &[u8; 4]) { + let fourcc: u64 = fourcc[0] as u64 + | ((fourcc[1] as u64) << 8) + | ((fourcc[2] as u64) << 16) + | ((fourcc[3] as u64) << 24); + unsafe { sys::zbar_image_set_format(self.inner, fourcc) } + } + + /// Link: [`sys::zbar_image_set_size`] + fn set_size(&mut self, width: u32, height: u32) { + unsafe { sys::zbar_image_set_size(self.inner, width, height) } + } + + /// Link: [`sys::zbar_image_set_data`] + /// + /// Accepts raw data in the configured format. See: [`Image::set_format`] + fn set_data(&mut self, data: Vec) { + unsafe { + sys::zbar_image_set_data( + self.inner, + data.as_ptr().cast(), + data.len() as u64, + None, + ) + } + // keep data in self to avoid use after free when data goes out of scope + let _ = self.inner_data.insert(data); + } +} + +#[cfg(feature = "image")] +mod impls { + use super::*; + use image::{DynamicImage, GenericImageView}; + + impl From for Image { + fn from(value: DynamicImage) -> Self { + let mut image = Self::alloc(); + let (width, height) = value.dimensions(); + image.set_size(width, height); + image.set_format(b"Y800"); + image.set_data(value.to_luma8().into_raw()); + image + } + } +} + +impl Drop for Image { + fn drop(&mut self) { + unsafe { sys::zbar_image_destroy(self.inner) } + } +} diff --git a/keyfork-zbar/src/image_scanner.rs b/keyfork-zbar/src/image_scanner.rs new file mode 100644 index 0000000..dc98d01 --- /dev/null +++ b/keyfork-zbar/src/image_scanner.rs @@ -0,0 +1,80 @@ +use super::{ + image::Image, + symbol::{Symbol, SymbolType}, + sys, Config, +}; + +#[derive(thiserror::Error, Debug)] +pub enum ImageScannerError { + #[error("Unable to set Image Scanner configuration")] + UnableToSetConfig, +} + +pub struct ImageScanner { + inner: *mut sys::zbar_image_scanner_t, +} + +impl ImageScanner { + /// Link: [`sys::zbar_image_scanner_create`] + pub fn new() -> Self { + Self { + inner: unsafe { sys::zbar_image_scanner_create() }, + } + } + + /// Link: [`sys::zbar_image_scanner_set_config`] + pub fn set_config( + &mut self, + symbol: SymbolType, + config: Config, + value: i32, + ) -> Result<(), ImageScannerError> { + let result = + unsafe { sys::zbar_image_scanner_set_config(self.inner, symbol, config, value) }; + + if result != 0 { + return Err(ImageScannerError::UnableToSetConfig); + } + + Ok(()) + } + + /// Link: [`sys::zbar_scan_image`] + /// + /// TODO: move `image` to newtype, offering conversions + /// to and from image::Image + /// + /// TODO: return an iterator over scanned values + pub fn scan_image( + &mut self, + image: &Image, + ) -> Vec { + unsafe { sys::zbar_scan_image(self.inner, image.inner) }; + let mut result = vec![]; + let mut symbol = unsafe { sys::zbar_image_first_symbol(image.inner) }; + while !symbol.is_null() { + let symbol_type = unsafe { sys::zbar_symbol_get_type(symbol) }; + let symbol_data = unsafe { sys::zbar_symbol_get_data(symbol) }; + let symbol_data_len = unsafe { sys::zbar_symbol_get_data_length(symbol) }; + let symbol_slice = unsafe { + std::slice::from_raw_parts(symbol_data as *const u8, symbol_data_len as usize) + }; + result.push(Symbol::new(symbol_type, symbol_slice)); + symbol = unsafe { sys::zbar_symbol_next(symbol) }; + } + + result + } +} + +impl Default for ImageScanner { + fn default() -> Self { + Self::new() + } +} + +impl Drop for ImageScanner { + fn drop(&mut self) { + unsafe { sys::zbar_image_scanner_destroy(self.inner) } + } +} diff --git a/keyfork-zbar/src/lib.rs b/keyfork-zbar/src/lib.rs new file mode 100644 index 0000000..e89b4db --- /dev/null +++ b/keyfork-zbar/src/lib.rs @@ -0,0 +1,16 @@ +//! Rustic bindings to the zbar library. Not intended to be high-level. +//! +//! This library includes a conversion from [`::image::DynamicImage`] for [`image::Image`]. +//! +//! Reference: + +pub use keyfork_zbar_sys as sys; + +pub use sys::zbar_color_e as Color; +pub use sys::zbar_config_e as Config; +pub use sys::zbar_modifier_e as Modifier; +pub use sys::zbar_orientation_e as Orientation; + +pub mod image_scanner; +pub mod image; +pub mod symbol; diff --git a/keyfork-zbar/src/symbol.rs b/keyfork-zbar/src/symbol.rs new file mode 100644 index 0000000..b94f404 --- /dev/null +++ b/keyfork-zbar/src/symbol.rs @@ -0,0 +1,31 @@ +use super::sys; + +pub use sys::zbar_symbol_type_e as SymbolType; + +// TODO: config, modifiers +#[derive(Debug)] +pub struct Symbol { + _type: SymbolType, + data: Vec, +} + +impl Symbol { + pub(crate) fn new(_type: SymbolType, data: &[u8]) -> Self { + Self { + _type, + data: data.to_vec(), + } + } + + pub fn _type(&self) -> SymbolType { + self._type + } + + pub fn data(&self) -> &[u8] { + self.data.as_slice() + } + + pub fn into_data(self) -> Vec { + self.data + } +} diff --git a/keyfork/Cargo.toml b/keyfork/Cargo.toml index d68286d..0a8d0d0 100644 --- a/keyfork/Cargo.toml +++ b/keyfork/Cargo.toml @@ -5,8 +5,10 @@ edition = "2021" license = "AGPL-3.0-only" [features] -default = ["completion"] +default = ["completion", "qrcode-decode-backend-rqrr"] completion = ["dep:clap_complete"] +qrcode-decode-backend-rqrr = ["keyfork-qrcode/decode-backend-rqrr"] +qrcode-decode-backend-zbar = ["keyfork-qrcode/decode-backend-zbar"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -28,3 +30,4 @@ openpgp-card = "0.4.1" keyfork-prompt = { version = "0.1.0", path = "../keyfork-prompt" } keyfork-entropy = { version = "0.1.0", path = "../keyfork-entropy" } clap_complete = { version = "4.4.6", optional = true } +keyfork-qrcode = { version = "0.1.0", path = "../keyfork-qrcode" }