keyfork-qrcode: restructure to prefer libzbar and compile with both enabled
This commit is contained in:
parent
723194fdd7
commit
98b9dbb811
|
@ -1942,6 +1942,7 @@ dependencies = [
|
||||||
name = "keyfork-qrcode"
|
name = "keyfork-qrcode"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
"image",
|
"image",
|
||||||
"keyfork-bug",
|
"keyfork-bug",
|
||||||
"keyfork-zbar",
|
"keyfork-zbar",
|
||||||
|
@ -2907,9 +2908,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rqrr"
|
name = "rqrr"
|
||||||
version = "0.7.1"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ad0cd0432e6beb2f86aa4c8af1bb5edcf3c9bcb9d4836facc048664205458575"
|
checksum = "f126a9b02152815d84315316e7a759ee18a216d057095d56d19cec68a428b385"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"g2p",
|
"g2p",
|
||||||
"image",
|
"image",
|
||||||
|
|
|
@ -15,8 +15,9 @@ decode-backend-zbar = ["dep:keyfork-zbar"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
keyfork-bug = { workspace = true }
|
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"] }
|
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 }
|
thiserror = { workspace = true }
|
||||||
v4l = { workspace = true }
|
v4l = { workspace = true }
|
||||||
|
cfg-if = "1.0.0"
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::time::Duration;
|
||||||
use keyfork_qrcode::scan_camera;
|
use keyfork_qrcode::scan_camera;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
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 {
|
if let Some(scanned_text) = output {
|
||||||
println!("{scanned_text}");
|
println!("{scanned_text}");
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,17 @@
|
||||||
|
|
||||||
use keyfork_bug as bug;
|
use keyfork_bug as bug;
|
||||||
|
|
||||||
use image::ImageReader;
|
use image::{ImageBuffer, ImageReader, Luma};
|
||||||
use std::{
|
use std::{
|
||||||
io::{Cursor, Write},
|
io::{Cursor, Write},
|
||||||
time::{Duration, Instant},
|
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use v4l::{
|
use v4l::{
|
||||||
buffer::Type,
|
buffer::Type,
|
||||||
io::{userptr::Stream, traits::CaptureStream},
|
io::{traits::CaptureStream, userptr::Stream},
|
||||||
video::Capture,
|
video::Capture,
|
||||||
FourCC,
|
Device, FourCC,
|
||||||
Device,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A QR code could not be generated.
|
/// 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";
|
const VIDEO_FORMAT_READ_ERROR: &str = "Failed to read video device format";
|
||||||
|
|
||||||
|
trait Scanner {
|
||||||
|
fn scan_image(&mut self, image: ImageBuffer<Luma<u8>, Vec<u8>>) -> Option<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<Luma<u8>, Vec<u8>>,
|
||||||
|
) -> Option<String> {
|
||||||
|
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<Luma<u8>, Vec<u8>>,
|
||||||
|
) -> Option<String> {
|
||||||
|
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.
|
/// Continuously scan the `index`-th camera for a QR code.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// The function may return an error if the hardware is unable to scan video or if an image could
|
/// The function may return an error if the hardware is unable to scan video or if an image could
|
||||||
/// not be decoded.
|
/// not be decoded.
|
||||||
#[cfg(feature = "decode-backend-rqrr")]
|
|
||||||
pub fn scan_camera(timeout: Duration, index: usize) -> Result<Option<String>, QRCodeScanError> {
|
pub fn scan_camera(timeout: Duration, index: usize) -> Result<Option<String>, QRCodeScanError> {
|
||||||
let device = Device::new(index)?;
|
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");
|
fmt.fourcc = FourCC::new(b"MPG1");
|
||||||
device.set_format(&fmt)?;
|
device.set_format(&fmt)?;
|
||||||
let mut stream = Stream::with_buffers(&device, Type::VideoCapture, 4)?;
|
let mut stream = Stream::with_buffers(&device, Type::VideoCapture, 4)?;
|
||||||
let start = Instant::now();
|
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()
|
#[allow(unused)]
|
||||||
.duration_since(start)
|
let mut count = 0;
|
||||||
< timeout
|
|
||||||
{
|
while Instant::now().duration_since(start) < timeout {
|
||||||
|
count += 1;
|
||||||
let (buffer, _) = stream.next()?;
|
let (buffer, _) = stream.next()?;
|
||||||
let image = ImageReader::new(Cursor::new(buffer))
|
let image = ImageReader::new(Cursor::new(buffer))
|
||||||
.with_guessed_format()?
|
.with_guessed_format()?
|
||||||
.decode()?
|
.decode()?
|
||||||
.to_luma8();
|
.to_luma8();
|
||||||
let mut image = rqrr::PreparedImage::prepare(image);
|
if let Some(content) = scanner.scan_image(image) {
|
||||||
for grid in image.detect_grids() {
|
// dbg_elapsed(count, start);
|
||||||
if let Ok((_, content)) = grid.decode() {
|
return Ok(Some(content));
|
||||||
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<Option<String>, 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)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ impl Image {
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
mod impls {
|
mod impls {
|
||||||
use super::*;
|
use super::*;
|
||||||
use image::{DynamicImage, GenericImageView};
|
use image::{DynamicImage, GenericImageView, ImageBuffer, Luma};
|
||||||
|
|
||||||
impl From<DynamicImage> for Image {
|
impl From<DynamicImage> for Image {
|
||||||
fn from(value: DynamicImage) -> Self {
|
fn from(value: DynamicImage) -> Self {
|
||||||
|
@ -70,6 +70,17 @@ mod impls {
|
||||||
image
|
image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ImageBuffer<Luma<u8>, Vec<u8>>> for Image {
|
||||||
|
fn from(value: ImageBuffer<Luma<u8>, Vec<u8>>) -> 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 {
|
impl Drop for Image {
|
||||||
|
|
Loading…
Reference in New Issue