#[cfg(feature = "image")]
mod image_pipeline_tests {
use dotmax::image::{
apply_dithering, auto_threshold, load_from_path, pixels_to_braille, resize_to_dimensions,
to_grayscale, DitheringMethod,
};
use std::path::Path;
#[test]
fn test_full_pipeline_with_threshold() {
let img_path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(img_path).expect("Failed to load test image");
let resized = resize_to_dimensions(&img, 160, 96, true).expect("Failed to resize");
let binary = auto_threshold(&resized);
let expected_width = ((binary.width + 1) / 2) as usize;
let expected_height = ((binary.height + 3) / 4) as usize;
let grid = pixels_to_braille(&binary, expected_width, expected_height)
.expect("Failed to map to braille");
assert_eq!(grid.width(), expected_width, "Grid width mismatch");
assert_eq!(grid.height(), expected_height, "Grid height mismatch");
let unicode_grid = grid.to_unicode_grid();
assert_eq!(unicode_grid.len(), expected_height, "Unicode grid height");
assert_eq!(unicode_grid[0].len(), expected_width, "Unicode grid width");
}
#[test]
fn test_full_pipeline_with_floyd_steinberg_dithering() {
let img_path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(img_path).expect("Failed to load test image");
let resized = resize_to_dimensions(&img, 40, 24, true).expect("Failed to resize");
let gray = to_grayscale(&resized);
let binary = apply_dithering(&gray, DitheringMethod::FloydSteinberg)
.expect("Failed to apply Floyd-Steinberg dithering");
let expected_width = ((binary.width + 1) / 2) as usize;
let expected_height = ((binary.height + 3) / 4) as usize;
let grid = pixels_to_braille(&binary, expected_width, expected_height)
.expect("Failed to map to braille");
assert_eq!(grid.width(), expected_width);
assert_eq!(grid.height(), expected_height);
}
#[test]
fn test_full_pipeline_with_bayer_dithering() {
let img_path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(img_path).expect("Failed to load test image");
let resized = resize_to_dimensions(&img, 40, 24, true).expect("Failed to resize");
let gray = to_grayscale(&resized);
let binary = apply_dithering(&gray, DitheringMethod::Bayer)
.expect("Failed to apply Bayer dithering");
let expected_width = ((binary.width + 1) / 2) as usize;
let expected_height = ((binary.height + 3) / 4) as usize;
let grid = pixels_to_braille(&binary, expected_width, expected_height)
.expect("Failed to map to braille");
assert_eq!(grid.width(), expected_width);
assert_eq!(grid.height(), expected_height);
}
#[test]
fn test_full_pipeline_with_atkinson_dithering() {
let img_path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(img_path).expect("Failed to load test image");
let resized = resize_to_dimensions(&img, 40, 24, true).expect("Failed to resize");
let gray = to_grayscale(&resized);
let binary = apply_dithering(&gray, DitheringMethod::Atkinson)
.expect("Failed to apply Atkinson dithering");
let expected_width = ((binary.width + 1) / 2) as usize;
let expected_height = ((binary.height + 3) / 4) as usize;
let grid = pixels_to_braille(&binary, expected_width, expected_height)
.expect("Failed to map to braille");
assert_eq!(grid.width(), expected_width);
assert_eq!(grid.height(), expected_height);
}
#[test]
fn test_pipeline_with_non_divisible_dimensions() {
let img_path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(img_path).expect("Failed to load test image");
let resized = resize_to_dimensions(&img, 37, 21, false).expect("Failed to resize");
let binary = auto_threshold(&resized);
let grid = pixels_to_braille(&binary, 19, 6).expect("Failed to map to braille");
assert_eq!(grid.width(), 19);
assert_eq!(grid.height(), 6);
}
#[test]
fn test_pipeline_preserves_details() {
let img_path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(img_path).expect("Failed to load test image");
let resized = resize_to_dimensions(&img, 160, 96, true).expect("Failed to resize");
let gray = to_grayscale(&resized);
let binary_threshold = auto_threshold(&resized);
let binary_dithered = apply_dithering(&gray, DitheringMethod::FloydSteinberg).unwrap();
let expected_width = ((binary_threshold.width + 1) / 2) as usize;
let expected_height = ((binary_threshold.height + 3) / 4) as usize;
let grid_threshold =
pixels_to_braille(&binary_threshold, expected_width, expected_height).unwrap();
let grid_dithered =
pixels_to_braille(&binary_dithered, expected_width, expected_height).unwrap();
assert_eq!(grid_threshold.width(), grid_dithered.width());
assert_eq!(grid_threshold.height(), grid_dithered.height());
let unicode_threshold = grid_threshold.to_unicode_grid();
let unicode_dithered = grid_dithered.to_unicode_grid();
assert_eq!(unicode_threshold.len(), expected_height);
assert_eq!(unicode_dithered.len(), expected_height);
}
#[test]
fn test_extreme_wide_aspect_ratio_renders_successfully() {
let img_path = Path::new("tests/fixtures/images/viper_ultra_wide.png");
let img = load_from_path(img_path).expect("Failed to load extreme wide image");
assert_eq!(img.width(), 10000);
assert_eq!(img.height(), 4000);
let resized =
resize_to_dimensions(&img, 160, 96, true).expect("Failed to resize extreme wide image");
assert!(resized.width() > 0 && resized.width() <= 160);
assert!(resized.height() > 0 && resized.height() <= 96);
let binary = auto_threshold(&resized);
let grid_width = ((binary.width + 1) / 2) as usize;
let grid_height = ((binary.height + 3) / 4) as usize;
let grid = pixels_to_braille(&binary, grid_width, grid_height)
.expect("Failed to map extreme wide image to braille");
assert!(grid.width() > 0);
assert!(grid.height() > 0);
}
#[test]
fn test_extreme_tall_aspect_ratio_renders_successfully() {
let img_path = Path::new("tests/fixtures/images/viper_ultra_tall.png");
let img = load_from_path(img_path).expect("Failed to load extreme tall image");
assert_eq!(img.width(), 4000);
assert_eq!(img.height(), 10000);
let resized =
resize_to_dimensions(&img, 160, 96, true).expect("Failed to resize extreme tall image");
assert!(resized.width() > 0 && resized.width() <= 160);
assert!(resized.height() > 0 && resized.height() <= 96);
let binary = auto_threshold(&resized);
let grid_width = ((binary.width + 1) / 2) as usize;
let grid_height = ((binary.height + 3) / 4) as usize;
let grid = pixels_to_braille(&binary, grid_width, grid_height)
.expect("Failed to map extreme tall image to braille");
assert!(grid.width() > 0);
assert!(grid.height() > 0);
}
#[test]
fn test_truly_extreme_aspect_ratio_100_to_1() {
let img_path = Path::new("tests/fixtures/images/extreme_wide_10000x100.png");
let img = load_from_path(img_path).expect("Failed to load truly extreme wide image");
assert_eq!(img.width(), 10000);
assert_eq!(img.height(), 100);
let resized = resize_to_dimensions(&img, 160, 96, true)
.expect("Failed to resize truly extreme wide image");
assert!(resized.width() > 0);
assert!(resized.height() > 0);
let binary = auto_threshold(&resized);
let grid_width = ((binary.width + 1) / 2) as usize;
let grid_height = ((binary.height + 3) / 4) as usize;
let _grid = pixels_to_braille(&binary, grid_width, grid_height)
.expect("Failed to map truly extreme image to braille");
}
#[test]
fn test_very_large_square_image_no_regression() {
let img_path = Path::new("tests/fixtures/images/viper_4k.png");
let img = load_from_path(img_path).expect("Failed to load large square image");
assert_eq!(img.width(), 4000);
assert_eq!(img.height(), 4000);
let resized =
resize_to_dimensions(&img, 160, 96, true).expect("Failed to resize large square image");
assert!(resized.width() > 0 && resized.width() <= 160);
assert!(resized.height() > 0 && resized.height() <= 96);
let binary = auto_threshold(&resized);
let grid_width = ((binary.width + 1) / 2) as usize;
let grid_height = ((binary.height + 3) / 4) as usize;
let grid = pixels_to_braille(&binary, grid_width, grid_height)
.expect("Failed to map large square image to braille");
assert!(grid.width() > 0);
assert!(grid.height() > 0);
}
}
#[cfg(feature = "image")]
mod color_pipeline_tests {
use dotmax::image::{load_from_path, render_image_with_color, ColorMode, DitheringMethod};
use std::path::Path;
#[test]
fn test_color_pipeline_monochrome_mode() {
let img_path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(img_path).expect("Failed to load test image");
let grid = render_image_with_color(
&img,
ColorMode::Monochrome,
80, 24, DitheringMethod::FloydSteinberg,
None, 1.0, 1.0, 1.0, )
.expect("Failed to render with monochrome mode");
assert!(grid.width() > 0 && grid.width() <= 80);
assert!(grid.height() > 0 && grid.height() <= 24);
let unicode_grid = grid.to_unicode_grid();
assert_eq!(unicode_grid.len(), grid.height());
assert_eq!(unicode_grid[0].len(), grid.width());
}
#[test]
fn test_color_pipeline_grayscale_mode() {
let img_path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(img_path).expect("Failed to load test image");
let grid = render_image_with_color(
&img,
ColorMode::Grayscale,
80,
24,
DitheringMethod::FloydSteinberg,
None,
1.0,
1.0,
1.0,
)
.expect("Failed to render with grayscale mode");
assert!(grid.width() > 0 && grid.width() <= 80);
assert!(grid.height() > 0 && grid.height() <= 24);
let mut has_gray = false;
for y in 0..grid.height() {
for x in 0..grid.width() {
if let Some(color) = grid.get_color(x, y) {
if color.r == color.g && color.g == color.b {
has_gray = true;
break;
}
}
}
if has_gray {
break;
}
}
}
#[test]
fn test_color_pipeline_truecolor_mode() {
let img_path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(img_path).expect("Failed to load test image");
let grid = render_image_with_color(
&img,
ColorMode::TrueColor,
80,
24,
DitheringMethod::FloydSteinberg,
None,
1.0,
1.0,
1.0,
)
.expect("Failed to render with truecolor mode");
assert!(grid.width() > 0 && grid.width() <= 80);
assert!(grid.height() > 0 && grid.height() <= 24);
let mut color_count = 0;
for y in 0..grid.height() {
for x in 0..grid.width() {
if grid.get_color(x, y).is_some() {
color_count += 1;
}
}
}
let expected_count = grid.width() * grid.height();
assert_eq!(
color_count, expected_count,
"Every cell should have a color in TrueColor mode"
);
}
#[test]
fn test_color_pipeline_dimensions_consistent() {
let img_path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(img_path).expect("Failed to load test image");
let grid_mono = render_image_with_color(
&img,
ColorMode::Monochrome,
80,
24,
DitheringMethod::FloydSteinberg,
None,
1.0,
1.0,
1.0,
)
.expect("Failed to render monochrome");
let grid_gray = render_image_with_color(
&img,
ColorMode::Grayscale,
80,
24,
DitheringMethod::FloydSteinberg,
None,
1.0,
1.0,
1.0,
)
.expect("Failed to render grayscale");
let grid_true = render_image_with_color(
&img,
ColorMode::TrueColor,
80,
24,
DitheringMethod::FloydSteinberg,
None,
1.0,
1.0,
1.0,
)
.expect("Failed to render truecolor");
assert_eq!(grid_mono.width(), grid_gray.width());
assert_eq!(grid_mono.width(), grid_true.width());
assert_eq!(grid_mono.height(), grid_gray.height());
assert_eq!(grid_mono.height(), grid_true.height());
}
#[test]
fn test_color_extraction_with_small_image() {
use image::{DynamicImage, RgbImage};
let mut img = RgbImage::new(4, 8);
for y in 0..8 {
for x in 0..4 {
let intensity = ((x + y) * 255 / 11) as u8;
img.put_pixel(x, y, image::Rgb([intensity, intensity, intensity]));
}
}
let dyn_img = DynamicImage::ImageRgb8(img);
let grid = render_image_with_color(
&dyn_img,
ColorMode::TrueColor,
80,
24,
DitheringMethod::FloydSteinberg,
None,
1.0,
1.0,
1.0,
)
.expect("Failed to render small image");
assert_eq!(grid.width(), 2);
assert_eq!(grid.height(), 2);
}
}
#[cfg(all(feature = "image", feature = "svg"))]
mod svg_pipeline_tests {
use dotmax::image::{
apply_dithering, auto_threshold, load_svg_from_path, pixels_to_braille, to_grayscale,
DitheringMethod,
};
use std::path::Path;
#[test]
fn test_svg_to_dynamic_image_properties() {
let svg_path = Path::new("tests/fixtures/svg/svg_test.svg");
let img = load_svg_from_path(svg_path, 100, 100).expect("Failed to load SVG");
assert_eq!(img.width(), 100);
assert_eq!(img.height(), 100);
assert!(matches!(img, image::DynamicImage::ImageRgba8(_)));
}
#[test]
fn test_svg_grayscale_threshold_braille_pipeline() {
let svg_path = Path::new("tests/fixtures/svg/svg_test.svg");
let img = load_svg_from_path(svg_path, 80, 80).expect("Failed to load SVG");
let _gray = to_grayscale(&img);
let binary = auto_threshold(&img);
let expected_width = ((binary.width + 1) / 2) as usize;
let expected_height = ((binary.height + 3) / 4) as usize;
let grid =
pixels_to_braille(&binary, expected_width, expected_height).expect("Failed to map");
assert_eq!(grid.width(), expected_width);
assert_eq!(grid.height(), expected_height);
let unicode_grid = grid.to_unicode_grid();
assert_eq!(unicode_grid.len(), expected_height);
}
#[test]
fn test_svg_dither_floyd_steinberg_braille() {
let svg_path = Path::new("tests/fixtures/svg/svg_test.svg");
let img = load_svg_from_path(svg_path, 100, 100).expect("Failed to load SVG");
let gray = to_grayscale(&img);
let binary =
apply_dithering(&gray, DitheringMethod::FloydSteinberg).expect("Failed to dither");
let expected_width = ((binary.width + 1) / 2) as usize;
let expected_height = ((binary.height + 3) / 4) as usize;
let grid =
pixels_to_braille(&binary, expected_width, expected_height).expect("Failed to map");
assert_eq!(grid.width(), expected_width);
assert_eq!(grid.height(), expected_height);
}
#[test]
fn test_svg_dither_bayer_braille() {
let svg_path = Path::new("tests/fixtures/svg/svg_test.svg");
let img = load_svg_from_path(svg_path, 100, 100).expect("Failed to load SVG");
let gray = to_grayscale(&img);
let binary = apply_dithering(&gray, DitheringMethod::Bayer).expect("Failed to dither");
let expected_width = ((binary.width + 1) / 2) as usize;
let expected_height = ((binary.height + 3) / 4) as usize;
let grid =
pixels_to_braille(&binary, expected_width, expected_height).expect("Failed to map");
assert_eq!(grid.width(), expected_width);
assert_eq!(grid.height(), expected_height);
}
#[test]
fn test_svg_dither_atkinson_braille() {
let svg_path = Path::new("tests/fixtures/svg/svg_test.svg");
let img = load_svg_from_path(svg_path, 100, 100).expect("Failed to load SVG");
let gray = to_grayscale(&img);
let binary = apply_dithering(&gray, DitheringMethod::Atkinson).expect("Failed to dither");
let expected_width = ((binary.width + 1) / 2) as usize;
let expected_height = ((binary.height + 3) / 4) as usize;
let grid =
pixels_to_braille(&binary, expected_width, expected_height).expect("Failed to map");
assert_eq!(grid.width(), expected_width);
assert_eq!(grid.height(), expected_height);
}
#[test]
fn test_svg_logo_full_pipeline() {
let svg_path = Path::new("tests/fixtures/svg/svg_test.svg");
let img = load_svg_from_path(svg_path, 160, 160).expect("Failed to load SVG");
let gray = to_grayscale(&img);
let binary =
apply_dithering(&gray, DitheringMethod::FloydSteinberg).expect("Failed to dither");
let expected_width = ((binary.width + 1) / 2) as usize;
let expected_height = ((binary.height + 3) / 4) as usize;
let grid =
pixels_to_braille(&binary, expected_width, expected_height).expect("Failed to map");
assert_eq!(grid.width(), expected_width);
assert_eq!(grid.height(), expected_height);
}
#[test]
fn test_svg_text_heavy_pipeline() {
let svg_path = Path::new("tests/fixtures/svg/svg_test.svg");
let img = load_svg_from_path(svg_path, 150, 50).expect("Failed to load text SVG");
let binary = auto_threshold(&img);
let expected_width = ((binary.width + 1) / 2) as usize;
let expected_height = ((binary.height + 3) / 4) as usize;
let grid =
pixels_to_braille(&binary, expected_width, expected_height).expect("Failed to map");
assert_eq!(grid.width(), expected_width);
assert_eq!(grid.height(), expected_height);
let unicode_grid = grid.to_unicode_grid();
let has_text = unicode_grid
.iter()
.any(|row| row.iter().any(|&ch| ch != '\u{2800}'));
assert!(has_text, "Text SVG should produce visible braille output");
}
#[test]
#[ignore] fn test_svg_dark_background_light_content_renders_correctly() {
let svg_path = Path::new("tests/fixtures/svg/dark_bg_light_content.svg");
let img = load_svg_from_path(svg_path, 160, 96).expect("SVG should load");
let binary = auto_threshold(&img);
let mut black_count = 0;
let mut white_count = 0;
for y in 0..binary.height as usize {
for x in 0..binary.width as usize {
if binary.pixels[y * binary.width as usize + x] {
black_count += 1;
} else {
white_count += 1;
}
}
}
let total = black_count + white_count;
let black_percentage = (black_count * 100) / total;
assert!(
black_count > 0 && black_count < total,
"SVG content should be visible, got {}% black pixels (expected <100%)",
black_percentage
);
assert!(
black_percentage > 5 && black_percentage < 95,
"SVG should have content contrast, got {}% black (expected 5-95%)",
black_percentage
);
let expected_width = ((binary.width + 1) / 2) as usize;
let expected_height = ((binary.height + 3) / 4) as usize;
let grid = pixels_to_braille(&binary, expected_width, expected_height)
.expect("Should map to braille");
let mut filled_cells = 0;
let mut _empty_cells = 0;
for y in 0..grid.height() {
for x in 0..grid.width() {
let ch = grid.get_char(x, y);
if ch != ' ' && ch != '\u{2800}' {
filled_cells += 1;
} else {
_empty_cells += 1;
}
}
}
let total_cells = grid.width() * grid.height();
let filled_percentage = (filled_cells * 100) / total_cells;
assert!(
filled_cells > 0 && filled_cells < total_cells,
"Braille output should show content, got {} filled cells out of {} (expected <100%)",
filled_cells,
total_cells
);
assert!(
filled_percentage > 5 && filled_percentage < 95,
"Braille output should have content contrast, got {}% filled (expected 5-95%)",
filled_percentage
);
}
#[test]
fn test_svg_simple_text_renders_without_panic() {
let svg_path = Path::new("tests/test_assets/svg_font_tests/simple_text.svg");
let result = load_svg_from_path(svg_path, 400, 300);
assert!(
result.is_ok(),
"Simple text SVG should load successfully: {:?}",
result.err()
);
let img = result.unwrap();
assert_eq!(img.width(), 400);
assert_eq!(img.height(), 300);
let binary = auto_threshold(&img);
let has_content = binary.pixels.iter().any(|&pixel| pixel);
assert!(
has_content,
"SVG with text should produce visible pixels (not all white)"
);
}
#[test]
fn test_svg_fallback_font_handles_missing_fonts_gracefully() {
let svg_path = Path::new("tests/test_assets/svg_font_tests/fallback_font.svg");
let result = load_svg_from_path(svg_path, 500, 200);
assert!(
result.is_ok(),
"SVG with missing fonts should still render (fallback): {:?}",
result.err()
);
let img = result.unwrap();
assert_eq!(img.width(), 500);
assert_eq!(img.height(), 200);
let binary = auto_threshold(&img);
let has_text = binary.pixels.iter().any(|&pixel| pixel);
assert!(
has_text,
"SVG with missing font should still render text via fallback"
);
}
#[test]
fn test_svg_small_text_renders_legibly() {
let svg_path = Path::new("tests/test_assets/svg_font_tests/small_text.svg");
let result = load_svg_from_path(svg_path, 400, 300);
assert!(
result.is_ok(),
"Small text SVG should load: {:?}",
result.err()
);
let img = result.unwrap();
let binary = auto_threshold(&img);
let text_pixels = binary.pixels.iter().filter(|&&p| p).count();
let total_pixels = binary.pixels.len();
let text_percentage = (text_pixels * 100) / total_pixels;
assert!(
text_percentage > 5,
"Small text should be visible, got {}% text coverage",
text_percentage
);
let expected_width = ((binary.width + 1) / 2) as usize;
let expected_height = ((binary.height + 3) / 4) as usize;
let grid = pixels_to_braille(&binary, expected_width, expected_height)
.expect("Should map to braille");
let unicode_grid = grid.to_unicode_grid();
let has_dots = unicode_grid
.iter()
.any(|row| row.iter().any(|&ch| ch != '\u{2800}' && ch != ' '));
assert!(
has_dots,
"Small text should produce distinguishable braille output"
);
}
#[test]
fn test_svg_mixed_fonts_renders_all_families() {
let svg_path = Path::new("tests/test_assets/svg_font_tests/mixed_fonts.svg");
let result = load_svg_from_path(svg_path, 500, 300);
assert!(
result.is_ok(),
"Mixed fonts SVG should load: {:?}",
result.err()
);
let img = result.unwrap();
let binary = auto_threshold(&img);
let text_pixels = binary.pixels.iter().filter(|&&p| p).count();
let total_pixels = binary.pixels.len();
let text_percentage = (text_pixels * 100) / total_pixels;
assert!(
text_percentage > 15,
"Mixed fonts should produce visible text, got {}% coverage",
text_percentage
);
}
#[test]
fn test_svg_bold_italic_renders_font_styles() {
let svg_path = Path::new("tests/test_assets/svg_font_tests/bold_italic.svg");
let result = load_svg_from_path(svg_path, 500, 300);
assert!(
result.is_ok(),
"Bold/italic SVG should load: {:?}",
result.err()
);
let img = result.unwrap();
let binary = auto_threshold(&img);
let text_pixels = binary.pixels.iter().filter(|&&p| p).count();
let total_pixels = binary.pixels.len();
let text_percentage = (text_pixels * 100) / total_pixels;
assert!(
text_percentage > 15,
"Bold/italic text should be visible, got {}% coverage",
text_percentage
);
let expected_width = ((binary.width + 1) / 2) as usize;
let expected_height = ((binary.height + 3) / 4) as usize;
let grid = pixels_to_braille(&binary, expected_width, expected_height)
.expect("Should map to braille");
let unicode_grid = grid.to_unicode_grid();
let has_content = unicode_grid
.iter()
.any(|row| row.iter().any(|&ch| ch != '\u{2800}'));
assert!(
has_content,
"Bold/italic text should produce visible braille output"
);
}
}