sharpy 0.1.2

High-performance image sharpening library for Rust
Documentation
use sharpy::{Image, EdgeMethod};
use image::{RgbImage, Rgb};

/// Create a test image with patterns to test sharpening
fn create_test_image() -> RgbImage {
    let mut img = RgbImage::new(256, 256);
    
    // Create a pattern with edges and gradients
    for y in 0..256 {
        for x in 0..256 {
            let value = if x < 128 {
                if y < 128 {
                    // Checkerboard pattern in top-left
                    if (x / 16 + y / 16) % 2 == 0 { 64 } else { 192 }
                } else {
                    // Vertical gradient in bottom-left
                    (x * 2) as u8
                }
            } else {
                if y < 128 {
                    // Horizontal gradient in top-right
                    y as u8
                } else {
                    // Solid color in bottom-right
                    128
                }
            };
            
            img.put_pixel(x as u32, y as u32, Rgb([value, value, value]));
        }
    }
    
    img
}

/// Calculate mean squared error between two images
fn calculate_mse(img1: &RgbImage, img2: &RgbImage) -> f64 {
    assert_eq!(img1.dimensions(), img2.dimensions());
    
    let mut sum = 0.0;
    let pixels = (img1.width() * img1.height()) as f64;
    
    for (p1, p2) in img1.pixels().zip(img2.pixels()) {
        for i in 0..3 {
            let diff = p1[i] as f64 - p2[i] as f64;
            sum += diff * diff;
        }
    }
    
    sum / (pixels * 3.0)
}

/// Check if sharpening actually increases edge contrast
fn measure_edge_strength(img: &RgbImage) -> f64 {
    let (width, height) = img.dimensions();
    let mut edge_sum = 0.0;
    let mut count = 0;
    
    // Simple edge detection using pixel differences
    for y in 1..height-1 {
        for x in 1..width-1 {
            let center = img.get_pixel(x, y)[0] as f64;
            let right = img.get_pixel(x+1, y)[0] as f64;
            let bottom = img.get_pixel(x, y+1)[0] as f64;
            
            let edge_strength = ((center - right).abs() + (center - bottom).abs()) / 2.0;
            edge_sum += edge_strength;
            count += 1;
        }
    }
    
    edge_sum / count as f64
}

#[test]
fn test_unsharp_mask_increases_sharpness() {
    let test_img = create_test_image();
    let image = Image::from_rgb(test_img.clone()).unwrap();
    
    let sharpened = image.unsharp_mask(1.0, 1.0, 0).unwrap();
    let sharpened_rgb = sharpened.into_rgb();
    
    let original_edges = measure_edge_strength(&test_img);
    let sharpened_edges = measure_edge_strength(&sharpened_rgb);
    
    // Sharpening should increase edge strength
    assert!(sharpened_edges > original_edges * 1.1, 
            "Edge strength should increase by at least 10% (original: {}, sharpened: {})", 
            original_edges, sharpened_edges);
}

#[test]
fn test_high_pass_sharpen_effect() {
    let test_img = create_test_image();
    let image = Image::from_rgb(test_img.clone()).unwrap();
    
    let sharpened = image.high_pass_sharpen(0.7).unwrap();
    let sharpened_rgb = sharpened.into_rgb();
    
    // High-pass should modify the image
    let mse = calculate_mse(&test_img, &sharpened_rgb);
    assert!(mse > 10.0, "High-pass sharpening should significantly modify the image (MSE: {})", mse);
    
    // But not too much
    assert!(mse < 5000.0, "High-pass sharpening should not destroy the image (MSE: {})", mse);
}

#[test]
fn test_edge_enhancement_methods() {
    let test_img = create_test_image();
    
    // Test both Sobel and Prewitt methods
    for method in [EdgeMethod::Sobel, EdgeMethod::Prewitt] {
        let image = Image::from_rgb(test_img.clone()).unwrap();
        let enhanced = image.enhance_edges(1.0, method).unwrap();
        let enhanced_rgb = enhanced.into_rgb();
        
        let original_edges = measure_edge_strength(&test_img);
        let enhanced_edges = measure_edge_strength(&enhanced_rgb);
        
        assert!(enhanced_edges > original_edges, 
                "Edge enhancement with {:?} should increase edge strength", method);
    }
}

#[test]
fn test_clarity_enhancement() {
    let test_img = create_test_image();
    let image = Image::from_rgb(test_img.clone()).unwrap();
    
    let enhanced = image.clarity(1.0, 3.0).unwrap();
    let enhanced_rgb = enhanced.into_rgb();
    
    // Clarity should modify the image
    let mse = calculate_mse(&test_img, &enhanced_rgb);
    assert!(mse > 5.0, "Clarity should modify the image (MSE: {})", mse);
    assert!(mse < 1000.0, "Clarity should not destroy the image (MSE: {})", mse);
}

