use bgone::{ProcessOptions, process_image};
use image::{DynamicImage, Rgba, RgbaImage};
use tempfile::TempDir;
fn process_palette(
palette: [[u8; 4]; 4],
background: [u8; 3],
fg_threshold: Option<f64>,
bg_threshold: Option<f64>,
strict: bool,
foreground_colors: Vec<[u8; 3]>,
) -> [Rgba<u8>; 4] {
let temp_dir = TempDir::new().unwrap();
let mut img = RgbaImage::new(4, 1);
for (x, px) in palette.iter().enumerate() {
img.put_pixel(x as u32, 0, Rgba(*px));
}
let input_path = temp_dir.path().join("in.png");
let output_path = temp_dir.path().join("out.png");
DynamicImage::ImageRgba8(img).save(&input_path).unwrap();
process_image(
&input_path,
&output_path,
foreground_colors,
background,
ProcessOptions {
strict_mode: strict,
fg_threshold,
bg_threshold,
..Default::default()
},
)
.unwrap();
let out = image::open(&output_path).unwrap().to_rgba8();
[
*out.get_pixel(0, 0),
*out.get_pixel(1, 0),
*out.get_pixel(2, 0),
*out.get_pixel(3, 0),
]
}
#[test]
fn test_bg_threshold_default_zero_does_not_snap_near_background() {
let bg = [0, 0, 0];
let palette = [
[0, 0, 0, 255], [1, 0, 0, 255], [3, 3, 3, 255], [255, 0, 0, 255], ];
let out = process_palette(palette, bg, None, None, false, vec![]);
assert_eq!(out[0][3], 0, "exact bg should be fully transparent");
assert!(
out[1][3] > 0,
"near-bg pixel #1 should retain non-zero alpha without --bg-threshold, got alpha={}",
out[1][3]
);
assert!(
out[2][3] > 0,
"near-bg pixel #3 should retain non-zero alpha without --bg-threshold, got alpha={}",
out[2][3]
);
assert!(out[3][3] > 0, "true fg should stay opaque-ish");
}
#[test]
fn test_bg_threshold_snaps_near_black_pixels_to_transparent() {
let bg = [0, 0, 0];
let palette = [
[0, 0, 0, 255], [1, 0, 0, 255], [3, 3, 3, 255], [10, 0, 0, 255], ];
let out = process_palette(palette, bg, None, Some(0.016), false, vec![]);
assert_eq!(out[0][3], 0, "exact bg snaps");
assert_eq!(out[1][3], 0, "#010000 snaps with bg_threshold≈4/255");
assert_eq!(out[2][3], 0, "#030303 snaps with bg_threshold≈4/255");
assert!(
out[3][3] > 0,
"10/255 off must NOT snap, got alpha={}",
out[3][3]
);
}
#[test]
fn test_bg_threshold_works_off_non_black_background() {
let bg = [200, 100, 50];
let palette = [
[200, 100, 50, 255], [201, 99, 51, 255], [200, 100, 53, 255], [220, 100, 50, 255], ];
let out = process_palette(palette, bg, None, Some(0.016), false, vec![]);
assert_eq!(out[0][3], 0);
assert_eq!(out[1][3], 0, "near-bg snaps");
assert_eq!(out[2][3], 0, "near-bg snaps");
assert!(
out[3][3] > 0,
"far from bg must NOT snap, got alpha={}",
out[3][3]
);
}
#[test]
fn test_bg_threshold_l_infinity_metric_not_euclidean() {
let bg = [0, 0, 0];
let palette = [
[0, 0, 0, 255], [5, 5, 5, 255], [0, 0, 0, 255], [10, 0, 0, 255], ];
let out = process_palette(palette, bg, None, Some(0.024), false, vec![]);
assert_eq!(
out[1][3], 0,
"L∞=5 should snap at threshold ≈6/255 (L∞ metric)"
);
assert!(out[3][3] > 0, "L∞=10 must not snap at threshold ≈6/255");
}
#[test]
fn test_bg_threshold_applies_in_strict_mode() {
let bg = [0, 0, 0];
let palette = [
[0, 0, 0, 255], [2, 0, 0, 255], [255, 0, 0, 255], [128, 0, 0, 255], ];
let out = process_palette(
palette,
bg,
None,
Some(0.016),
true,
vec![[255, 0, 0]], );
assert_eq!(out[0][3], 0);
assert_eq!(out[1][3], 0, "near-bg snaps even in strict mode");
assert!(
out[2][3] > 200,
"pure red should be ~opaque, got alpha={}",
out[2][3]
);
assert!(out[3][3] > 0, "half-red should have non-zero alpha");
}
#[test]
fn test_bg_threshold_snaps_translucent_input_pixels() {
let temp_dir = TempDir::new().unwrap();
let mut img = RgbaImage::new(2, 1);
img.put_pixel(0, 0, Rgba([128, 128, 128, 5]));
img.put_pixel(1, 0, Rgba([200, 200, 200, 255]));
let input_path = temp_dir.path().join("in.png");
let output_path = temp_dir.path().join("out.png");
DynamicImage::ImageRgba8(img).save(&input_path).unwrap();
process_image(
&input_path,
&output_path,
vec![],
[0, 0, 0],
ProcessOptions {
bg_threshold: Some(0.016), ..Default::default()
},
)
.unwrap();
let out = image::open(&output_path).unwrap().to_rgba8();
assert_eq!(
out.get_pixel(0, 0).0,
[0, 0, 0, 0],
"translucent input compositing to near-bg should snap to fully transparent"
);
assert!(
out.get_pixel(1, 0).0[3] > 0,
"opaque mid-gray must not snap, got alpha={}",
out.get_pixel(1, 0).0[3]
);
}
#[test]
fn test_bg_threshold_combines_with_trim() {
let temp_dir = TempDir::new().unwrap();
let mut img = RgbaImage::new(5, 1);
img.put_pixel(0, 0, Rgba([1, 0, 0, 255])); img.put_pixel(1, 0, Rgba([2, 2, 2, 255])); img.put_pixel(2, 0, Rgba([255, 0, 0, 255])); img.put_pixel(3, 0, Rgba([0, 1, 0, 255])); img.put_pixel(4, 0, Rgba([3, 0, 3, 255]));
let input_path = temp_dir.path().join("in.png");
let output_path = temp_dir.path().join("out.png");
DynamicImage::ImageRgba8(img).save(&input_path).unwrap();
process_image(
&input_path,
&output_path,
vec![],
[0, 0, 0],
ProcessOptions {
bg_threshold: Some(0.016),
trim: true,
..Default::default()
},
)
.unwrap();
let out = image::open(&output_path).unwrap().to_rgba8();
assert_eq!(
out.dimensions(),
(1, 1),
"after snap+trim, only the single fg pixel should remain"
);
let kept = out.get_pixel(0, 0);
assert!(
kept[3] > 0,
"the surviving pixel must be non-transparent, got alpha={}",
kept[3]
);
}