Skip to main content

oximedia_optimize/partition/
split.rs

1//! Partition decision optimization.
2
3use crate::OptimizerConfig;
4use oximedia_core::OxiResult;
5
6/// Partition modes.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum PartitionMode {
9    /// No split (use full block).
10    None,
11    /// Split horizontally.
12    Horizontal,
13    /// Split vertically.
14    Vertical,
15    /// Split into 4 quadrants.
16    Split4,
17}
18
19/// Partition decision result.
20#[derive(Debug, Clone, Copy)]
21pub struct PartitionDecision {
22    /// Selected partition mode.
23    pub mode: PartitionMode,
24    /// Decision cost.
25    pub cost: f64,
26    /// Whether to recurse.
27    pub should_recurse: bool,
28}
29
30/// Partition optimizer.
31pub struct SplitOptimizer {
32    min_block_size: usize,
33    #[allow(dead_code)]
34    max_block_size: usize,
35    enable_asymmetric: bool,
36}
37
38impl SplitOptimizer {
39    /// Creates a new partition optimizer.
40    pub fn new(config: &OptimizerConfig) -> OxiResult<Self> {
41        let (min_size, max_size, asymmetric) = match config.level {
42            crate::OptimizationLevel::Fast => (16, 64, false),
43            crate::OptimizationLevel::Medium => (8, 64, false),
44            crate::OptimizationLevel::Slow => (4, 128, true),
45            crate::OptimizationLevel::Placebo => (4, 128, true),
46        };
47
48        Ok(Self {
49            min_block_size: min_size,
50            max_block_size: max_size,
51            enable_asymmetric: asymmetric,
52        })
53    }
54
55    /// Decides optimal partition for a block.
56    #[allow(dead_code)]
57    #[must_use]
58    pub fn decide(&self, pixels: &[u8], block_size: usize, complexity: f64) -> PartitionDecision {
59        if block_size <= self.min_block_size {
60            return PartitionDecision {
61                mode: PartitionMode::None,
62                cost: 0.0,
63                should_recurse: false,
64            };
65        }
66
67        let mut best_mode = PartitionMode::None;
68        let mut best_cost = self.evaluate_no_split(pixels, complexity);
69
70        // Try splitting if block is complex enough
71        if complexity > 100.0 && block_size > self.min_block_size {
72            let split_cost = self.evaluate_split4(pixels, complexity);
73            if split_cost < best_cost {
74                best_cost = split_cost;
75                best_mode = PartitionMode::Split4;
76            }
77
78            if self.enable_asymmetric {
79                let h_cost = self.evaluate_horizontal(pixels, complexity);
80                if h_cost < best_cost {
81                    best_cost = h_cost;
82                    best_mode = PartitionMode::Horizontal;
83                }
84
85                let v_cost = self.evaluate_vertical(pixels, complexity);
86                if v_cost < best_cost {
87                    best_cost = v_cost;
88                    best_mode = PartitionMode::Vertical;
89                }
90            }
91        }
92
93        PartitionDecision {
94            mode: best_mode,
95            cost: best_cost,
96            should_recurse: best_mode != PartitionMode::None,
97        }
98    }
99
100    fn evaluate_no_split(&self, pixels: &[u8], complexity: f64) -> f64 {
101        // Cost of not splitting
102        let variance = self.calculate_variance(pixels);
103        variance + complexity * 0.1
104    }
105
106    fn evaluate_split4(&self, pixels: &[u8], complexity: f64) -> f64 {
107        // Cost of splitting into 4
108        let variance = self.calculate_variance(pixels);
109        variance * 0.7 + complexity * 0.2 + 10.0 // Splitting cost
110    }
111
112    fn evaluate_horizontal(&self, pixels: &[u8], complexity: f64) -> f64 {
113        // Cost of horizontal split
114        let variance = self.calculate_variance(pixels);
115        variance * 0.8 + complexity * 0.15 + 8.0
116    }
117
118    fn evaluate_vertical(&self, pixels: &[u8], complexity: f64) -> f64 {
119        // Cost of vertical split
120        let variance = self.calculate_variance(pixels);
121        variance * 0.8 + complexity * 0.15 + 8.0
122    }
123
124    fn calculate_variance(&self, pixels: &[u8]) -> f64 {
125        if pixels.is_empty() {
126            return 0.0;
127        }
128
129        let mean = pixels.iter().map(|&p| f64::from(p)).sum::<f64>() / pixels.len() as f64;
130        pixels
131            .iter()
132            .map(|&p| {
133                let diff = f64::from(p) - mean;
134                diff * diff
135            })
136            .sum::<f64>()
137            / pixels.len() as f64
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn test_split_optimizer_creation() {
147        let config = OptimizerConfig::default();
148        let optimizer =
149            SplitOptimizer::new(&config).expect("split optimizer creation should succeed");
150        assert_eq!(optimizer.min_block_size, 8);
151    }
152
153    #[test]
154    fn test_partition_modes() {
155        assert_ne!(PartitionMode::None, PartitionMode::Split4);
156        assert_eq!(PartitionMode::Horizontal, PartitionMode::Horizontal);
157    }
158
159    #[test]
160    fn test_min_block_size_no_split() {
161        let config = OptimizerConfig::default();
162        let optimizer =
163            SplitOptimizer::new(&config).expect("split optimizer creation should succeed");
164        let pixels = vec![128u8; 64];
165        let decision = optimizer.decide(&pixels, 8, 50.0);
166        assert_eq!(decision.mode, PartitionMode::None);
167        assert!(!decision.should_recurse);
168    }
169
170    #[test]
171    fn test_high_complexity_split() {
172        let config = OptimizerConfig::default();
173        let optimizer =
174            SplitOptimizer::new(&config).expect("split optimizer creation should succeed");
175        let pixels = vec![128u8; 256]; // Larger block
176        let decision = optimizer.decide(&pixels, 32, 500.0); // High complexity
177                                                             // May or may not split depending on cost evaluation
178        assert!(decision.cost >= 0.0);
179    }
180}