1use crate::ViewportFit;
2use crate::geometry::{Point, Px, Rect, Size};
3use crate::scene::UvRect;
4
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub struct ImageObjectFitMapped {
7 pub draw_rect: Rect,
8 pub uv: UvRect,
9}
10
11pub fn map_image_object_fit(
16 destination_rect: Rect,
17 source_px_size: (u32, u32),
18 fit: ViewportFit,
19) -> Option<ImageObjectFitMapped> {
20 let (sw, sh) = source_px_size;
21 if sw == 0 || sh == 0 {
22 return None;
23 }
24
25 let dw = destination_rect.size.width.0.max(0.0);
26 let dh = destination_rect.size.height.0.max(0.0);
27 if dw <= 0.0 || dh <= 0.0 || !dw.is_finite() || !dh.is_finite() {
28 return None;
29 }
30
31 let sw = sw as f32;
32 let sh = sh as f32;
33
34 match fit {
35 ViewportFit::Stretch => Some(ImageObjectFitMapped {
36 draw_rect: destination_rect,
37 uv: UvRect::FULL,
38 }),
39 ViewportFit::Contain => {
40 let s = (dw / sw).min(dh / sh);
41 if !s.is_finite() || s <= 0.0 {
42 return None;
43 }
44
45 let draw_w = sw * s;
46 let draw_h = sh * s;
47 let x = destination_rect.origin.x.0 + (dw - draw_w) * 0.5;
48 let y = destination_rect.origin.y.0 + (dh - draw_h) * 0.5;
49
50 Some(ImageObjectFitMapped {
51 draw_rect: Rect::new(Point::new(Px(x), Px(y)), Size::new(Px(draw_w), Px(draw_h))),
52 uv: UvRect::FULL,
53 })
54 }
55 ViewportFit::Cover => {
56 let s = (dw / sw).max(dh / sh);
57 if !s.is_finite() || s <= 0.0 {
58 return None;
59 }
60
61 let cover_w = sw * s;
62 let cover_h = sh * s;
63 if cover_w <= 0.0 || cover_h <= 0.0 || !cover_w.is_finite() || !cover_h.is_finite() {
64 return None;
65 }
66
67 let mut u0 = ((cover_w - dw) * 0.5) / cover_w;
68 let mut v0 = ((cover_h - dh) * 0.5) / cover_h;
69 let mut u1 = 1.0 - u0;
70 let mut v1 = 1.0 - v0;
71
72 if !(u0.is_finite() && v0.is_finite() && u1.is_finite() && v1.is_finite()) {
73 return None;
74 }
75
76 u0 = u0.clamp(0.0, 1.0);
77 v0 = v0.clamp(0.0, 1.0);
78 u1 = u1.clamp(0.0, 1.0);
79 v1 = v1.clamp(0.0, 1.0);
80
81 if u1 < u0 {
82 u1 = u0;
83 }
84 if v1 < v0 {
85 v1 = v0;
86 }
87
88 Some(ImageObjectFitMapped {
89 draw_rect: destination_rect,
90 uv: UvRect { u0, v0, u1, v1 },
91 })
92 }
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 fn rect(x: f32, y: f32, w: f32, h: f32) -> Rect {
101 Rect::new(Point::new(Px(x), Px(y)), Size::new(Px(w), Px(h)))
102 }
103
104 #[test]
105 fn stretch_maps_to_full_uv_and_dest_rect() {
106 let mapped = map_image_object_fit(
107 rect(10.0, 20.0, 100.0, 80.0),
108 (200, 100),
109 ViewportFit::Stretch,
110 )
111 .unwrap();
112 assert_eq!(mapped.draw_rect, rect(10.0, 20.0, 100.0, 80.0));
113 assert_eq!(mapped.uv, UvRect::FULL);
114 }
115
116 #[test]
117 fn contain_letterboxes_by_shrinking_draw_rect() {
118 let mapped = map_image_object_fit(
119 rect(0.0, 0.0, 100.0, 100.0),
120 (200, 100),
121 ViewportFit::Contain,
122 )
123 .unwrap();
124 assert_eq!(mapped.draw_rect, rect(0.0, 25.0, 100.0, 50.0));
125 assert_eq!(mapped.uv, UvRect::FULL);
126 }
127
128 #[test]
129 fn contain_pillarboxes_when_source_is_tall() {
130 let mapped = map_image_object_fit(
131 rect(0.0, 0.0, 200.0, 100.0),
132 (100, 200),
133 ViewportFit::Contain,
134 )
135 .unwrap();
136 assert_eq!(mapped.draw_rect, rect(75.0, 0.0, 50.0, 100.0));
137 assert_eq!(mapped.uv, UvRect::FULL);
138 }
139
140 #[test]
141 fn cover_center_crops_by_adjusting_uv() {
142 let mapped =
143 map_image_object_fit(rect(0.0, 0.0, 100.0, 100.0), (200, 100), ViewportFit::Cover)
144 .unwrap();
145 assert_eq!(mapped.draw_rect, rect(0.0, 0.0, 100.0, 100.0));
146 assert!((mapped.uv.u0 - 0.25).abs() <= 1.0e-6);
147 assert!((mapped.uv.u1 - 0.75).abs() <= 1.0e-6);
148 assert!((mapped.uv.v0 - 0.0).abs() <= 1.0e-6);
149 assert!((mapped.uv.v1 - 1.0).abs() <= 1.0e-6);
150 }
151
152 #[test]
153 fn cover_handles_tall_images() {
154 let mapped =
155 map_image_object_fit(rect(0.0, 0.0, 200.0, 100.0), (100, 200), ViewportFit::Cover)
156 .unwrap();
157 assert_eq!(mapped.draw_rect, rect(0.0, 0.0, 200.0, 100.0));
158 assert!((mapped.uv.u0 - 0.0).abs() <= 1.0e-6);
159 assert!((mapped.uv.u1 - 1.0).abs() <= 1.0e-6);
160 assert!((mapped.uv.v0 - 0.375).abs() <= 1.0e-6);
161 assert!((mapped.uv.v1 - 0.625).abs() <= 1.0e-6);
162 }
163
164 #[test]
165 fn cover_uv_is_clamped_and_monotonic() {
166 let mapped =
167 map_image_object_fit(rect(0.0, 0.0, 100.0, 100.0), (1, 1), ViewportFit::Cover).unwrap();
168
169 assert!((0.0..=1.0).contains(&mapped.uv.u0));
170 assert!((0.0..=1.0).contains(&mapped.uv.v0));
171 assert!((0.0..=1.0).contains(&mapped.uv.u1));
172 assert!((0.0..=1.0).contains(&mapped.uv.v1));
173 assert!(mapped.uv.u0 <= mapped.uv.u1);
174 assert!(mapped.uv.v0 <= mapped.uv.v1);
175 }
176
177 #[test]
178 fn degenerate_inputs_return_none() {
179 assert!(
180 map_image_object_fit(rect(0.0, 0.0, 0.0, 10.0), (10, 10), ViewportFit::Cover).is_none()
181 );
182 assert!(
183 map_image_object_fit(rect(0.0, 0.0, 10.0, 10.0), (0, 10), ViewportFit::Cover).is_none()
184 );
185 }
186}