From 98b9dbb8116c96919d6da09691043333b066a832 Mon Sep 17 00:00:00 2001 From: ryan Date: Sat, 22 Feb 2025 02:48:13 -0500 Subject: [PATCH] keyfork-qrcode: restructure to prefer libzbar and compile with both enabled --- Cargo.lock | 5 +- crates/qrcode/keyfork-qrcode/Cargo.toml | 5 +- .../src/bin/keyfork-qrcode-scan.rs | 2 +- crates/qrcode/keyfork-qrcode/src/lib.rs | 142 ++++++++++++------ crates/qrcode/keyfork-zbar/src/image.rs | 13 +- 5 files changed, 113 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 52b2b02..a17b4dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1942,6 +1942,7 @@ dependencies = [ name = "keyfork-qrcode" version = "0.1.2" dependencies = [ + "cfg-if", "image", "keyfork-bug", "keyfork-zbar", @@ -2907,9 +2908,9 @@ dependencies = [ [[package]] name = "rqrr" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0cd0432e6beb2f86aa4c8af1bb5edcf3c9bcb9d4836facc048664205458575" +checksum = "f126a9b02152815d84315316e7a759ee18a216d057095d56d19cec68a428b385" dependencies = [ "g2p", "image", diff --git a/crates/qrcode/keyfork-qrcode/Cargo.toml b/crates/qrcode/keyfork-qrcode/Cargo.toml index 054b40b..71d3359 100644 --- a/crates/qrcode/keyfork-qrcode/Cargo.toml +++ b/crates/qrcode/keyfork-qrcode/Cargo.toml @@ -15,8 +15,9 @@ decode-backend-zbar = ["dep:keyfork-zbar"] [dependencies] keyfork-bug = { workspace = true } -keyfork-zbar = { workspace = true, optional = true } +keyfork-zbar = { workspace = true, optional = true, features = ["image"] } image = { workspace = true, default-features = false, features = ["jpeg"] } -rqrr = { version = "0.7.0", optional = true } +rqrr = { version = "0.9.0", optional = true } thiserror = { workspace = true } v4l = { workspace = true } +cfg-if = "1.0.0" diff --git a/crates/qrcode/keyfork-qrcode/src/bin/keyfork-qrcode-scan.rs b/crates/qrcode/keyfork-qrcode/src/bin/keyfork-qrcode-scan.rs index 69e845c..b76d7a9 100644 --- a/crates/qrcode/keyfork-qrcode/src/bin/keyfork-qrcode-scan.rs +++ b/crates/qrcode/keyfork-qrcode/src/bin/keyfork-qrcode-scan.rs @@ -5,7 +5,7 @@ use std::time::Duration; use keyfork_qrcode::scan_camera; fn main() -> Result<(), Box> { - let output = scan_camera(Duration::from_secs(60 * 10), 0)?; + let output = scan_camera(Duration::from_secs(15), 0)?; if let Some(scanned_text) = output { println!("{scanned_text}"); } diff --git a/crates/qrcode/keyfork-qrcode/src/lib.rs b/crates/qrcode/keyfork-qrcode/src/lib.rs index 380d432..08aa972 100644 --- a/crates/qrcode/keyfork-qrcode/src/lib.rs +++ b/crates/qrcode/keyfork-qrcode/src/lib.rs @@ -2,18 +2,17 @@ use keyfork_bug as bug; -use image::ImageReader; +use image::{ImageBuffer, ImageReader, Luma}; use std::{ io::{Cursor, Write}, - time::{Duration, Instant}, process::{Command, Stdio}, + time::{Duration, Instant}, }; use v4l::{ buffer::Type, - io::{userptr::Stream, traits::CaptureStream}, + io::{traits::CaptureStream, userptr::Stream}, video::Capture, - FourCC, - Device, + Device, FourCC, }; /// A QR code could not be generated. @@ -102,70 +101,117 @@ pub fn qrencode( const VIDEO_FORMAT_READ_ERROR: &str = "Failed to read video device format"; +trait Scanner { + fn scan_image(&mut self, image: ImageBuffer, Vec>) -> Option; +} + +#[cfg(feature = "decode-backend-zbar")] +mod zbar { + use super::{ImageBuffer, Luma, Scanner}; + + pub struct Zbar { + scanner: keyfork_zbar::image_scanner::ImageScanner, + } + + impl Zbar { + #[allow(dead_code)] + pub fn new() -> Self { + Self::default() + } + } + + impl Default for Zbar { + fn default() -> Self { + Self { + scanner: keyfork_zbar::image_scanner::ImageScanner::new(), + } + } + } + + impl Scanner for Zbar { + fn scan_image( + &mut self, + image: ImageBuffer, Vec>, + ) -> Option { + let image = keyfork_zbar::image::Image::from(image); + self.scanner.scan_image(&image).into_iter().next().map(|symbol| { + String::from_utf8_lossy(symbol.data()).into() + }) + } + } +} + +#[cfg(feature = "decode-backend-rqrr")] +mod rqrr { + use super::{ImageBuffer, Luma, Scanner}; + + pub struct Rqrr; + + impl Scanner for Rqrr { + fn scan_image( + &mut self, + image: ImageBuffer, Vec>, + ) -> Option { + let mut image = rqrr::PreparedImage::prepare(image); + for grid in image.detect_grids() { + if let Ok((_, content)) = grid.decode() { + return Some(content); + } + } + None + } + } +} + +#[allow(dead_code)] +fn dbg_elapsed(count: u64, instant: Instant) { + let elapsed = instant.elapsed().as_secs(); + let framerate = count as f64 / elapsed as f64; + eprintln!("framerate: {count}/{elapsed} = {framerate}"); +} + /// Continuously scan the `index`-th camera for a QR code. /// /// # Errors /// /// The function may return an error if the hardware is unable to scan video or if an image could /// not be decoded. -#[cfg(feature = "decode-backend-rqrr")] pub fn scan_camera(timeout: Duration, index: usize) -> Result, QRCodeScanError> { let device = Device::new(index)?; - let mut fmt = device.format().unwrap_or_else(bug::panic!(VIDEO_FORMAT_READ_ERROR)); + let mut fmt = device + .format() + .unwrap_or_else(bug::panic!(VIDEO_FORMAT_READ_ERROR)); fmt.fourcc = FourCC::new(b"MPG1"); device.set_format(&fmt)?; let mut stream = Stream::with_buffers(&device, Type::VideoCapture, 4)?; let start = Instant::now(); + cfg_if::cfg_if! { + if #[cfg(feature = "decode-backend-zbar")] { + let mut scanner = zbar::Zbar::default(); + } else if #[cfg(feature = "decode-backend-rqrr")] { + let mut scanner = rqrr::Rqrr; + } else { + unimplemented!("neither decode-backend-zbar nor decode-backend-rqrr were selected") + } + }; - while Instant::now() - .duration_since(start) - < timeout - { + #[allow(unused)] + let mut count = 0; + + while Instant::now().duration_since(start) < timeout { + count += 1; let (buffer, _) = stream.next()?; let image = ImageReader::new(Cursor::new(buffer)) .with_guessed_format()? .decode()? .to_luma8(); - let mut image = rqrr::PreparedImage::prepare(image); - for grid in image.detect_grids() { - if let Ok((_, content)) = grid.decode() { - return Ok(Some(content)) - } + if let Some(content) = scanner.scan_image(image) { + // dbg_elapsed(count, start); + return Ok(Some(content)); } } - Ok(None) -} - -/// Continuously scan the `index`-th camera for a QR code. -/// -/// # Errors -/// -/// The function may return an error if the hardware is unable to scan video or if an image could -/// not be decoded. -#[cfg(feature = "decode-backend-zbar")] -pub fn scan_camera(timeout: Duration, index: usize) -> Result, QRCodeScanError> { - let device = Device::new(index)?; - let mut fmt = device.format().unwrap_or_else(bug::panic!(VIDEO_FORMAT_READ_ERROR)); - fmt.fourcc = FourCC::new(b"MPG1"); - device.set_format(&fmt)?; - let mut stream = Stream::with_buffers(&device, Type::VideoCapture, 4)?; - let start = Instant::now(); - let mut scanner = keyfork_zbar::image_scanner::ImageScanner::new(); - - while Instant::now() - .duration_since(start) - < 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())); - } - } + // dbg_elapsed(count, start); Ok(None) } diff --git a/crates/qrcode/keyfork-zbar/src/image.rs b/crates/qrcode/keyfork-zbar/src/image.rs index f49f580..f3cc55d 100644 --- a/crates/qrcode/keyfork-zbar/src/image.rs +++ b/crates/qrcode/keyfork-zbar/src/image.rs @@ -58,7 +58,7 @@ impl Image { #[cfg(feature = "image")] mod impls { use super::*; - use image::{DynamicImage, GenericImageView}; + use image::{DynamicImage, GenericImageView, ImageBuffer, Luma}; impl From for Image { fn from(value: DynamicImage) -> Self { @@ -70,6 +70,17 @@ mod impls { image } } + + impl From, Vec>> for Image { + fn from(value: ImageBuffer, Vec>) -> 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.into_raw()); + image + } + } } impl Drop for Image {