keyfork-zbar: initial commit
This commit is contained in:
parent
9d7cbc17fa
commit
946b89349f
|
@ -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"
|
||||
|
|
|
@ -16,6 +16,8 @@ members = [
|
|||
"keyfork-shard",
|
||||
"keyfork-slip10-test-data",
|
||||
"keyfork-qrcode",
|
||||
"keyfork-zbar",
|
||||
"keyfork-zbar-sys",
|
||||
"keyforkd",
|
||||
"keyforkd-client",
|
||||
"keyforkd-models",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<Option<String>, 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<Option<String>, 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<Option<String>, QR
|
|||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[cfg(feature = "decode-backend-zbar")]
|
||||
pub fn scan_camera(timeout: Duration, index: usize) -> Result<Option<String>, 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)
|
||||
}
|
||||
|
|
|
@ -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"
|
|
@ -0,0 +1,50 @@
|
|||
use std::{env::VarError, path::Path};
|
||||
|
||||
use pkg_config::Config;
|
||||
|
||||
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
||||
|
||||
fn env_var(var: &str) -> Result<String, VarError> {
|
||||
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(())
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
#![allow(non_upper_case_globals, non_camel_case_types, non_snake_case)]
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
|
@ -0,0 +1 @@
|
|||
#include <zbar.h>
|
|
@ -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"
|
|
@ -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<dyn std::error::Error>> {
|
||||
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(())
|
||||
}
|
|
@ -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<Vec<u8>>,
|
||||
}
|
||||
|
||||
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<u8>) {
|
||||
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<DynamicImage> 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) }
|
||||
}
|
||||
}
|
|
@ -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<Symbol> {
|
||||
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) }
|
||||
}
|
||||
}
|
|
@ -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: <https://github.com/mchehab/zbar>
|
||||
|
||||
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;
|
|
@ -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<u8>,
|
||||
}
|
||||
|
||||
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<u8> {
|
||||
self.data
|
||||
}
|
||||
}
|
|
@ -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" }
|
||||
|
|
Loading…
Reference in New Issue