crabcamera 0.9.2

Advanced cross-platform camera integration for Tauri applications
Documentation
/// Image alignment utilities.
pub mod align;
/// Focus Stacking Module
///
/// Implements automated focus stacking for macro photography:
/// 1. Capture multiple images at different focus distances
/// 2. Align images to compensate for camera movement
/// 3. Detect sharp regions using edge detection
/// 4. Blend images using pyramid blending for smooth transitions
///
/// This is useful for macro photography where depth of field is limited.
pub mod capture;
/// Image merging and stacking algorithms.
pub mod merge;

use crate::types::CameraFrame;

/// Focus stack configuration
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct FocusStackConfig {
    /// Number of focus steps to capture
    pub num_steps: u32,

    /// Delay between captures (ms) - allows camera to stabilize
    pub step_delay_ms: u32,

    /// Focus distance start (0.0 = near, 1.0 = far)
    pub focus_start: f32,

    /// Focus distance end
    pub focus_end: f32,

    /// Enable alignment compensation
    pub enable_alignment: bool,

    /// Sharpness threshold for region detection (0.0-1.0)
    pub sharpness_threshold: f32,

    /// Pyramid blending levels (3-7 recommended)
    pub blend_levels: u32,
}

impl Default for FocusStackConfig {
    fn default() -> Self {
        Self {
            num_steps: 10,
            step_delay_ms: 200,
            focus_start: 0.0,
            focus_end: 1.0,
            enable_alignment: true,
            sharpness_threshold: 0.5,
            blend_levels: 5,
        }
    }
}

/// Focus stack result containing the merged image and metadata
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct FocusStackResult {
    /// The final merged frame
    pub merged_frame: CameraFrame,

    /// Number of source images used
    pub num_sources: usize,

    /// Average alignment error (pixels)
    pub alignment_error: f32,

    /// Processing time (ms)
    pub processing_time_ms: u64,
}

/// Focus stack error types
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum FocusStackError {
    /// Not enough source images
    InsufficientImages {
        /// The minimum number of images required.
        required: usize,
        /// The number of images actually provided.
        provided: usize,
    },

    /// Image dimensions don't match
    DimensionMismatch {
        /// Expected dimensions (width, height).
        expected: (u32, u32),
        /// Actual dimensions found (width, height).
        got: (u32, u32),
    },

    /// Frame data is corrupted or wrong size
    DataCorruption {
        /// Frame size found.
        frame_size: usize,
        /// Frame size expected.
        expected_size: usize,
    },

    /// Alignment failed
    AlignmentFailed(String),

    /// Merge failed
    MergeFailed(String),

    /// Invalid configuration
    InvalidConfig(String),
}

impl std::fmt::Display for FocusStackError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InsufficientImages { required, provided } => {
                write!(f, "Insufficient images: need {required}, got {provided}")
            }
            Self::DimensionMismatch { expected, got } => {
                write!(
                    f,
                    "Image dimension mismatch: expected {}x{}, got {}x{}",
                    expected.0, expected.1, got.0, got.1
                )
            }
            Self::DataCorruption {
                frame_size,
                expected_size,
            } => {
                write!(
                    f,
                    "Frame data corruption: got {frame_size} bytes, expected {expected_size}"
                )
            }
            Self::AlignmentFailed(msg) => write!(f, "Alignment failed: {msg}"),
            Self::MergeFailed(msg) => write!(f, "Merge failed: {msg}"),
            Self::InvalidConfig(msg) => write!(f, "Invalid config: {msg}"),
        }
    }
}

impl std::error::Error for FocusStackError {}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_default_config() {
        let config = FocusStackConfig::default();
        assert_eq!(config.num_steps, 10);
        assert_eq!(config.step_delay_ms, 200);
        assert!((config.focus_start - 0.0).abs() < 1e-6);
        assert!((config.focus_end - 1.0).abs() < 1e-6);
        assert!(config.enable_alignment);
        assert!((config.sharpness_threshold - 0.5).abs() < 1e-6);
        assert_eq!(config.blend_levels, 5);
    }

    #[test]
    fn test_config_validation() {
        let config = FocusStackConfig {
            num_steps: 2,
            ..Default::default()
        };

        // Test num_steps bounds
        assert!(config.num_steps >= 2);

        // Test focus range
        assert!(config.focus_start >= 0.0 && config.focus_start <= 1.0);
        assert!(config.focus_end >= 0.0 && config.focus_end <= 1.0);

        // Test threshold bounds
        assert!(config.sharpness_threshold >= 0.0 && config.sharpness_threshold <= 1.0);

        // Test blend levels reasonable
        assert!(config.blend_levels >= 3 && config.blend_levels <= 10);
    }

    #[test]
    fn test_error_display() {
        let err = FocusStackError::InsufficientImages {
            required: 5,
            provided: 2,
        };
        assert!(err.to_string().contains("Insufficient images"));

        let err = FocusStackError::DimensionMismatch {
            expected: (1920, 1080),
            got: (1280, 720),
        };
        assert!(err.to_string().contains("dimension mismatch"));
    }
}