pub mod ffi;
use core::{
ffi::{CStr, c_int, c_ulong},
fmt::{self, Display, Formatter},
marker::PhantomData,
slice,
};
use std::{error::Error, ffi::CString};
use enum_ordinalize::Ordinalize;
#[inline]
pub const fn fourcc(code: &[u8; 4]) -> u32 {
u32::from_le_bytes(*code)
}
pub const FOURCC_Y800: u32 = fourcc(b"Y800");
pub const FOURCC_GREY: u32 = fourcc(b"GREY");
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ordinalize)]
#[repr(i32)]
pub enum ZBarColor {
ZBarSpace = 0,
ZBarBar = 1,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ordinalize)]
#[repr(i32)]
pub enum ZBarSymbolType {
ZBarNone = 0,
ZBarPartial = 1,
ZBarEAN2 = 2,
ZBarEAN5 = 5,
ZBarEAN8 = 8,
ZBarUPCE = 9,
ZBarISBN10 = 10,
ZBarUPCA = 12,
ZBarEAN13 = 13,
ZBarISBN13 = 14,
ZBarComposite = 15,
ZBarI25 = 25,
ZBarDataBar = 34,
ZBarDataBarExp = 35,
ZBarCodeBar = 38,
ZBarCode39 = 39,
ZBarPDF417 = 57,
ZBarQRCode = 64,
ZBarSQCode = 80,
ZBarCode93 = 93,
ZBarCode128 = 128,
ZBarSymbol = 0x00FF,
ZBarAddOn2 = 0x0200,
ZBarAddOn5 = 0x0500,
ZBarAddOn = 0x0700,
}
impl ZBarSymbolType {
#[inline]
pub fn name(self) -> &'static str {
unsafe { CStr::from_ptr(ffi::zbar_get_symbol_name(self.ordinal())).to_str().unwrap() }
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ordinalize)]
#[repr(i32)]
pub enum ZBarOrientation {
ZBarOrientUnknown = -1,
ZBarOrientUp = 0,
ZBarOrientRight = 1,
ZBarOrientDown = 2,
ZBarOrientLeft = 3,
}
impl ZBarOrientation {
#[inline]
pub fn name(self) -> &'static str {
unsafe { CStr::from_ptr(ffi::zbar_get_orientation_name(self.ordinal())).to_str().unwrap() }
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ordinalize)]
#[repr(i32)]
pub enum ZBarErrorCode {
ZBarOK,
ZBarErrNoMem,
ZBarErrInternal,
ZBarErrUnsupported,
ZBarErrInvalid,
ZBarErrSystem,
ZBarErrLocking,
ZBarErrBusy,
ZBarErrXDisplay,
ZBarErrXProto,
ZBarErrClosed,
ZBarErrWinAPI,
ZBarErrNum,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ordinalize)]
#[repr(i32)]
pub enum ZBarConfig {
ZBarCfgEnable = 0,
ZBarCfgAddCheck = 1,
ZBarCfgEmitCheck = 2,
ZBarCfgASCII = 3,
ZBarCfgBinary = 4,
ZBarCfgNum = 5,
ZBarCfgMinLen = 0x20,
ZBarCfgMaxLen = 0x21,
ZBarCfgUncertainty = 0x40,
ZBarCfgPosition = 0x80,
ZBarCfgTestInverted = 0x81,
ZBarCfgXDensity = 0x100,
ZBarCfgYDensity = 0x101,
}
impl ZBarConfig {
#[inline]
pub fn name(self) -> &'static str {
unsafe { CStr::from_ptr(ffi::zbar_get_config_name(self.ordinal())).to_str().unwrap() }
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ordinalize)]
#[repr(i32)]
pub enum ZBarModifier {
ZBarModGS1,
ZBarModAIM,
ZBarModNum,
}
impl ZBarModifier {
#[inline]
pub fn name(self) -> &'static str {
unsafe { CStr::from_ptr(ffi::zbar_get_modifier_name(self.ordinal())).to_str().unwrap() }
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ordinalize)]
#[repr(i32)]
pub enum VideoControlType {
VideoCntlInteger = 1,
VideoCntlMenu = 2,
VideoCntlButton = 3,
VideoCntlInteger64 = 4,
VideoCntlString = 5,
VideoCntlBoolean = 6,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum ZBarError {
InsufficientData {
expected: u64,
actual: u64,
},
UnsupportedImageFormat(u32),
UnknownSymbolType(i32),
InvalidConfig,
ImageConversionFailed,
}
impl Display for ZBarError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::InsufficientData {
expected,
actual,
} => {
write!(f, "the image needs {expected} bytes of data but only {actual} were given")
},
Self::UnsupportedImageFormat(format) => {
let code = format.to_le_bytes();
write!(
f,
"ZBar cannot scan images in the {} format",
String::from_utf8_lossy(&code)
)
},
Self::UnknownSymbolType(symbol_type) => {
write!(f, "ZBar reported the unknown symbol type {symbol_type}")
},
Self::InvalidConfig => f.write_str("ZBar rejected the configuration"),
Self::ImageConversionFailed => {
f.write_str("ZBar cannot convert the image to the requested format")
},
}
}
}
impl Error for ZBarError {}
#[inline]
pub fn version() -> (u32, u32, u32) {
let mut major = 0;
let mut minor = 0;
let mut patch = 0;
unsafe {
ffi::zbar_version(&mut major, &mut minor, &mut patch);
}
(major, minor, patch)
}
#[inline]
pub unsafe fn set_verbosity(verbosity: i32) {
unsafe {
ffi::zbar_set_verbosity(verbosity);
}
}
#[inline]
pub unsafe fn increase_verbosity() {
unsafe {
ffi::zbar_increase_verbosity();
}
}
unsafe extern "C" fn keep_borrowed_data(_image: *mut ffi::zbar_image_t) {}
unsafe extern "C" fn free_owned_data(image: *mut ffi::zbar_image_t) {
unsafe {
let data = ffi::zbar_image_get_data(image).cast_mut().cast::<u8>();
let len = ffi::zbar_image_get_data_length(image) as usize;
drop(Box::from_raw(core::ptr::slice_from_raw_parts_mut(data, len)));
}
}
pub struct ZBarImage<'a> {
image: *mut ffi::zbar_image_t,
data: PhantomData<&'a [u8]>,
}
unsafe impl Send for ZBarImage<'_> {}
impl<'a> ZBarImage<'a> {
#[inline]
pub fn new_y800(data: &'a [u8], width: u32, height: u32) -> Result<Self, ZBarError> {
Self::new_grayscale(data, width, height, FOURCC_Y800)
}
#[inline]
pub fn new_grey(data: &'a [u8], width: u32, height: u32) -> Result<Self, ZBarError> {
Self::new_grayscale(data, width, height, FOURCC_GREY)
}
fn new_grayscale(
data: &'a [u8],
width: u32,
height: u32,
format: u32,
) -> Result<Self, ZBarError> {
let expected = u64::from(width) * u64::from(height);
let actual = data.len() as u64;
if actual < expected {
return Err(ZBarError::InsufficientData {
expected,
actual,
});
}
Ok(unsafe { Self::new_with_format(data, width, height, format) })
}
pub unsafe fn new_with_format(data: &'a [u8], width: u32, height: u32, format: u32) -> Self {
let image = unsafe { ffi::zbar_image_create() };
assert!(!image.is_null(), "ZBar cannot allocate an image");
unsafe {
ffi::zbar_image_set_format(image, c_ulong::from(format));
ffi::zbar_image_set_size(image, width, height);
ffi::zbar_image_set_data(
image,
data.as_ptr().cast(),
data.len() as c_ulong,
Some(keep_borrowed_data),
);
}
ZBarImage {
image,
data: PhantomData,
}
}
#[inline]
pub fn width(&self) -> u32 {
unsafe { ffi::zbar_image_get_width(self.image) }
}
#[inline]
pub fn height(&self) -> u32 {
unsafe { ffi::zbar_image_get_height(self.image) }
}
#[inline]
pub fn format(&self) -> u32 {
unsafe { ffi::zbar_image_get_format(self.image) as u32 }
}
#[inline]
pub fn data(&self) -> &[u8] {
unsafe {
let data = ffi::zbar_image_get_data(self.image);
if data.is_null() {
&[]
} else {
slice::from_raw_parts(
data.cast::<u8>(),
ffi::zbar_image_get_data_length(self.image) as usize,
)
}
}
}
#[inline]
pub fn crop(&self) -> (u32, u32, u32, u32) {
let mut x = 0;
let mut y = 0;
let mut width = 0;
let mut height = 0;
unsafe {
ffi::zbar_image_get_crop(self.image, &mut x, &mut y, &mut width, &mut height);
}
(x, y, width, height)
}
#[inline]
pub fn set_crop(&mut self, x: u32, y: u32, width: u32, height: u32) {
let image_width = self.width();
let image_height = self.height();
let x = x.min(image_width);
let y = y.min(image_height);
let width = width.min(image_width - x);
let height = height.min(image_height - y);
unsafe {
ffi::zbar_image_set_crop(self.image, x, y, width, height);
}
}
#[inline]
pub fn convert(&self, format: u32) -> Result<ZBarImage<'a>, ZBarError> {
let image = unsafe { ffi::zbar_image_convert(self.image, c_ulong::from(format)) };
Self::from_converted(image)
}
#[inline]
pub fn convert_resize(
&self,
format: u32,
width: u32,
height: u32,
) -> Result<ZBarImage<'a>, ZBarError> {
if matches!(self.format(), FOURCC_Y800 | FOURCC_GREY)
&& matches!(format, FOURCC_Y800 | FOURCC_GREY)
&& (width != self.width() || height != self.height())
{
return self.resize_grayscale(format, width, height);
}
let image = unsafe {
ffi::zbar_image_convert_resize(self.image, c_ulong::from(format), width, height)
};
Self::from_converted(image)
}
fn resize_grayscale(
&self,
format: u32,
width: u32,
height: u32,
) -> Result<ZBarImage<'a>, ZBarError> {
let len = (width as usize)
.checked_mul(height as usize)
.filter(|&len| len <= c_ulong::MAX as usize)
.ok_or(ZBarError::ImageConversionFailed)?;
let source_width = self.width() as usize;
let source_height = self.height() as usize;
if len != 0 && (source_width == 0 || source_height == 0) {
return Err(ZBarError::ImageConversionFailed);
}
let mut data = Vec::new();
data.try_reserve_exact(len).map_err(|_| ZBarError::ImageConversionFailed)?;
if len != 0 {
let width = width as usize;
let copy_width = width.min(source_width);
let copy_height = (height as usize).min(source_height);
for row in self.data().chunks_exact(source_width).take(copy_height) {
data.extend_from_slice(&row[..copy_width]);
data.resize(data.len() + width - copy_width, row[copy_width - 1]);
}
let last_row = data.len() - width..data.len();
while data.len() < len {
data.extend_from_within(last_row.clone());
}
}
let image = unsafe { ffi::zbar_image_create() };
if image.is_null() {
return Err(ZBarError::ImageConversionFailed);
}
let data = Box::into_raw(data.into_boxed_slice()).cast::<u8>();
unsafe {
ffi::zbar_image_set_format(image, c_ulong::from(format));
ffi::zbar_image_set_size(image, width, height);
ffi::zbar_image_set_data(image, data.cast(), len as c_ulong, Some(free_owned_data));
}
let mut image = ZBarImage {
image,
data: PhantomData,
};
let (x, y, width, height) = self.crop();
image.set_crop(x, y, width, height);
Ok(image)
}
#[inline]
fn from_converted(image: *mut ffi::zbar_image_t) -> Result<ZBarImage<'a>, ZBarError> {
if image.is_null() {
Err(ZBarError::ImageConversionFailed)
} else {
Ok(ZBarImage {
image,
data: PhantomData,
})
}
}
}
impl Drop for ZBarImage<'_> {
#[inline]
fn drop(&mut self) {
unsafe {
ffi::zbar_image_destroy(self.image);
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub struct ZBarImageScanResult {
pub symbol_type: ZBarSymbolType,
pub data: Vec<u8>,
pub points: Vec<(i32, i32)>,
pub quality: i32,
pub orientation: ZBarOrientation,
}
pub struct ZBarImageScanner {
scanner: *mut ffi::zbar_image_scanner_t,
}
unsafe impl Send for ZBarImageScanner {}
impl ZBarImageScanner {
#[inline]
pub fn new() -> ZBarImageScanner {
let scanner = unsafe { ffi::zbar_image_scanner_create() };
assert!(!scanner.is_null(), "ZBar cannot allocate an image scanner");
ZBarImageScanner {
scanner,
}
}
#[inline]
pub fn set_config(
&mut self,
symbology: ZBarSymbolType,
config: ZBarConfig,
value: i32,
) -> Result<(), ZBarError> {
if matches!(
symbology,
ZBarSymbolType::ZBarSymbol
| ZBarSymbolType::ZBarAddOn2
| ZBarSymbolType::ZBarAddOn5
| ZBarSymbolType::ZBarAddOn
) {
return Err(ZBarError::InvalidConfig);
}
let result = unsafe {
ffi::zbar_image_scanner_set_config(
self.scanner,
symbology.ordinal(),
config.ordinal(),
value,
)
};
if result == 0 { Ok(()) } else { Err(ZBarError::InvalidConfig) }
}
#[inline]
pub fn get_config(
&mut self,
symbology: ZBarSymbolType,
config: ZBarConfig,
) -> Result<i32, ZBarError> {
let mut value = 0;
let result = unsafe {
ffi::zbar_image_scanner_get_config(
self.scanner,
symbology.ordinal(),
config.ordinal(),
&mut value,
)
};
if result == 0 { Ok(value) } else { Err(ZBarError::InvalidConfig) }
}
pub fn parse_config(&mut self, config_string: &str) -> Result<(), ZBarError> {
let config_string = CString::new(config_string).map_err(|_| ZBarError::InvalidConfig)?;
let mut symbology = 0;
let mut config = 0;
let mut value = 0;
let result = unsafe {
ffi::zbar_parse_config(config_string.as_ptr(), &mut symbology, &mut config, &mut value)
};
if result != 0 {
return Err(ZBarError::InvalidConfig);
}
let result =
unsafe { ffi::zbar_image_scanner_set_config(self.scanner, symbology, config, value) };
if result == 0 { Ok(()) } else { Err(ZBarError::InvalidConfig) }
}
#[inline]
pub fn enable_cache(&mut self, enable: bool) {
unsafe {
ffi::zbar_image_scanner_enable_cache(self.scanner, c_int::from(enable));
}
}
#[inline]
pub fn scan_y800<D: AsRef<[u8]>>(
&mut self,
data: D,
width: u32,
height: u32,
) -> Result<Vec<ZBarImageScanResult>, ZBarError> {
let mut image = ZBarImage::new_y800(data.as_ref(), width, height)?;
self.scan_image(&mut image)
}
#[inline]
pub fn scan_grey<D: AsRef<[u8]>>(
&mut self,
data: D,
width: u32,
height: u32,
) -> Result<Vec<ZBarImageScanResult>, ZBarError> {
let mut image = ZBarImage::new_grey(data.as_ref(), width, height)?;
self.scan_image(&mut image)
}
pub fn scan_image(
&mut self,
image: &mut ZBarImage<'_>,
) -> Result<Vec<ZBarImageScanResult>, ZBarError> {
let n = unsafe { ffi::zbar_scan_image(self.scanner, image.image) };
if n < 0 {
return Err(ZBarError::UnsupportedImageFormat(image.format()));
}
let symbols = unsafe { ffi::zbar_image_get_symbols(image.image) };
let mut results = if symbols.is_null() {
Vec::new()
} else {
let size = unsafe { ffi::zbar_symbol_set_get_size(symbols) };
Vec::with_capacity(size.max(0) as usize)
};
let mut symbol = unsafe { ffi::zbar_image_first_symbol(image.image) };
while !symbol.is_null() {
let raw_symbol_type = unsafe { ffi::zbar_symbol_get_type(symbol) };
let symbol_type = ZBarSymbolType::from_ordinal(raw_symbol_type)
.ok_or(ZBarError::UnknownSymbolType(raw_symbol_type))?;
let data = unsafe {
let data = ffi::zbar_symbol_get_data(symbol);
if data.is_null() {
Vec::new()
} else {
let data_length = ffi::zbar_symbol_get_data_length(symbol) as usize;
slice::from_raw_parts(data.cast::<u8>(), data_length).to_vec()
}
};
let loc_size = unsafe { ffi::zbar_symbol_get_loc_size(symbol) };
let mut points = Vec::with_capacity(loc_size as usize);
for i in 0..loc_size {
let x = unsafe { ffi::zbar_symbol_get_loc_x(symbol, i) };
let y = unsafe { ffi::zbar_symbol_get_loc_y(symbol, i) };
points.push((x, y));
}
let quality = unsafe { ffi::zbar_symbol_get_quality(symbol) };
let orientation = unsafe { ffi::zbar_symbol_get_orientation(symbol) };
let orientation = ZBarOrientation::from_ordinal(orientation)
.unwrap_or(ZBarOrientation::ZBarOrientUnknown);
results.push(ZBarImageScanResult {
symbol_type,
data,
points,
quality,
orientation,
});
symbol = unsafe { ffi::zbar_symbol_next(symbol) };
}
Ok(results)
}
}
impl Default for ZBarImageScanner {
#[inline]
fn default() -> Self {
ZBarImageScanner::new()
}
}
impl Drop for ZBarImageScanner {
#[inline]
fn drop(&mut self) {
unsafe {
ffi::zbar_image_scanner_destroy(self.scanner);
}
}
}