#![cfg(all(feature = "scan", feature = "code128", feature = "png"))]
use smart_package_tracker::render::{Color, Length, QuietZone};
use smart_package_tracker::scan::{scan_png, GrayImage, Scanner};
use smart_package_tracker::{Barcode, Error, RenderOptions, SymbologyKind, TrackingId};
fn render(payload: &str) -> Vec<u8> {
Barcode::code128(payload)
.unwrap()
.to_png(&RenderOptions::default())
.unwrap()
}
fn render_gray(payload: &str, options: &RenderOptions) -> GrayImage {
let png = Barcode::code128(payload).unwrap().to_png(options).unwrap();
GrayImage::from_png(&png).unwrap()
}
struct Lcg(u64);
impl Lcg {
fn next(&mut self) -> u32 {
self.0 = self.0.wrapping_mul(6364136223846793005).wrapping_add(1);
(self.0 >> 33) as u32
}
}
#[test]
fn a_rendered_label_reads_back() {
let png = render("PKG-9ED9285C");
let found = scan_png(&png).unwrap();
assert_eq!(found.payload(), "PKG-9ED9285C");
assert_eq!(found.kind(), SymbologyKind::Code128);
}
#[test]
fn reads_back_at_every_module_width_a_printer_would_use() {
for (mils, dpi) in [
(7.5, 203.0),
(10.0, 203.0),
(13.0, 300.0),
(13.0, 600.0),
(20.0, 600.0),
(40.0, 300.0),
] {
let options = RenderOptions::builder()
.module_width(Length::Mils(mils))
.dpi(dpi as u32)
.build()
.unwrap();
let image = render_gray("PKG-9ED9285C", &options);
let found = Scanner::new()
.scan(&image)
.unwrap_or_else(|e| panic!("{mils} mil at {dpi} dpi: {e}"));
assert_eq!(found.payload(), "PKG-9ED9285C");
}
}
#[test]
fn a_one_pixel_module_still_reads() {
let options = RenderOptions::builder()
.module_width(Length::Px(1.0))
.height(Length::Px(40.0))
.human_readable(false)
.build()
.unwrap();
let image = render_gray("PKG-9ED9285C", &options);
assert_eq!(
Scanner::new().scan(&image).unwrap().payload(),
"PKG-9ED9285C"
);
}
#[test]
fn the_human_readable_line_does_not_fool_the_scanner() {
let options = RenderOptions::builder()
.human_readable(true)
.build()
.unwrap();
let image = render_gray("PKG-9ED9285C", &options);
assert_eq!(
Scanner::new().scan(&image).unwrap().payload(),
"PKG-9ED9285C"
);
}
#[test]
fn reads_a_label_printed_on_its_side() {
let image = render_gray("PKG-9ED9285C", &RenderOptions::default());
let (w, h) = (image.width(), image.height());
let mut rotated = Vec::with_capacity((w * h) as usize);
for x in 0..w {
for y in (0..h).rev() {
rotated.push(image.pixel(x, y));
}
}
let rotated = GrayImage::from_luma(h, w, rotated).unwrap();
assert_eq!(
Scanner::new().scan(&rotated).unwrap().payload(),
"PKG-9ED9285C"
);
}
#[test]
fn reads_light_bars_on_a_dark_background() {
let options = RenderOptions::builder()
.colors(Color::WHITE, Color::BLACK)
.build()
.unwrap();
let image = render_gray("PKG-9ED9285C", &options);
assert_eq!(
Scanner::new().scan(&image).unwrap().payload(),
"PKG-9ED9285C"
);
}
#[test]
fn a_transparent_background_reads_as_paper() {
let options = RenderOptions::builder()
.colors(Color::BLACK, Color::TRANSPARENT)
.build()
.unwrap();
let png = Barcode::code128("PKG-9ED9285C")
.unwrap()
.to_png(&options)
.unwrap();
assert_eq!(scan_png(&png).unwrap().payload(), "PKG-9ED9285C");
}
#[test]
fn finds_a_barcode_placed_inside_a_larger_scene() {
let label = render_gray("PKG-9ED9285C", &RenderOptions::default());
let (lw, lh) = (label.width(), label.height());
let (pw, ph) = (lw + 400, lh + 300);
let mut page = vec![255u8; (pw * ph) as usize];
let (ox, oy) = (137u32, 211u32);
for y in 0..lh {
for x in 0..lw {
page[((y + oy) * pw + (x + ox)) as usize] = label.pixel(x, y);
}
}
for x in 0..pw {
page[(40 * pw + x) as usize] = 0;
}
let image = GrayImage::from_luma(pw, ph, page).unwrap();
assert_eq!(
Scanner::new().scan(&image).unwrap().payload(),
"PKG-9ED9285C"
);
}
#[test]
fn survives_speckle_and_blur() {
let image = render_gray("PKG-9ED9285C", &RenderOptions::default());
let (w, h) = (image.width(), image.height());
let mut rng = Lcg(0x5EED);
let mut luma: Vec<u8> = image.luma().to_vec();
for px in luma.iter_mut() {
if rng.next() % 100 < 2 {
*px = if rng.next() % 2 == 0 { 0 } else { 255 };
}
}
let blurred: Vec<u8> = (0..h)
.flat_map(|y| {
let luma = &luma;
(0..w).map(move |x| {
let mut sum = 0u32;
let mut n = 0u32;
for dy in -1i64..=1 {
for dx in -1i64..=1 {
let (nx, ny) = (x as i64 + dx, y as i64 + dy);
if (0..w as i64).contains(&nx) && (0..h as i64).contains(&ny) {
sum += u32::from(luma[(ny as u32 * w + nx as u32) as usize]);
n += 1;
}
}
}
(sum / n) as u8
})
})
.collect();
let image = GrayImage::from_luma(w, h, blurred).unwrap();
assert_eq!(
Scanner::new().scan(&image).unwrap().payload(),
"PKG-9ED9285C"
);
}
#[test]
fn reads_a_symbol_cropped_flush_to_its_bars() {
let options = RenderOptions::builder()
.quiet_zone(QuietZone::None)
.human_readable(false)
.build()
.unwrap();
let image = render_gray("PKG-9ED9285C", &options);
assert_eq!(
Scanner::new().scan(&image).unwrap().payload(),
"PKG-9ED9285C"
);
}
#[test]
fn reads_the_payload_shapes_a_carrier_actually_uses() {
for payload in [
"PKG-9ED9285C",
"PKG-0123456789ABCDEF3",
"1Z999AA10123456784",
"9400111899223197428490",
"00123456789012345675",
"A",
"Mixed 123 Case!",
] {
let png = render(payload);
let found = scan_png(&png).unwrap_or_else(|e| panic!("{payload}: {e}"));
assert_eq!(found.payload(), payload);
}
}
#[test]
fn a_generated_tracking_id_survives_the_whole_loop() {
let id = TrackingId::generate().unwrap();
let png = render(id.as_str());
let scanned = scan_png(&png).unwrap();
let recovered = TrackingId::parse(scanned.payload()).unwrap();
assert_eq!(recovered, id);
}
#[test]
fn a_file_on_disk_reads_back() {
let dir = std::env::temp_dir().join("spt-scan-test");
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("label.png");
Barcode::code128("PKG-9ED9285C")
.unwrap()
.to_png_file(&path, &RenderOptions::default())
.unwrap();
let found = smart_package_tracker::scan::scan_png_file(&path).unwrap();
assert_eq!(found.payload(), "PKG-9ED9285C");
std::fs::remove_file(&path).ok();
}
#[test]
fn an_image_with_no_barcode_reports_that_rather_than_inventing_one() {
let blank = GrayImage::from_luma(400, 200, vec![255; 400 * 200]).unwrap();
assert!(matches!(
Scanner::new().scan(&blank),
Err(Error::NoSymbolFound(_))
));
let mut rng = Lcg(1);
let noise: Vec<u8> = (0..400 * 200).map(|_| (rng.next() & 0xFF) as u8).collect();
let noise = GrayImage::from_luma(400, 200, noise).unwrap();
assert!(matches!(
Scanner::new().scan(&noise),
Err(Error::NoSymbolFound(_))
));
}
#[test]
#[cfg(feature = "qr")]
fn a_qr_code_is_a_clean_miss_not_a_wrong_answer() {
let png = Barcode::qr("PKG-9ED9285C")
.unwrap()
.to_png(&RenderOptions::default())
.unwrap();
assert!(matches!(scan_png(&png), Err(Error::NoSymbolFound(_))));
}
#[test]
fn malformed_image_bytes_are_an_error_not_a_panic() {
assert!(matches!(
scan_png(b"not a png"),
Err(Error::InvalidImage(_))
));
assert!(matches!(scan_png(&[]), Err(Error::InvalidImage(_))));
let mut truncated = render("PKG-9ED9285C");
truncated.truncate(40);
assert!(matches!(scan_png(&truncated), Err(Error::InvalidImage(_))));
}