#![cfg(feature = "image")]
use dotmax::image::{
adjust_brightness, adjust_contrast, adjust_gamma, apply_threshold, auto_threshold,
load_from_bytes, load_from_path, resize_to_dimensions, resize_to_terminal, supported_formats,
to_grayscale,
};
use dotmax::DotmaxError;
use std::path::Path;
#[test]
fn test_integration_load_png_verify_dimensions() {
let path = Path::new("tests/fixtures/images/sample.png");
let result = load_from_path(path);
assert!(result.is_ok(), "Failed to load PNG: {:?}", result.err());
let img = result.unwrap();
assert_eq!(img.width(), 10, "Expected 10x10 test image");
assert_eq!(img.height(), 10, "Expected 10x10 test image");
}
#[test]
fn test_integration_load_second_image_file() {
let path = Path::new("tests/fixtures/images/test_photo.jpg");
if path.exists() {
let result = load_from_path(path);
match result {
Ok(img) => {
assert!(img.width() > 0);
assert!(img.height() > 0);
}
Err(DotmaxError::ImageLoad { .. }) => {
}
Err(other) => {
panic!("Unexpected error type: {:?}", other);
}
}
}
}
#[test]
fn test_integration_load_all_supported_formats() {
let formats = supported_formats();
assert!(formats.contains(&"png"), "PNG should be supported");
assert!(formats.contains(&"jpg"), "JPG should be supported");
assert!(formats.contains(&"jpeg"), "JPEG should be supported");
assert!(formats.contains(&"gif"), "GIF should be supported");
assert!(formats.contains(&"bmp"), "BMP should be supported");
assert!(formats.contains(&"webp"), "WebP should be supported");
assert!(formats.contains(&"tiff"), "TIFF should be supported");
assert_eq!(formats.len(), 7, "Expected 7 supported formats");
}
#[test]
fn test_integration_error_handling_missing_file() {
let path = Path::new("tests/fixtures/images/does_not_exist.png");
let result = load_from_path(path);
assert!(result.is_err(), "Should return error for missing file");
match result.unwrap_err() {
DotmaxError::ImageLoad { path: err_path, .. } => {
assert!(err_path.to_string_lossy().contains("does_not_exist"));
}
other => panic!("Expected ImageLoad error, got {:?}", other),
}
}
#[test]
fn test_integration_error_handling_corrupted_file() {
let path = Path::new("tests/fixtures/images/corrupted.png");
let result = load_from_path(path);
assert!(result.is_err(), "Should return error for corrupted file");
match result.unwrap_err() {
DotmaxError::ImageLoad { .. } => {
}
other => panic!("Expected ImageLoad error, got {:?}", other),
}
}
#[test]
fn test_integration_bytes_loading_roundtrip() {
let path = Path::new("tests/fixtures/images/sample.png");
let bytes = std::fs::read(path).expect("Failed to read sample.png");
let img_from_path = load_from_path(path).expect("Failed to load from path");
let img_from_bytes = load_from_bytes(&bytes).expect("Failed to load from bytes");
assert_eq!(
img_from_path.width(),
img_from_bytes.width(),
"Width should match"
);
assert_eq!(
img_from_path.height(),
img_from_bytes.height(),
"Height should match"
);
}
#[test]
fn test_integration_feature_gate_compiles() {
let formats = supported_formats();
assert!(!formats.is_empty(), "Should have supported formats");
}
#[test]
fn test_integration_zero_panics_guarantee() {
let result = load_from_path(Path::new("nonexistent.png"));
assert!(result.is_err());
let result = load_from_bytes(b"not an image");
assert!(result.is_err());
let result = load_from_path(Path::new("tests/fixtures/images/corrupted.png"));
assert!(result.is_err());
}
#[test]
fn test_integration_load_and_resize_to_terminal() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample.png");
let resized = resize_to_terminal(&img, 80, 24).expect("Resize failed");
assert!(
resized.width() <= 160,
"Width {} exceeds terminal width 160",
resized.width()
);
assert!(
resized.height() <= 96,
"Height {} exceeds terminal height 96",
resized.height()
);
assert_eq!(
resized.width(),
96,
"Square image upscales to fit terminal height"
);
assert_eq!(
resized.height(),
96,
"Square image upscales to fit terminal height"
);
}
#[test]
fn test_integration_load_jpg_and_resize_preserve_aspect() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load image");
let resized = resize_to_dimensions(&img, 100, 50, true).expect("Resize failed");
assert!(
resized.width() <= 100,
"Width {} exceeds target 100",
resized.width()
);
assert!(
resized.height() <= 50,
"Height {} exceeds target 50",
resized.height()
);
assert_eq!(resized.width(), 50);
assert_eq!(resized.height(), 50);
}
#[test]
fn test_integration_resize_without_aspect_preservation() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample.png");
let resized = resize_to_dimensions(&img, 100, 50, false).expect("Resize failed");
assert_eq!(resized.width(), 100, "Width should match target exactly");
assert_eq!(resized.height(), 50, "Height should match target exactly");
}
#[test]
fn test_integration_braille_cell_math_verification() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample.png");
let test_cases = [
(80, 24, 160, 96), (100, 30, 200, 120), (40, 12, 80, 48), ];
for (term_w, term_h, max_px_w, max_px_h) in test_cases {
let resized = resize_to_terminal(&img, term_w, term_h)
.unwrap_or_else(|_| panic!("Failed to resize to {}×{}", term_w, term_h));
assert!(
resized.width() <= max_px_w,
"Terminal {}×{} → width {} exceeds max {}",
term_w,
term_h,
resized.width(),
max_px_w
);
assert!(
resized.height() <= max_px_h,
"Terminal {}×{} → height {} exceeds max {}",
term_w,
term_h,
resized.height(),
max_px_h
);
}
}
#[test]
fn test_integration_resize_error_handling_zero_dimensions() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample.png");
let result = resize_to_terminal(&img, 0, 24);
assert!(result.is_err(), "Should error on zero terminal width");
assert!(
matches!(
result.unwrap_err(),
DotmaxError::InvalidImageDimensions { .. }
),
"Should return InvalidImageDimensions error"
);
let result = resize_to_terminal(&img, 80, 0);
assert!(result.is_err(), "Should error on zero terminal height");
let result = resize_to_dimensions(&img, 0, 100, true);
assert!(result.is_err(), "Should error on zero target width");
let result = resize_to_dimensions(&img, 100, 0, false);
assert!(result.is_err(), "Should error on zero target height");
}
#[test]
fn test_integration_full_pipeline_bytes_to_resize() {
let path = Path::new("tests/fixtures/images/sample.png");
let bytes = std::fs::read(path).expect("Failed to read sample.png");
let img = load_from_bytes(&bytes).expect("Failed to load from bytes");
let resized = resize_to_terminal(&img, 80, 24).expect("Resize failed");
assert!(resized.width() > 0 && resized.width() <= 160);
assert!(resized.height() > 0 && resized.height() <= 96);
}
#[test]
fn test_integration_large_terminal_resize() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample.png");
let resized = resize_to_terminal(&img, 200, 50).expect("Resize failed");
assert!(resized.width() <= 400);
assert!(resized.height() <= 200);
assert_eq!(
resized.width(),
200,
"Square image upscales to fit terminal height (200×200)"
);
assert_eq!(
resized.height(),
200,
"Square image upscales to fit terminal height (200×200)"
);
}
#[test]
fn test_integration_resize_uses_lanczos3_quality() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample.png");
let sizes = [(80, 24), (100, 30), (40, 12)];
for (w, h) in sizes {
let resized = resize_to_terminal(&img, w, h)
.unwrap_or_else(|_| panic!("Failed to resize to {}×{}", w, h));
assert!(resized.width() > 0, "Resized image has zero width");
assert!(resized.height() > 0, "Resized image has zero height");
let channels = resized.color().channel_count();
assert!(
channels == 3 || channels == 4,
"Resized image should be RGB or RGBA, got {} channels",
channels
);
}
}
#[test]
fn test_integration_load_resize_grayscale_pipeline() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample.png");
let resized = resize_to_terminal(&img, 80, 24).expect("Resize failed");
let gray = to_grayscale(&resized);
assert_eq!(gray.width(), resized.width());
assert_eq!(gray.height(), resized.height());
let first_pixel = gray.get_pixel(0, 0);
assert_eq!(first_pixel.0.len(), 1, "GrayImage should have 1 channel");
}
#[test]
fn test_integration_auto_threshold_pipeline() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample.png");
let binary = auto_threshold(&img);
assert_eq!(binary.width, img.width());
assert_eq!(binary.height, img.height());
assert_eq!(binary.pixel_count(), (img.width() * img.height()) as usize);
for &pixel in &binary.pixels {
let _: bool = pixel;
}
}
#[test]
fn test_integration_brightness_adjustment_affects_threshold() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample.png");
let gray = to_grayscale(&img);
let bright = adjust_brightness(&gray, 1.5).expect("Brightness adjustment failed");
let binary_normal = apply_threshold(&gray, 128);
let binary_bright = apply_threshold(&bright, 128);
let normal_black_count = binary_normal.pixels.iter().filter(|&&p| p).count();
let bright_black_count = binary_bright.pixels.iter().filter(|&&p| p).count();
assert!(
bright_black_count >= normal_black_count,
"Brightness should increase black pixels"
);
}
#[test]
fn test_integration_contrast_adjustment_pipeline() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample.png");
let gray = to_grayscale(&img);
let contrasted = adjust_contrast(&gray, 1.5).expect("Contrast adjustment failed");
assert_eq!(contrasted.width(), gray.width());
assert_eq!(contrasted.height(), gray.height());
let binary = apply_threshold(&contrasted, 128);
assert_eq!(
binary.pixel_count(),
(gray.width() * gray.height()) as usize
);
}
#[test]
fn test_integration_gamma_correction_pipeline() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample.png");
let gray = to_grayscale(&img);
let gamma_corrected = adjust_gamma(&gray, 0.8).expect("Gamma correction failed");
assert_eq!(gamma_corrected.width(), gray.width());
assert_eq!(gamma_corrected.height(), gray.height());
let binary = apply_threshold(&gamma_corrected, 128);
assert_eq!(
binary.pixel_count(),
(gray.width() * gray.height()) as usize
);
}
#[test]
fn test_integration_chained_adjustments_pipeline() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample.png");
let gray = to_grayscale(&img);
let adjusted = adjust_brightness(&gray, 1.2)
.and_then(|img| adjust_contrast(&img, 1.3))
.and_then(|img| adjust_gamma(&img, 0.9))
.expect("Chained adjustments failed");
assert_eq!(adjusted.width(), gray.width());
assert_eq!(adjusted.height(), gray.height());
let binary = apply_threshold(&adjusted, 128);
assert_eq!(
binary.pixel_count(),
(gray.width() * gray.height()) as usize
);
}
#[test]
fn test_integration_adjustment_error_handling() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample.png");
let gray = to_grayscale(&img);
let result = adjust_brightness(&gray, -0.5);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
DotmaxError::InvalidParameter { .. }
));
let result = adjust_brightness(&gray, 3.0);
assert!(result.is_err());
let result = adjust_contrast(&gray, -1.0);
assert!(result.is_err());
let result = adjust_contrast(&gray, 2.5);
assert!(result.is_err());
let result = adjust_gamma(&gray, 0.05);
assert!(result.is_err());
let result = adjust_gamma(&gray, 5.0);
assert!(result.is_err());
}
#[test]
fn test_integration_complete_image_to_binary_pipeline() {
use image::DynamicImage;
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load");
let resized = resize_to_terminal(&img, 80, 24).expect("Failed to resize");
let gray = to_grayscale(&resized);
let adjusted = adjust_brightness(&gray, 1.1).expect("Failed to adjust brightness");
let binary = auto_threshold(&DynamicImage::ImageLuma8(adjusted));
assert!(binary.width > 0);
assert!(binary.height > 0);
assert_eq!(
binary.pixel_count(),
(binary.width * binary.height) as usize
);
for &pixel in &binary.pixels {
let _: bool = pixel;
}
}
#[test]
fn test_integration_binary_image_pixel_access() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample.png");
let binary = auto_threshold(&img);
let pixel = binary.get_pixel(0, 0);
assert!(pixel.is_some());
let out_of_bounds = binary.get_pixel(10000, 10000);
assert!(out_of_bounds.is_none());
}
use dotmax::image::{apply_dithering, DitheringMethod};
#[test]
fn test_integration_full_pipeline_with_floyd_steinberg() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample image");
let resized = resize_to_dimensions(&img, 20, 20, true).expect("Resize failed");
let gray = to_grayscale(&resized);
let binary = apply_dithering(&gray, DitheringMethod::FloydSteinberg).expect("Dithering failed");
assert_eq!(binary.width, 20);
assert_eq!(binary.height, 20);
assert_eq!(binary.pixels.len(), 400); }
#[test]
fn test_integration_full_pipeline_with_bayer() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample image");
let resized = resize_to_dimensions(&img, 20, 20, true).expect("Resize failed");
let gray = to_grayscale(&resized);
let binary = apply_dithering(&gray, DitheringMethod::Bayer).expect("Dithering failed");
assert_eq!(binary.width, 20);
assert_eq!(binary.height, 20);
}
#[test]
fn test_integration_full_pipeline_with_atkinson() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample image");
let resized = resize_to_dimensions(&img, 20, 20, true).expect("Resize failed");
let gray = to_grayscale(&resized);
let binary = apply_dithering(&gray, DitheringMethod::Atkinson).expect("Dithering failed");
assert_eq!(binary.width, 20);
assert_eq!(binary.height, 20);
}
#[test]
fn test_integration_dithering_method_none() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample image");
let resized = resize_to_dimensions(&img, 20, 20, true).expect("Resize failed");
let gray = to_grayscale(&resized);
let binary = apply_dithering(&gray, DitheringMethod::None).expect("Dithering failed");
assert_eq!(binary.width, 20);
assert_eq!(binary.height, 20);
}
#[test]
fn test_integration_all_dithering_methods_produce_valid_output() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample image");
let resized = resize_to_dimensions(&img, 30, 30, true).expect("Resize failed");
let gray = to_grayscale(&resized);
let floyd =
apply_dithering(&gray, DitheringMethod::FloydSteinberg).expect("Floyd-Steinberg failed");
let bayer = apply_dithering(&gray, DitheringMethod::Bayer).expect("Bayer failed");
let atkinson = apply_dithering(&gray, DitheringMethod::Atkinson).expect("Atkinson failed");
let none = apply_dithering(&gray, DitheringMethod::None).expect("None failed");
assert_eq!(floyd.width, 30);
assert_eq!(bayer.width, 30);
assert_eq!(atkinson.width, 30);
assert_eq!(none.width, 30);
assert_eq!(floyd.pixels.len(), 900);
assert_eq!(bayer.pixels.len(), 900);
assert_eq!(atkinson.pixels.len(), 900);
assert_eq!(none.pixels.len(), 900);
}
#[test]
fn test_integration_dithering_preserves_dimensions() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample image");
for size in [10, 20, 50, 100] {
let resized = resize_to_dimensions(&img, size, size, true).expect("Resize failed");
let gray = to_grayscale(&resized);
let floyd = apply_dithering(&gray, DitheringMethod::FloydSteinberg)
.expect("Floyd-Steinberg failed");
let bayer = apply_dithering(&gray, DitheringMethod::Bayer).expect("Bayer failed");
let atkinson = apply_dithering(&gray, DitheringMethod::Atkinson).expect("Atkinson failed");
assert_eq!(floyd.width, size);
assert_eq!(floyd.height, size);
assert_eq!(bayer.width, size);
assert_eq!(bayer.height, size);
assert_eq!(atkinson.width, size);
assert_eq!(atkinson.height, size);
}
}
#[test]
fn test_integration_dithering_with_brightness_adjustment() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample image");
let resized = resize_to_dimensions(&img, 20, 20, true).expect("Resize failed");
let gray = to_grayscale(&resized);
let adjusted = adjust_brightness(&gray, 1.5).expect("Brightness adjustment failed");
let binary =
apply_dithering(&adjusted, DitheringMethod::FloydSteinberg).expect("Dithering failed");
assert_eq!(binary.width, 20);
assert_eq!(binary.height, 20);
}
#[test]
fn test_integration_dithering_with_contrast_adjustment() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample image");
let resized = resize_to_dimensions(&img, 20, 20, true).expect("Resize failed");
let gray = to_grayscale(&resized);
let adjusted = adjust_contrast(&gray, 1.5).expect("Contrast adjustment failed");
let binary = apply_dithering(&adjusted, DitheringMethod::Bayer).expect("Dithering failed");
assert_eq!(binary.width, 20);
assert_eq!(binary.height, 20);
}
#[test]
fn test_integration_dithering_cross_platform_consistency() {
let path = Path::new("tests/fixtures/images/sample.png");
let img = load_from_path(path).expect("Failed to load sample image");
let resized = resize_to_dimensions(&img, 15, 15, true).expect("Resize failed");
let gray = to_grayscale(&resized);
let result1 = apply_dithering(&gray, DitheringMethod::Bayer).expect("Dithering failed");
let result2 = apply_dithering(&gray, DitheringMethod::Bayer).expect("Dithering failed");
assert_eq!(
result1.pixels, result2.pixels,
"Bayer dithering should be deterministic"
);
}