Skip to main content

use_image/
size.rs

1/// Basic image dimensions.
2#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
3pub struct ImageSize {
4    pub width: u32,
5    pub height: u32,
6}
7
8#[derive(Clone, Copy)]
9enum Rounding {
10    Floor,
11    Nearest,
12    Ceil,
13}
14
15impl ImageSize {
16    /// Builds a size value directly.
17    #[must_use]
18    pub const fn new(width: u32, height: u32) -> Self {
19        Self { width, height }
20    }
21
22    /// Returns true when either dimension is zero.
23    #[must_use]
24    pub const fn is_empty(self) -> bool {
25        self.width == 0 || self.height == 0
26    }
27
28    /// Returns the pixel area using a wide integer.
29    #[must_use]
30    pub const fn area(self) -> u64 {
31        (self.width as u64) * (self.height as u64)
32    }
33}
34
35fn scale_value(numerator: u64, denominator: u32, rounding: Rounding) -> u32 {
36    let denominator = u64::from(denominator);
37    let value = match rounding {
38        Rounding::Floor => numerator / denominator,
39        Rounding::Nearest => (numerator + (denominator / 2)) / denominator,
40        Rounding::Ceil => numerator.div_ceil(denominator),
41    };
42
43    value as u32
44}
45
46fn scale_by_width(size: ImageSize, width: u32, rounding: Rounding) -> Option<ImageSize> {
47    if size.is_empty() {
48        return None;
49    }
50
51    if width == 0 {
52        return Some(ImageSize::default());
53    }
54
55    let height = scale_value(
56        u64::from(size.height) * u64::from(width),
57        size.width,
58        rounding,
59    )
60    .max(1);
61
62    Some(ImageSize::new(width, height))
63}
64
65fn scale_by_height(size: ImageSize, height: u32, rounding: Rounding) -> Option<ImageSize> {
66    if size.is_empty() {
67        return None;
68    }
69
70    if height == 0 {
71        return Some(ImageSize::default());
72    }
73
74    let width = scale_value(
75        u64::from(size.width) * u64::from(height),
76        size.height,
77        rounding,
78    )
79    .max(1);
80
81    Some(ImageSize::new(width, height))
82}
83
84/// Scales a size to fit completely within bounds, preserving aspect ratio.
85#[must_use]
86pub fn fit_within(size: ImageSize, bounds: ImageSize) -> ImageSize {
87    if size.is_empty() || bounds.is_empty() {
88        return ImageSize::default();
89    }
90
91    let use_width = u64::from(bounds.width) * u64::from(size.height)
92        <= u64::from(bounds.height) * u64::from(size.width);
93
94    if use_width {
95        scale_by_width(size, bounds.width, Rounding::Floor).unwrap_or_default()
96    } else {
97        scale_by_height(size, bounds.height, Rounding::Floor).unwrap_or_default()
98    }
99}
100
101/// Scales a size so it fully covers bounds, preserving aspect ratio.
102#[must_use]
103pub fn cover_size(size: ImageSize, bounds: ImageSize) -> ImageSize {
104    if size.is_empty() || bounds.is_empty() {
105        return ImageSize::default();
106    }
107
108    let use_width = u64::from(bounds.width) * u64::from(size.height)
109        >= u64::from(bounds.height) * u64::from(size.width);
110
111    if use_width {
112        scale_by_width(size, bounds.width, Rounding::Ceil).unwrap_or_default()
113    } else {
114        scale_by_height(size, bounds.height, Rounding::Ceil).unwrap_or_default()
115    }
116}
117
118/// Scales a size to an exact width and rounded matching height.
119#[must_use]
120pub fn scale_to_width(size: ImageSize, width: u32) -> Option<ImageSize> {
121    scale_by_width(size, width, Rounding::Nearest)
122}
123
124/// Scales a size to an exact height and rounded matching width.
125#[must_use]
126pub fn scale_to_height(size: ImageSize, height: u32) -> Option<ImageSize> {
127    scale_by_height(size, height, Rounding::Nearest)
128}