image_optimizer/file_ops/
size_calculator.rs

1/// Calculates new image dimensions while preserving aspect ratio.
2///
3/// This function determines the optimal dimensions for resizing an image to fit within
4/// a maximum size constraint while maintaining the original aspect ratio. The resize
5/// is based on the longer edge of the image.
6///
7/// # Arguments
8///
9/// * `width` - Original image width in pixels
10/// * `height` - Original image height in pixels  
11/// * `max_size` - Maximum allowed size for the longer edge in pixels
12///
13/// # Returns
14///
15/// A tuple `(new_width, new_height)` containing the calculated dimensions.
16/// If no resizing is needed (longer edge ≤ `max_size`), returns the original dimensions.
17///
18/// # Algorithm
19///
20/// 1. Determines the longer edge between width and height
21/// 2. If longer edge ≤ `max_size`, no resizing is needed
22/// 3. Otherwise, calculates a scaling factor to fit the longer edge to `max_size`
23/// 4. Applies the scaling factor to both dimensions and rounds to nearest integer
24///
25/// # Examples
26///
27/// ```rust
28/// use image_optimizer::file_ops::calculate_resize_dimensions;
29///
30/// // No resize needed
31/// assert_eq!(calculate_resize_dimensions(800, 600, 1000), (800, 600));
32///
33/// // Width is longer edge, needs resize
34/// assert_eq!(calculate_resize_dimensions(1200, 800, 600), (600, 400));
35///
36/// // Height is longer edge, needs resize  
37/// assert_eq!(calculate_resize_dimensions(800, 1200, 600), (400, 600));
38///
39/// // Square image resize
40/// assert_eq!(calculate_resize_dimensions(1000, 1000, 500), (500, 500));
41/// ```
42#[must_use]
43pub fn calculate_resize_dimensions(width: u32, height: u32, max_size: u32) -> (u32, u32) {
44    let longer_edge = width.max(height);
45
46    if longer_edge <= max_size {
47        return (width, height);
48    }
49
50    let scale_factor = f64::from(max_size) / f64::from(longer_edge);
51    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
52    let new_width = (f64::from(width) * scale_factor).round() as u32;
53    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
54    let new_height = (f64::from(height) * scale_factor).round() as u32;
55
56    (new_width, new_height)
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[test]
64    fn test_no_resize_needed() {
65        assert_eq!(calculate_resize_dimensions(800, 600, 1000), (800, 600));
66        assert_eq!(calculate_resize_dimensions(100, 100, 200), (100, 100));
67        assert_eq!(calculate_resize_dimensions(500, 300, 500), (500, 300));
68    }
69
70    #[test]
71    fn test_width_longer_resize() {
72        assert_eq!(calculate_resize_dimensions(1200, 800, 600), (600, 400));
73        assert_eq!(calculate_resize_dimensions(2000, 1000, 800), (800, 400));
74    }
75
76    #[test]
77    fn test_height_longer_resize() {
78        assert_eq!(calculate_resize_dimensions(800, 1200, 600), (400, 600));
79        assert_eq!(calculate_resize_dimensions(1000, 2000, 800), (400, 800));
80    }
81
82    #[test]
83    fn test_square_image_resize() {
84        assert_eq!(calculate_resize_dimensions(1000, 1000, 500), (500, 500));
85        assert_eq!(calculate_resize_dimensions(2000, 2000, 800), (800, 800));
86    }
87
88    #[test]
89    fn test_edge_cases() {
90        assert_eq!(calculate_resize_dimensions(1, 1, 100), (1, 1));
91        assert_eq!(calculate_resize_dimensions(u32::MAX, 100, 50), (50, 0));
92    }
93
94    #[test]
95    fn test_rounding() {
96        assert_eq!(calculate_resize_dimensions(1333, 1000, 800), (800, 600));
97        assert_eq!(calculate_resize_dimensions(1001, 1000, 800), (800, 799));
98    }
99}