#![allow(clippy::missing_safety_doc)]
#![allow(non_camel_case_types)]
pub mod config;
pub mod error;
pub mod image;
pub mod scanner;
pub mod symbol;
pub(crate) mod color;
pub(crate) mod decoder;
pub(crate) mod decoders;
#[cfg(feature = "qrcode")]
pub(crate) mod finder;
pub(crate) mod image_data;
pub(crate) mod img_scanner;
pub(crate) mod img_scanner_config;
#[cfg(feature = "qrcode")]
pub(crate) mod qrcode;
#[cfg(feature = "sqcode")]
pub(crate) mod sqcode;
#[cfg(feature = "wasm")]
pub mod wasm;
pub use config::DecoderConfig;
pub use error::{Error, Result};
pub use image::Image;
pub use scanner::{FinderRegion, ScanResult, Scanner};
pub use symbol::{Orientation, SymbolType};
#[cfg(all(test, feature = "qrcode"))]
mod proptest_qr;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_qr_decode_png() {
let img = ::image::ImageReader::open("examples/test-qr.png")
.expect("Failed to open image")
.decode()
.expect("Failed to decode image");
let gray = img.to_luma8();
let (width, height) = gray.dimensions();
let data = gray.as_raw();
let mut img = Image::from_gray(data, width, height).expect("Failed to create zedbar image");
let mut scanner = Scanner::new();
let symbols = scanner.scan(&mut img);
assert!(!symbols.is_empty(), "Expected to find at least one QR code");
let mut zedbar_results = Vec::new();
for symbol in symbols {
let symbol_type = symbol.symbol_type();
let data = symbol.data();
println!("Decoded {symbol_type:?}: {} bytes", data.len());
assert_eq!(symbol_type, SymbolType::QrCode);
assert!(!data.is_empty(), "QR code data should not be empty");
zedbar_results.push(data.to_vec());
}
let width_usize = width as usize;
let height_usize = height as usize;
let raw = gray.as_raw();
let mut prepared_img =
rqrr::PreparedImage::prepare_from_greyscale(width_usize, height_usize, |x, y| {
raw[y * width_usize + x]
});
let grids = prepared_img.detect_grids();
assert!(
!grids.is_empty(),
"rqrr: Expected to find at least one grid"
);
let mut rqrr_results = Vec::new();
for grid in grids {
let (_meta, content) = grid.decode().expect("rqrr: Failed to decode grid");
rqrr_results.push(content.into_bytes());
}
zedbar_results.sort();
rqrr_results.sort();
assert_eq!(
zedbar_results, rqrr_results,
"zedbar and rqrr produced different results"
);
println!(
"✓ zedbar and rqrr agree on {} symbols",
zedbar_results.len()
);
}
#[test]
fn test_qr_decode_jpg() {
let img = ::image::ImageReader::open("examples/test-qr.jpg")
.expect("Failed to open image")
.decode()
.expect("Failed to decode image");
let gray = img.to_luma8();
let (width, height) = gray.dimensions();
let data = gray.as_raw();
let mut img = Image::from_gray(data, width, height).expect("Failed to create zedbar image");
let mut scanner = Scanner::new();
let symbols = scanner.scan(&mut img);
assert!(!symbols.is_empty(), "Expected to find at least one QR code");
let mut zedbar_results = Vec::new();
for symbol in symbols {
let symbol_type = symbol.symbol_type();
let data = symbol.data();
println!("Decoded {symbol_type:?}: {} bytes", data.len());
assert_eq!(symbol_type, SymbolType::QrCode);
assert!(!data.is_empty(), "QR code data should not be empty");
zedbar_results.push(data.to_vec());
}
let width_usize = width as usize;
let height_usize = height as usize;
let raw = gray.as_raw();
let mut prepared_img =
rqrr::PreparedImage::prepare_from_greyscale(width_usize, height_usize, |x, y| {
raw[y * width_usize + x]
});
let grids = prepared_img.detect_grids();
assert!(
!grids.is_empty(),
"rqrr: Expected to find at least one grid"
);
let mut rqrr_results = Vec::new();
for grid in grids {
let (_meta, content) = grid.decode().expect("rqrr: Failed to decode grid");
rqrr_results.push(content.into_bytes());
}
zedbar_results.sort();
rqrr_results.sort();
assert_eq!(
zedbar_results, rqrr_results,
"zedbar and rqrr produced different results"
);
println!(
"✓ zedbar and rqrr agree on {} symbols",
zedbar_results.len()
);
}
#[test]
fn test_qr_decode_inverted() {
let img = ::image::ImageReader::open("examples/test-qr.png")
.expect("Failed to open image")
.decode()
.expect("Failed to decode image");
let gray = img.to_luma8();
let (width, height) = gray.dimensions();
let original_raw = gray.as_raw();
let mut inverted = Vec::with_capacity(original_raw.len());
inverted.extend(original_raw.iter().map(|&v| 255u8.saturating_sub(v)));
let mut img =
Image::from_gray(&inverted, width, height).expect("Failed to create zedbar image");
use crate::config::*;
let config = DecoderConfig::new().enable(QrCode).test_inverted(true);
let mut scanner = Scanner::with_config(config);
let symbols = scanner.scan(&mut img);
assert!(
!symbols.is_empty(),
"Expected to find at least one QR code in inverted image"
);
let mut zedbar_results = Vec::new();
for symbol in symbols {
let symbol_type = symbol.symbol_type();
let data = symbol.data();
println!("Decoded {symbol_type:?} (inverted): {} bytes", data.len());
assert_eq!(symbol_type, SymbolType::QrCode);
assert!(!data.is_empty(), "QR code data should not be empty");
zedbar_results.push(data.to_vec());
}
let width_usize = width as usize;
let height_usize = height as usize;
let mut prepared_img =
rqrr::PreparedImage::prepare_from_greyscale(width_usize, height_usize, |x, y| {
original_raw[y * width_usize + x]
});
let grids = prepared_img.detect_grids();
assert!(
!grids.is_empty(),
"rqrr: Expected to find at least one grid in original image"
);
let mut rqrr_results = Vec::new();
for grid in grids {
let (_meta, content) = grid.decode().expect("rqrr: Failed to decode grid");
rqrr_results.push(content.into_bytes());
}
zedbar_results.sort();
rqrr_results.sort();
assert_eq!(
zedbar_results, rqrr_results,
"zedbar and rqrr produced different results for inverted image"
);
println!(
"✓ zedbar and rqrr agree on {} symbols for inverted image",
zedbar_results.len()
);
}
#[test]
fn test_ean13_decode() {
let img = ::image::ImageReader::open("examples/test-ean13.png")
.expect("Failed to open test-ean13.png")
.decode()
.expect("Failed to decode image");
let gray = img.to_luma8();
let (width, height) = gray.dimensions();
let data = gray.as_raw();
let mut img = Image::from_gray(data, width, height).expect("Failed to create zedbar image");
let mut scanner = Scanner::new();
let symbols = scanner.scan(&mut img);
assert!(
!symbols.is_empty(),
"Expected to find at least one EAN-13 barcode"
);
for symbol in symbols {
assert_eq!(symbol.symbol_type(), SymbolType::Ean13);
println!("Decoded EAN-13: {}", symbol.data_string().unwrap_or(""));
}
}
#[test]
fn test_ean8_decode() {
let img = ::image::ImageReader::open("examples/test-ean8.png")
.expect("Failed to open test-ean8.png")
.decode()
.expect("Failed to decode image");
let gray = img.to_luma8();
let (width, height) = gray.dimensions();
let data = gray.as_raw();
let mut img = Image::from_gray(data, width, height).expect("Failed to create zedbar image");
let mut scanner = Scanner::new();
let symbols = scanner.scan(&mut img);
assert!(
!symbols.is_empty(),
"Expected to find at least one EAN-8 barcode"
);
for symbol in symbols {
let decoded_data = symbol.data_string().unwrap_or("");
println!("Decoded as {:?}: {}", symbol.symbol_type(), decoded_data);
assert!(
symbol.symbol_type() == SymbolType::Ean8
|| symbol.symbol_type() == SymbolType::Ean13,
"Expected EAN-8 or EAN-13, got {:?}",
symbol.symbol_type()
);
assert!(decoded_data.contains("96385074") || decoded_data == "96385074");
}
}
#[test]
fn test_upca_decode() {
let img = ::image::ImageReader::open("examples/test-upca.png")
.expect("Failed to open test-upca.png")
.decode()
.expect("Failed to decode image");
let gray = img.to_luma8();
let (width, height) = gray.dimensions();
let data = gray.as_raw();
let mut img = Image::from_gray(data, width, height).expect("Failed to create zedbar image");
use crate::config::*;
let config = DecoderConfig::new().enable(Upca);
let mut scanner = Scanner::with_config(config);
let symbols = scanner.scan(&mut img);
assert!(
!symbols.is_empty(),
"Expected to find at least one UPC-A barcode"
);
for symbol in symbols {
assert_eq!(symbol.symbol_type(), SymbolType::Upca);
println!("Decoded UPC-A: {}", symbol.data_string().unwrap_or(""));
}
}
#[test]
fn test_code128_decode() {
let img = ::image::ImageReader::open("examples/test-code128.png")
.expect("Failed to open test-code128.png")
.decode()
.expect("Failed to decode image");
let gray = img.to_luma8();
let (width, height) = gray.dimensions();
let data = gray.as_raw();
let mut img = Image::from_gray(data, width, height).expect("Failed to create zedbar image");
let mut scanner = Scanner::new();
let symbols = scanner.scan(&mut img);
assert!(
!symbols.is_empty(),
"Expected to find at least one Code128 barcode"
);
for symbol in symbols {
assert_eq!(symbol.symbol_type(), SymbolType::Code128);
println!("Decoded Code128: {}", symbol.data_string().unwrap_or(""));
}
}
#[test]
fn test_code39_decode() {
let img = ::image::ImageReader::open("examples/test-code39.png")
.expect("Failed to open test-code39.png")
.decode()
.expect("Failed to decode image");
let gray = img.to_luma8();
let (width, height) = gray.dimensions();
let data = gray.as_raw();
let mut img = Image::from_gray(data, width, height).expect("Failed to create zedbar image");
let mut scanner = Scanner::new();
let symbols = scanner.scan(&mut img);
assert!(
!symbols.is_empty(),
"Expected to find at least one Code39 barcode"
);
for symbol in symbols {
assert_eq!(symbol.symbol_type(), SymbolType::Code39);
println!("Decoded Code39: {}", symbol.data_string().unwrap_or(""));
}
}
#[test]
fn test_code93_decode() {
let img = ::image::ImageReader::open("examples/test-code93.png")
.expect("Failed to open test-code93.png")
.decode()
.expect("Failed to decode image");
let gray = img.to_luma8();
let (width, height) = gray.dimensions();
let data = gray.as_raw();
let mut img = Image::from_gray(data, width, height).expect("Failed to create zedbar image");
let mut scanner = Scanner::new();
let symbols = scanner.scan(&mut img);
assert!(
!symbols.is_empty(),
"Expected to find at least one Code93 barcode"
);
for symbol in symbols {
assert_eq!(symbol.symbol_type(), SymbolType::Code93);
println!("Decoded Code93: {}", symbol.data_string().unwrap_or(""));
}
}
#[test]
fn test_codabar_decode() {
let img = ::image::ImageReader::open("examples/test-codabar.png")
.expect("Failed to open test-codabar.png")
.decode()
.expect("Failed to decode image");
let gray = img.to_luma8();
let (width, height) = gray.dimensions();
let data = gray.as_raw();
let mut img = Image::from_gray(data, width, height).expect("Failed to create zedbar image");
let mut scanner = Scanner::new();
let symbols = scanner.scan(&mut img);
assert!(
!symbols.is_empty(),
"Expected to find at least one Codabar barcode"
);
for symbol in symbols {
assert_eq!(symbol.symbol_type(), SymbolType::Codabar);
println!("Decoded Codabar: {}", symbol.data_string().unwrap_or(""));
}
}
#[test]
fn test_interleaved2of5_decode() {
let img = ::image::ImageReader::open("examples/test-i25.png")
.expect("Failed to open test-i25.png")
.decode()
.expect("Failed to decode image");
let gray = img.to_luma8();
let (width, height) = gray.dimensions();
let data = gray.as_raw();
let mut img = Image::from_gray(data, width, height).expect("Failed to create zedbar image");
let mut scanner = Scanner::new();
let symbols = scanner.scan(&mut img);
assert!(
!symbols.is_empty(),
"Expected to find at least one I25 barcode"
);
for symbol in symbols {
assert_eq!(symbol.symbol_type(), SymbolType::I25);
println!("Decoded I25: {}", symbol.data_string().unwrap_or(""));
}
}
#[test]
fn test_pixel_wifi_qr_decode() {
let img = ::image::ImageReader::open("examples/pixel-wifi-sharing-qr-code.png")
.expect("Failed to open pixel-wifi-sharing-qr-code.png")
.decode()
.expect("Failed to decode image");
let gray = img.to_luma8();
let (width, height) = gray.dimensions();
let data = gray.as_raw();
let mut img = Image::from_gray(data, width, height).expect("Failed to create zedbar image");
let mut scanner = Scanner::new();
let symbols = scanner.scan(&mut img);
assert!(
!symbols.is_empty(),
"Expected to find at least one QR code in pixel-wifi-sharing-qr-code.png"
);
let symbol = symbols.into_iter().next().unwrap();
assert_eq!(symbol.symbol_type(), SymbolType::QrCode);
let data = symbol.data_string().unwrap_or("");
assert_eq!(data, "WIFI:S:Not a real network;T:SAE;P:password;H:false;;");
}
#[test]
fn test_qr_code_capstone_interference() {
let img = ::image::ImageReader::open("examples/qr-code-capstone-interference.png")
.expect("Failed to open image")
.decode()
.expect("Failed to decode image");
let gray = img.to_luma8();
let (width, height) = gray.dimensions();
let data = gray.as_raw();
let mut img = Image::from_gray(data, width, height).expect("Failed to create zedbar image");
let mut scanner = Scanner::new();
let symbols = scanner.scan(&mut img);
assert!(!symbols.is_empty(), "Expected to find at least one QR code");
let symbol = symbols.into_iter().next().unwrap();
assert_eq!(symbol.symbol_type(), SymbolType::QrCode);
let data = symbol.data_string().unwrap_or("");
assert_eq!(
data,
"http://txz.qq.com/p?k=T8sZMvS*JxhU0kQFseMOMQZAKuE7An3u&f=716027609"
);
}
#[test]
#[ignore = "QR decoder cannot decode low-contrast color QR codes - finder patterns detected but data extraction fails"]
fn test_qr_code_color_bands() {
let img = ::image::ImageReader::open("examples/qr-code-color-bands.png")
.expect("Failed to open image")
.decode()
.expect("Failed to decode image");
let gray = img.to_luma8();
let (width, height) = gray.dimensions();
let data = gray.as_raw();
let min_val = *data.iter().min().unwrap_or(&0);
let max_val = *data.iter().max().unwrap_or(&255);
let normalized = if max_val > min_val {
data.iter()
.map(|&pixel| {
let stretched =
((pixel as u16 - min_val as u16) * 255) / (max_val as u16 - min_val as u16);
stretched as u8
})
.collect::<Vec<u8>>()
} else {
data.to_vec()
};
let mut img =
Image::from_gray(&normalized, width, height).expect("Failed to create zedbar image");
let mut scanner = Scanner::new();
let symbols = scanner.scan(&mut img);
assert!(!symbols.is_empty(), "Expected to find at least one QR code");
let symbol = symbols.into_iter().next().unwrap();
assert_eq!(symbol.symbol_type(), SymbolType::QrCode);
let data = symbol.data_string().unwrap_or("");
assert_eq!(
data,
"二维码生成器
https://zh.qr-code-generator.com
打印出来的二维码至少要2厘米宽,确保用任何设备或应用都可以成功扫描。 ... 通过三个简单步骤,就能使用二维码生成器在几秒钟内创建一个二维码。首先,选择二维码的 ..."
);
}
#[test]
fn test_finder_region_manual_workflow() {
let img = ::image::ImageReader::open("examples/qr-in-large-page.jpg")
.expect("Failed to open qr-in-large-page.jpg")
.decode()
.expect("Failed to decode image");
let gray = img.to_luma8();
let (width, height) = gray.dimensions();
let mut zedbar_img =
Image::from_gray(gray.as_raw(), width, height).expect("Failed to create zedbar image");
let mut scanner = Scanner::new();
let result = scanner.scan(&mut zedbar_img);
assert!(result.symbols().is_empty());
assert!(
result.finder_region().is_some(),
"Expected finder region for the small QR code"
);
let mut recovered = Vec::new();
if let Some(region) = result.finder_region() {
let pad = region.width.max(region.height) / 2;
let x = region.x.saturating_sub(pad);
let y = region.y.saturating_sub(pad);
let w = (region.width + 2 * pad).min(width - x);
let h = (region.height + 2 * pad).min(height - y);
if let Some(mut upscaled) = zedbar_img.crop(x, y, w, h).and_then(|c| c.upscale(4)) {
let retry = scanner.scan(&mut upscaled);
recovered.extend(retry);
}
}
assert!(!recovered.is_empty());
assert_eq!(
recovered[0].data_string().unwrap_or(""),
"S;1;019be05c-54ba-7b32-a6f5-c06b288a46e8;A"
);
}
#[test]
fn test_retry_undecoded_regions() {
use crate::config::*;
let images = [
"examples/github-issue-qr.png",
"examples/qr-in-large-page.jpg",
"examples/qr-failing-2.jpg",
];
for path in images {
let img = ::image::ImageReader::open(path)
.unwrap_or_else(|_| panic!("Failed to open {path}"))
.decode()
.unwrap_or_else(|_| panic!("Failed to decode {path}"));
let gray = img.to_luma8();
let (width, height) = gray.dimensions();
let mut zedbar_img = Image::from_gray(gray.as_raw(), width, height)
.expect("Failed to create zedbar image");
let config = DecoderConfig::new().retry_undecoded_regions(true);
let mut scanner = Scanner::with_config(config);
let result = scanner.scan(&mut zedbar_img);
assert!(
!result.symbols().is_empty(),
"{path}: expected retry to decode the QR code"
);
assert_eq!(
result.symbols()[0].data_string().unwrap_or(""),
"S;1;019be05c-54ba-7b32-a6f5-c06b288a46e8;A",
"{path}: wrong decoded data"
);
}
}
}