#[test]
fn test_chained_operations() {
    let test_img = create_test_image();
    let image = Image::from_rgb(test_img.clone()).unwrap();
    
    // Apply multiple operations
    let result = image
        .unsharp_mask(0.5, 0.5, 5).unwrap()
        .high_pass_sharpen(0.3).unwrap()
        .clarity(0.5, 2.0).unwrap();
    
    let result_rgb = result.into_rgb();
    
    // Check that the image is still valid
    assert_eq!(result_rgb.dimensions(), test_img.dimensions());
    
    // Check that operations had cumulative effect
    let edge_strength = measure_edge_strength(&result_rgb);
    let original_strength = measure_edge_strength(&test_img);
    assert!(edge_strength > original_strength, "Chained operations should enhance edges");
}

#[test]
fn test_parameter_bounds() {
    let test_img = create_test_image();
    
    // Test with extreme but valid parameters
    let test_cases = vec![
        ("min radius", 0.5, 1.0, 0),
        ("max radius", 10.0, 1.0, 0),
        ("min amount", 1.0, 0.0, 0),
        ("max amount", 1.0, 5.0, 0),
        ("max threshold", 1.0, 1.0, 255),
    ];
    
    for (name, radius, amount, threshold) in test_cases {
        let image = Image::from_rgb(test_img.clone()).unwrap();
        let result = image.unsharp_mask(radius, amount, threshold);
        assert!(result.is_ok(), "Operation '{}' should succeed with valid parameters", name);
    }
}

#[test]
fn test_image_dimensions_preserved() {
    let sizes = vec![(100, 100), (256, 128), (333, 444)];
    
    for (width, height) in sizes {
        let img = RgbImage::new(width, height);
        let image = Image::from_rgb(img).unwrap();
        
        // Test all operations preserve dimensions
        let operations: Vec<(&str, Box<dyn Fn(Image) -> sharpy::Result<Image>>)> = vec![
            ("unsharp_mask", Box::new(|img| img.unsharp_mask(1.0, 1.0, 0))),
            ("high_pass", Box::new(|img| img.high_pass_sharpen(0.5))),
            ("edge_enhance", Box::new(|img| img.enhance_edges(1.0, EdgeMethod::Sobel))),
            ("clarity", Box::new(|img| img.clarity(1.0, 2.0))),
        ];
        
        for (name, op) in operations {
            let result = op(image.clone()).unwrap();
            let result_rgb = result.into_rgb();
            assert_eq!(result_rgb.dimensions(), (width, height), 
                      "{} should preserve dimensions", name);
        }
    }
}

#[test]
fn test_builder_pattern_integration() {
    let test_img = create_test_image();
    let image = Image::from_rgb(test_img.clone()).unwrap();
    
    let result = image.sharpen()
        .unsharp_mask(0.8, 0.8, 2)
        .edge_enhance(0.3, EdgeMethod::Sobel)
        .clarity(0.4, 2.5)
        .apply()
        .unwrap();
    
    let result_rgb = result.into_rgb();
    
    // Verify the builder applied all operations
    let edge_strength = measure_edge_strength(&result_rgb);
    let original_strength = measure_edge_strength(&test_img);
    assert!(edge_strength > original_strength * 1.2, 
            "Builder pattern should apply all sharpening operations");
}

#[test]
fn test_preset_integration() {
    use sharpy::SharpeningPresets;
    
    let test_img = create_test_image();
    let image = Image::from_rgb(test_img).unwrap();
    
    // Test all presets
    let presets = vec![
        ("subtle", SharpeningPresets::subtle(image.clone())),
        ("moderate", SharpeningPresets::moderate(image.clone())),
        ("strong", SharpeningPresets::strong(image.clone())),
        ("edge_aware", SharpeningPresets::edge_aware(image.clone())),
        ("portrait", SharpeningPresets::portrait(image.clone())),
        ("landscape", SharpeningPresets::landscape(image.clone())),
    ];
    
    for (name, builder) in presets {
        let result = builder.apply();
        assert!(result.is_ok(), "Preset '{}' should apply successfully", name);
    }
}

#[test]
fn test_memory_bounds_checking() {
    // Test that extremely large images are rejected
    let huge_image = RgbImage::new(100000, 100000); // 10 billion pixels
    let result = Image::from_rgb(huge_image);
    assert!(result.is_err(), "Should reject images exceeding memory limits");
    
    // Test dimension limits
    let tall_image = RgbImage::new(100, 70000); // Exceeds max dimension
    let result = Image::from_rgb(tall_image);
    assert!(result.is_err(), "Should reject images exceeding dimension limits");
    
    // Test that reasonable images are accepted
    let normal_image = RgbImage::new(4096, 4096); // 16 megapixels
    let result = Image::from_rgb(normal_image);
    assert!(result.is_ok(), "Should accept reasonably sized images");
}