1#[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 #[must_use]
18 pub const fn new(width: u32, height: u32) -> Self {
19 Self { width, height }
20 }
21
22 #[must_use]
24 pub const fn is_empty(self) -> bool {
25 self.width == 0 || self.height == 0
26 }
27
28 #[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#[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#[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#[must_use]
120pub fn scale_to_width(size: ImageSize, width: u32) -> Option<ImageSize> {
121 scale_by_width(size, width, Rounding::Nearest)
122}
123
124#[must_use]
126pub fn scale_to_height(size: ImageSize, height: u32) -> Option<ImageSize> {
127 scale_by_height(size, height, Rounding::Nearest)
128}