1use crate::media::{AlignmentX, AlignmentY, ITileBrush, MediaExtensions, Stretch, StretchDirection, TileMode};
2use crate::{Matrix, Point, Rect, RelativeRect, Size, Vector};
3
4pub struct TileBrushCalculator {
6 image_size: Size,
7 draw_rect: Rect,
8 is_valid: bool,
9 destination_rect: Rect,
10 intermediate_size: Size,
11 intermediate_transform: Matrix,
12 source_rect: Rect,
13}
14
15impl TileBrushCalculator {
16 pub fn from_brush(brush: &dyn ITileBrush, content_size: Size, target_size: Size) -> Self {
22 Self::new(
23 brush.tile_mode(),
24 brush.stretch(),
25 brush.alignment_x(),
26 brush.alignment_y(),
27 brush.source_rect(),
28 brush.destination_rect(),
29 content_size,
30 target_size,
31 )
32 }
33
34 #[allow(clippy::too_many_arguments)]
45 pub fn new(
46 tile_mode: TileMode,
47 stretch: Stretch,
48 alignment_x: AlignmentX,
49 alignment_y: AlignmentY,
50 source_rect: RelativeRect,
51 destination_rect: RelativeRect,
52 content_size: Size,
53 target_size: Size,
54 ) -> Self {
55 let image_size = content_size;
56
57 let source_rect = source_rect.to_pixels(image_size);
58 let destination_rect = destination_rect.to_pixels(target_size);
59
60 let scale = MediaExtensions::calculate_scaling(
61 stretch,
62 destination_rect.size(),
63 source_rect.size(),
64 StretchDirection::Both,
65 );
66 let translate = Self::calculate_translate(alignment_x, alignment_y, source_rect, destination_rect, scale);
67
68 let intermediate_size = if tile_mode == TileMode::None { target_size } else { destination_rect.size() };
69 let (intermediate_transform, draw_rect) =
70 Self::calculate_intermediate_transform(tile_mode, source_rect, destination_rect, scale, translate);
71
72 Self {
73 image_size,
74 draw_rect,
75 is_valid: false,
76 destination_rect,
77 intermediate_size,
78 intermediate_transform,
79 source_rect,
80 }
81 }
82
83 pub fn is_valid(&self) -> bool {
85 self.is_valid
86 }
87
88 pub fn destination_rect(&self) -> Rect {
93 self.destination_rect
94 }
95
96 pub fn intermediate_clip(&self) -> Rect {
99 self.draw_rect
100 }
101
102 pub fn intermediate_size(&self) -> Size {
105 self.intermediate_size
106 }
107
108 pub fn intermediate_transform(&self) -> Matrix {
111 self.intermediate_transform
112 }
113
114 pub fn needs_intermediate(&self) -> bool {
121 if self.intermediate_transform != Matrix::IDENTITY {
122 return true;
123 }
124 if self.source_rect.position() != Point::default() {
125 return true;
126 }
127 if self.source_rect.size().aspect_ratio() == self.image_size.aspect_ratio() {
128 return false;
129 }
130 if self.source_rect.width != self.image_size.width || self.source_rect.height != self.image_size.height {
131 return true;
132 }
133 false
134 }
135
136 pub fn source_rect(&self) -> Rect {
138 self.source_rect
139 }
140
141 pub fn calculate_translate(
142 alignment_x: AlignmentX,
143 alignment_y: AlignmentY,
144 source_rect: Rect,
145 destination_rect: Rect,
146 scale: Vector,
147 ) -> Vector {
148 Self::calculate_translate_sizes(alignment_x, alignment_y, source_rect.size() * scale, destination_rect.size())
149 }
150
151 pub fn calculate_translate_sizes(
152 alignment_x: AlignmentX,
153 alignment_y: AlignmentY,
154 source_size: Size,
155 destination_size: Size,
156 ) -> Vector {
157 let mut x = 0.0;
158 let mut y = 0.0;
159
160 match alignment_x {
161 AlignmentX::Center => x += (destination_size.width - source_size.width) / 2.0,
162 AlignmentX::Right => x += destination_size.width - source_size.width,
163 AlignmentX::Left => {}
164 }
165
166 match alignment_y {
167 AlignmentY::Center => y += (destination_size.height - source_size.height) / 2.0,
168 AlignmentY::Bottom => y += destination_size.height - source_size.height,
169 AlignmentY::Top => {}
170 }
171
172 Vector::new(x, y)
173 }
174
175 pub fn calculate_intermediate_transform(
177 tile_mode: TileMode,
178 source_rect: Rect,
179 destination_rect: Rect,
180 scale: Vector,
181 translate: Vector,
182 ) -> (Matrix, Rect) {
183 let mut transform = Matrix::create_translation_vector((-source_rect.position()).into())
184 * Matrix::create_scale_vector(scale)
185 * Matrix::create_translation_vector(translate);
186 let dr;
187
188 if tile_mode == TileMode::None {
189 dr = destination_rect;
190 transform *= Matrix::create_translation_vector(destination_rect.position().into());
191 } else {
192 dr = Rect::from_size(destination_rect.size());
193 }
194
195 (transform, dr)
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use crate::RelativeUnit;
203
204 #[test]
205 fn no_tile_fill_1x() {
206 let result = TileBrushCalculator::new(
207 TileMode::None,
208 Stretch::Fill,
209 AlignmentX::Center,
210 AlignmentY::Center,
211 RelativeRect::FILL,
212 RelativeRect::FILL,
213 Size::new(100.0, 100.0),
214 Size::new(100.0, 100.0),
215 );
216
217 assert!(!result.needs_intermediate());
218 assert_eq!(Rect::new(0.0, 0.0, 100.0, 100.0), result.source_rect());
219 assert_eq!(Rect::new(0.0, 0.0, 100.0, 100.0), result.destination_rect());
220 }
221
222 #[test]
223 fn no_tile_fill_2x() {
224 let result = TileBrushCalculator::new(
225 TileMode::None,
226 Stretch::Fill,
227 AlignmentX::Center,
228 AlignmentY::Center,
229 RelativeRect::FILL,
230 RelativeRect::FILL,
231 Size::new(100.0, 100.0),
232 Size::new(200.0, 200.0),
233 );
234
235 assert!(result.needs_intermediate());
237 assert_eq!(Rect::new(0.0, 0.0, 100.0, 100.0), result.source_rect());
238 assert_eq!(Rect::new(0.0, 0.0, 200.0, 200.0), result.destination_rect());
239 }
240
241 #[test]
242 fn no_tile_uniform_center_horiz() {
243 let result = TileBrushCalculator::new(
244 TileMode::None,
245 Stretch::Uniform,
246 AlignmentX::Center,
247 AlignmentY::Center,
248 RelativeRect::FILL,
249 RelativeRect::FILL,
250 Size::new(100.0, 100.0),
251 Size::new(200.0, 100.0),
252 );
253
254 assert!(result.needs_intermediate());
255 assert_eq!(Rect::new(0.0, 0.0, 100.0, 100.0), result.source_rect());
256 assert_eq!(Rect::new(0.0, 0.0, 200.0, 100.0), result.destination_rect());
257 assert_eq!(Size::new(200.0, 100.0), result.intermediate_size());
258 assert_eq!(Matrix::create_translation(50.0, 0.0), result.intermediate_transform());
259 }
260
261 #[test]
262 fn no_tile_uniform_center_vert() {
263 let result = TileBrushCalculator::new(
264 TileMode::None,
265 Stretch::Uniform,
266 AlignmentX::Center,
267 AlignmentY::Center,
268 RelativeRect::FILL,
269 RelativeRect::FILL,
270 Size::new(100.0, 100.0),
271 Size::new(100.0, 200.0),
272 );
273
274 assert!(result.needs_intermediate());
275 assert_eq!(Rect::new(0.0, 0.0, 100.0, 100.0), result.source_rect());
276 assert_eq!(Rect::new(0.0, 0.0, 100.0, 200.0), result.destination_rect());
277 assert_eq!(Size::new(100.0, 200.0), result.intermediate_size());
278 assert_eq!(Matrix::create_translation(0.0, 50.0), result.intermediate_transform());
279 }
280
281 #[test]
282 fn no_tile_no_stretch_bottom_right_quarter_dest() {
283 let result = TileBrushCalculator::new(
284 TileMode::None,
285 Stretch::None,
286 AlignmentX::Center,
287 AlignmentY::Center,
288 RelativeRect::FILL,
289 RelativeRect::new(0.5, 0.5, 0.5, 0.5, RelativeUnit::Relative),
290 Size::new(800.0, 800.0),
291 Size::new(400.0, 400.0),
292 );
293
294 assert!(result.needs_intermediate());
295 assert_eq!(Rect::new(0.0, 0.0, 800.0, 800.0), result.source_rect());
296 assert_eq!(Rect::new(200.0, 200.0, 200.0, 200.0), result.destination_rect());
297 assert_eq!(Size::new(400.0, 400.0), result.intermediate_size());
298 assert_eq!(Rect::new(200.0, 200.0, 200.0, 200.0), result.intermediate_clip());
299 assert_eq!(Matrix::create_translation(-100.0, -100.0), result.intermediate_transform());
300 }
301
302 #[test]
303 fn tile_no_stretch_bottom_right_quarter_source_center_quarter_dest() {
304 let result = TileBrushCalculator::new(
305 TileMode::Tile,
306 Stretch::None,
307 AlignmentX::Center,
308 AlignmentY::Center,
309 RelativeRect::new(0.5, 0.5, 0.5, 0.5, RelativeUnit::Relative),
310 RelativeRect::new(0.25, 0.25, 0.5, 0.5, RelativeUnit::Relative),
311 Size::new(800.0, 800.0),
312 Size::new(400.0, 400.0),
313 );
314
315 assert!(result.needs_intermediate());
316 assert_eq!(Rect::new(400.0, 400.0, 400.0, 400.0), result.source_rect());
317 assert_eq!(Rect::new(100.0, 100.0, 200.0, 200.0), result.destination_rect());
318 assert_eq!(Size::new(200.0, 200.0), result.intermediate_size());
319 assert_eq!(Rect::new(0.0, 0.0, 200.0, 200.0), result.intermediate_clip());
320 assert_eq!(Matrix::create_translation(-500.0, -500.0), result.intermediate_transform());
321 }
322
323 #[test]
326 fn calculate_translate_follows_the_alignment() {
327 let source = Size::new(40.0, 20.0);
328 let destination = Size::new(100.0, 100.0);
329
330 for (alignment_x, alignment_y, expected) in [
331 (AlignmentX::Left, AlignmentY::Top, Vector::new(0.0, 0.0)),
332 (AlignmentX::Center, AlignmentY::Center, Vector::new(30.0, 40.0)),
333 (AlignmentX::Right, AlignmentY::Bottom, Vector::new(60.0, 80.0)),
334 ] {
335 assert_eq!(
336 expected,
337 TileBrushCalculator::calculate_translate_sizes(alignment_x, alignment_y, source, destination)
338 );
339 }
340
341 assert_eq!(
342 Vector::new(10.0, 60.0),
343 TileBrushCalculator::calculate_translate(
344 AlignmentX::Center,
345 AlignmentY::Bottom,
346 Rect::new(5.0, 5.0, 40.0, 20.0),
347 Rect::new(7.0, 7.0, 100.0, 100.0),
348 Vector::new(2.0, 2.0),
349 )
350 );
351 }
352
353 #[test]
354 fn calculate_intermediate_transform_offsets_by_destination_only_when_not_tiled() {
355 let source = Rect::new(10.0, 20.0, 50.0, 50.0);
356 let destination = Rect::new(5.0, 6.0, 100.0, 100.0);
357 let scale = Vector::new(2.0, 2.0);
358 let translate = Vector::new(1.0, 2.0);
359
360 let (transform, draw_rect) =
361 TileBrushCalculator::calculate_intermediate_transform(TileMode::None, source, destination, scale, translate);
362 assert_eq!(Matrix::new(2.0, 0.0, 0.0, 2.0, -20.0 + 1.0 + 5.0, -40.0 + 2.0 + 6.0), transform);
363 assert_eq!(destination, draw_rect);
364
365 let (transform, draw_rect) =
366 TileBrushCalculator::calculate_intermediate_transform(TileMode::FlipXY, source, destination, scale, translate);
367 assert_eq!(Matrix::new(2.0, 0.0, 0.0, 2.0, -19.0, -38.0), transform);
368 assert_eq!(Rect::new(0.0, 0.0, 100.0, 100.0), draw_rect);
369 }
370
371 #[test]
372 fn is_valid_is_never_set() {
373 let result = TileBrushCalculator::new(
374 TileMode::None,
375 Stretch::Fill,
376 AlignmentX::Center,
377 AlignmentY::Center,
378 RelativeRect::FILL,
379 RelativeRect::FILL,
380 Size::new(100.0, 100.0),
381 Size::new(100.0, 100.0),
382 );
383 assert!(!result.is_valid());
384 }
385
386 #[test]
388 fn from_brush_uses_the_parameters_of_the_brush() {
389 let brush = crate::media::ImageBrush::new();
390 brush.set_tile_mode(TileMode::Tile);
391 brush.set_stretch(Stretch::Fill);
392 brush.set_destination_rect(RelativeRect::new(0.0, 0.0, 0.5, 0.5, RelativeUnit::Relative));
393 let handle: std::rc::Rc<dyn crate::media::IBrush> = brush.into();
394
395 let result =
396 TileBrushCalculator::from_brush(handle.as_tile_brush().unwrap(), Size::new(10.0, 10.0), Size::new(40.0, 40.0));
397 let expected = TileBrushCalculator::new(
398 TileMode::Tile,
399 Stretch::Fill,
400 AlignmentX::Center,
401 AlignmentY::Center,
402 RelativeRect::FILL,
403 RelativeRect::new(0.0, 0.0, 0.5, 0.5, RelativeUnit::Relative),
404 Size::new(10.0, 10.0),
405 Size::new(40.0, 40.0),
406 );
407
408 assert_eq!(Rect::new(0.0, 0.0, 20.0, 20.0), result.destination_rect());
409 assert_eq!(expected.destination_rect(), result.destination_rect());
410 assert_eq!(expected.intermediate_size(), result.intermediate_size());
411 assert_eq!(expected.intermediate_transform(), result.intermediate_transform());
412 assert_eq!(expected.source_rect(), result.source_rect());
413 }
414}