1use crate::media::transformation::{
2 InterpolationUtilities, OperationType, TransformOperation, TransformParser,
3};
4use crate::media::ITransform;
5use crate::utilities::FormatError;
6use crate::{Matrix, MatrixDecomposed};
7use std::any::Any;
8use std::rc::Rc;
9
10#[derive(Debug)]
13pub struct TransformOperations {
14 operations: Vec<TransformOperation>,
15 is_identity: bool,
16 value: Matrix,
17}
18
19
20impl PartialEq for TransformOperations {
23 #[inline]
24 fn eq(&self, other: &Self) -> bool {
25 std::ptr::eq(self, other)
26 }
27}
28
29impl TransformOperations {
30 pub fn identity() -> Rc<TransformOperations> {
32 crate::ferro_thread_local! {
33 static IDENTITY: Rc<TransformOperations> = Rc::new(TransformOperations::new(Vec::new()));
34 }
35 IDENTITY.with(Rc::clone)
36 }
37
38 fn new(operations: Vec<TransformOperation>) -> Self {
39 let is_identity = operations.iter().all(TransformOperation::is_identity);
40 let value = Self::apply_transforms(&operations, 0);
41 Self { operations, is_identity, value }
42 }
43
44 #[inline]
47 pub fn is_identity(&self) -> bool {
48 self.is_identity
49 }
50
51 #[inline]
53 pub fn operations(&self) -> &[TransformOperation] {
54 &self.operations
55 }
56
57 #[inline]
59 pub fn value(&self) -> Matrix {
60 self.value
61 }
62
63 pub fn parse(s: &str) -> Result<Rc<TransformOperations>, FormatError> {
65 TransformParser::parse(s)
66 }
67
68 pub fn create_builder(capacity: usize) -> TransformOperationsBuilder {
70 TransformOperationsBuilder::new(capacity)
71 }
72
73 pub fn interpolate(
75 from: &Rc<TransformOperations>,
76 to: &Rc<TransformOperations>,
77 progress: f64,
78 ) -> Rc<TransformOperations> {
79 match Self::try_interpolate(from, to, progress) {
80 Some(result) => result,
81 None => {
84 if progress < 0.5 {
85 from.clone()
86 } else {
87 to.clone()
88 }
89 }
90 }
91 }
92
93 fn apply_transforms(operations: &[TransformOperation], start_offset: usize) -> Matrix {
94 let mut matrix = Matrix::IDENTITY;
95 for operation in operations.iter().skip(start_offset) {
96 matrix *= operation.matrix;
97 }
98 matrix
99 }
100
101 fn try_interpolate(
102 from: &TransformOperations,
103 to: &TransformOperations,
104 progress: f64,
105 ) -> Option<Rc<TransformOperations>> {
106 let from_identity = from.is_identity;
107 let to_identity = to.is_identity;
108
109 if from_identity && to_identity {
110 return Some(Self::identity());
111 }
112
113 let matching_prefix_length = Self::compute_matching_prefix_length(from, to);
114 let from_size = if from_identity { 0 } else { from.operations.len() };
115 let to_size = if to_identity { 0 } else { to.operations.len() };
116 let num_operations = from_size.max(to_size);
117
118 let mut builder = TransformOperationsBuilder::new(matching_prefix_length);
119
120 for i in 0..matching_prefix_length {
121 let mut interpolated =
122 TransformOperation { type_: OperationType::Identity, ..TransformOperation::default() };
123
124 if !TransformOperation::try_interpolate(
125 if i >= from_size { None } else { Some(from.operations[i]) },
126 if i >= to_size { None } else { Some(to.operations[i]) },
127 progress,
128 &mut interpolated,
129 ) {
130 return None;
131 }
132
133 builder.append(interpolated);
134 }
135
136 if matching_prefix_length < num_operations {
137 let from_decomposed = Self::compute_decomposed_transform(from, matching_prefix_length)?;
138 let to_decomposed = Self::compute_decomposed_transform(to, matching_prefix_length)?;
139
140 let transform =
141 InterpolationUtilities::interpolate_decomposed_transforms(&from_decomposed, &to_decomposed, progress);
142
143 builder.append_matrix(InterpolationUtilities::compose_transform(transform));
144 }
145
146 Some(builder.build())
147 }
148
149 fn compute_decomposed_transform(operations: &TransformOperations, start_offset: usize) -> Option<MatrixDecomposed> {
150 let transform = Self::apply_transforms(&operations.operations, start_offset);
151 Matrix::try_decompose_transform(transform)
152 }
153
154 fn compute_matching_prefix_length(from: &TransformOperations, to: &TransformOperations) -> usize {
155 let num_operations = from.operations.len().min(to.operations.len());
156
157 for i in 0..num_operations {
158 if from.operations[i].type_ != to.operations[i].type_ {
159 return i;
160 }
161 }
162
163 from.operations.len().max(to.operations.len())
167 }
168}
169
170impl ITransform for TransformOperations {
171 #[inline]
172 fn value(&self) -> Matrix {
173 self.value
174 }
175
176 fn as_any(&self) -> &dyn Any {
177 self
178 }
179}
180
181#[derive(Clone, Debug, Default, PartialEq)]
183pub struct TransformOperationsBuilder {
184 operations: Vec<TransformOperation>,
185}
186
187impl TransformOperationsBuilder {
188 pub fn new(capacity: usize) -> Self {
189 Self { operations: Vec::with_capacity(capacity) }
190 }
191
192 pub fn append_translate(&mut self, x: f64, y: f64) {
193 let mut to_add = TransformOperation { type_: OperationType::Translate, ..TransformOperation::default() };
194 to_add.data.set_translate(x, y);
195 to_add.bake();
196 self.operations.push(to_add);
197 }
198
199 pub fn append_rotate(&mut self, angle: f64) {
200 let mut to_add = TransformOperation { type_: OperationType::Rotate, ..TransformOperation::default() };
201 to_add.data.set_rotate(angle);
202 to_add.bake();
203 self.operations.push(to_add);
204 }
205
206 pub fn append_scale(&mut self, x: f64, y: f64) {
207 let mut to_add = TransformOperation { type_: OperationType::Scale, ..TransformOperation::default() };
208 to_add.data.set_scale(x, y);
209 to_add.bake();
210 self.operations.push(to_add);
211 }
212
213 pub fn append_skew(&mut self, x: f64, y: f64) {
214 let mut to_add = TransformOperation { type_: OperationType::Skew, ..TransformOperation::default() };
215 to_add.data.set_skew(x, y);
216 to_add.bake();
217 self.operations.push(to_add);
218 }
219
220 pub fn append_matrix(&mut self, matrix: Matrix) {
221 self.operations.push(TransformOperation {
222 type_: OperationType::Matrix,
223 matrix,
224 ..TransformOperation::default()
225 });
226 }
227
228 pub fn append_identity(&mut self) {
231 self.operations.push(TransformOperation { type_: OperationType::Identity, ..TransformOperation::default() });
232 }
233
234 pub fn append(&mut self, to_add: TransformOperation) {
235 self.operations.push(to_add);
236 }
237
238 pub fn build(self) -> Rc<TransformOperations> {
239 Rc::new(TransformOperations::new(self.operations))
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246 use crate::media::{TransformGroup, TranslateTransform};
247 use crate::utilities::MathUtilities;
248
249 fn assert_close(expected: f64, actual: f64, precision: i32) {
250 let scale = 10f64.powi(precision);
251 assert_eq!((expected * scale).round(), (actual * scale).round(), "{expected} != {actual}");
252 }
253
254 #[test]
255 fn can_parse_translation() {
256 for (data, x, y) in [
257 ("translate(10px)", 10.0, 0.0),
258 ("translate(10px, 10px)", 10.0, 10.0),
259 ("translate(0px, 10px)", 0.0, 10.0),
260 ("translate(10px, 0px)", 10.0, 0.0),
261 ("translateX(10px)", 10.0, 0.0),
262 ("translateY(10px)", 0.0, 10.0),
263 ] {
264 let transform = TransformOperations::parse(data).unwrap();
265 let operations = transform.operations();
266 assert_eq!(1, operations.len());
267 assert_eq!(OperationType::Translate, operations[0].type_);
268 assert_eq!(x, operations[0].data.translate().x);
269 assert_eq!(y, operations[0].data.translate().y);
270 }
271 }
272
273 #[test]
274 fn can_parse_rotation() {
275 for (data, angle_deg) in [
276 ("rotate(90deg)", 90.0),
277 ("rotate(0.5turn)", 180.0),
278 ("rotate(200grad)", 180.0),
279 ("rotate(3.14159265rad)", 180.0),
280 ] {
281 let transform = TransformOperations::parse(data).unwrap();
282 let operations = transform.operations();
283 assert_eq!(1, operations.len());
284 assert_eq!(OperationType::Rotate, operations[0].type_);
285 assert_close(MathUtilities::deg2rad(angle_deg), operations[0].data.rotate().angle, 4);
286 }
287 }
288
289 #[test]
290 fn can_parse_scale() {
291 for (data, x, y) in [
292 ("scale(10)", 10.0, 10.0),
293 ("scale(10, 10)", 10.0, 10.0),
294 ("scale(0, 10)", 0.0, 10.0),
295 ("scale(10, 0)", 10.0, 0.0),
296 ("scaleX(10)", 10.0, 1.0),
297 ("scaleY(10)", 1.0, 10.0),
298 ] {
299 let transform = TransformOperations::parse(data).unwrap();
300 let operations = transform.operations();
301 assert_eq!(1, operations.len());
302 assert_eq!(OperationType::Scale, operations[0].type_);
303 assert_eq!(x, operations[0].data.scale().x);
304 assert_eq!(y, operations[0].data.scale().y);
305 }
306 }
307
308 #[test]
309 fn can_parse_skew() {
310 for (data, x, y) in [
311 ("skew(90deg)", 90.0, 0.0),
312 ("skew(0.5turn)", 180.0, 0.0),
313 ("skew(200grad)", 180.0, 0.0),
314 ("skew(3.14159265rad)", 180.0, 0.0),
315 ("skewX(90deg)", 90.0, 0.0),
316 ("skewX(0.5turn)", 180.0, 0.0),
317 ("skewX(200grad)", 180.0, 0.0),
318 ("skewX(3.14159265rad)", 180.0, 0.0),
319 ("skew(0, 90deg)", 0.0, 90.0),
320 ("skew(0, 0.5turn)", 0.0, 180.0),
321 ("skew(0, 200grad)", 0.0, 180.0),
322 ("skew(0, 3.14159265rad)", 0.0, 180.0),
323 ("skewY(90deg)", 0.0, 90.0),
324 ("skewY(0.5turn)", 0.0, 180.0),
325 ("skewY(200grad)", 0.0, 180.0),
326 ("skewY(3.14159265rad)", 0.0, 180.0),
327 ("skew(90deg, 90deg)", 90.0, 90.0),
328 ("skew(0.5turn, 0.5turn)", 180.0, 180.0),
329 ("skew(200grad, 200grad)", 180.0, 180.0),
330 ("skew(3.14159265rad, 3.14159265rad)", 180.0, 180.0),
331 ] {
332 let transform = TransformOperations::parse(data).unwrap();
333 let operations = transform.operations();
334 assert_eq!(1, operations.len());
335 assert_eq!(OperationType::Skew, operations[0].type_);
336 assert_close(MathUtilities::deg2rad(x), operations[0].data.skew().x, 4);
337 assert_close(MathUtilities::deg2rad(y), operations[0].data.skew().y, 4);
338 }
339 }
340
341 #[test]
342 fn can_parse_compound_operations() {
343 let data = "scale(1,2) translate(3px,4px) rotate(5deg) skew(6deg,7deg)";
344 let transform = TransformOperations::parse(data).unwrap();
345 let operations = transform.operations();
346
347 assert_eq!(OperationType::Scale, operations[0].type_);
348 assert_eq!(1.0, operations[0].data.scale().x);
349 assert_eq!(2.0, operations[0].data.scale().y);
350
351 assert_eq!(OperationType::Translate, operations[1].type_);
352 assert_eq!(3.0, operations[1].data.translate().x);
353 assert_eq!(4.0, operations[1].data.translate().y);
354
355 assert_eq!(OperationType::Rotate, operations[2].type_);
356 assert_eq!(MathUtilities::deg2rad(5.0), operations[2].data.rotate().angle);
357
358 assert_eq!(OperationType::Skew, operations[3].type_);
359 assert_eq!(MathUtilities::deg2rad(6.0), operations[3].data.skew().x);
360 assert_eq!(MathUtilities::deg2rad(7.0), operations[3].data.skew().y);
361 }
362
363 #[test]
364 fn can_parse_matrix_operation() {
365 let transform = TransformOperations::parse("matrix(1,2,3,4,5,6)").unwrap();
366 let operations = transform.operations();
367 assert_eq!(1, operations.len());
368 assert_eq!(OperationType::Matrix, operations[0].type_);
369 assert_eq!(Matrix::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0), operations[0].matrix);
370 }
371
372 #[test]
373 fn can_interpolate_translation() {
374 for (progress, x, y) in [(0.0, 10.0, 0.0), (0.5, 5.0, 10.0), (1.0, 0.0, 20.0)] {
375 let from = TransformOperations::parse("translateX(10px)").unwrap();
376 let to = TransformOperations::parse("translateY(20px)").unwrap();
377 let interpolated = TransformOperations::interpolate(&from, &to, progress);
378 let operations = interpolated.operations();
379 assert_eq!(1, operations.len());
380 assert_eq!(OperationType::Translate, operations[0].type_);
381 assert_eq!(x, operations[0].data.translate().x);
382 assert_eq!(y, operations[0].data.translate().y);
383 }
384 }
385
386 #[test]
387 fn can_interpolate_scale() {
388 for (progress, x, y) in [(0.0, 10.0, 1.0), (0.5, 5.5, 10.5), (1.0, 1.0, 20.0)] {
389 let from = TransformOperations::parse("scaleX(10)").unwrap();
390 let to = TransformOperations::parse("scaleY(20)").unwrap();
391 let interpolated = TransformOperations::interpolate(&from, &to, progress);
392 let operations = interpolated.operations();
393 assert_eq!(1, operations.len());
394 assert_eq!(OperationType::Scale, operations[0].type_);
395 assert_eq!(x, operations[0].data.scale().x);
396 assert_eq!(y, operations[0].data.scale().y);
397 }
398 }
399
400 #[test]
401 fn can_interpolate_skew() {
402 for (progress, x, y) in [(0.0, 10.0, 0.0), (0.5, 5.0, 10.0), (1.0, 0.0, 20.0)] {
403 let from = TransformOperations::parse("skewX(10deg)").unwrap();
404 let to = TransformOperations::parse("skewY(20deg)").unwrap();
405 let interpolated = TransformOperations::interpolate(&from, &to, progress);
406 let operations = interpolated.operations();
407 assert_eq!(1, operations.len());
408 assert_eq!(OperationType::Skew, operations[0].type_);
409 assert_eq!(MathUtilities::deg2rad(x), operations[0].data.skew().x);
410 assert_eq!(MathUtilities::deg2rad(y), operations[0].data.skew().y);
411 }
412 }
413
414 #[test]
415 fn can_interpolate_rotation() {
416 for (progress, angle) in [(0.0, 10.0), (0.5, 15.0), (1.0, 20.0)] {
417 let from = TransformOperations::parse("rotate(10deg)").unwrap();
418 let to = TransformOperations::parse("rotate(20deg)").unwrap();
419 let interpolated = TransformOperations::interpolate(&from, &to, progress);
420 let operations = interpolated.operations();
421 assert_eq!(1, operations.len());
422 assert_eq!(OperationType::Rotate, operations[0].type_);
423 assert_eq!(MathUtilities::deg2rad(angle), operations[0].data.rotate().angle);
424 }
425 }
426
427 #[test]
428 fn interpolation_fallback_to_matrix() {
429 let from = TransformOperations::parse("rotate(45deg)").unwrap();
430 let to = TransformOperations::parse("translate(100px, 100px) rotate(1215deg)").unwrap();
431 let interpolated = TransformOperations::interpolate(&from, &to, 0.5);
432 let operations = interpolated.operations();
433 assert_eq!(1, operations.len());
434 assert_eq!(OperationType::Matrix, operations[0].type_);
435 }
436
437 fn assert_matrix(matrix: Matrix, scale_x: f64, scale_y: f64, translate_x: f64, translate_y: f64) {
438 let composed = Matrix::try_decompose_transform(matrix).unwrap();
439 assert_eq!(scale_x, composed.scale.x);
440 assert_eq!(scale_y, composed.scale.y);
441 assert_eq!(translate_x, composed.translate.x);
442 assert_eq!(translate_y, composed.translate.y);
443 }
444
445 #[test]
446 fn order_of_operations_is_preserved_no_prefix() {
447 let from = TransformOperations::parse("scale(1)").unwrap();
448 let to = TransformOperations::parse("translate(50px,50px) scale(0.5,0.5)").unwrap();
449
450 let interpolated_0 = TransformOperations::interpolate(&from, &to, 0.0);
451 assert!(interpolated_0.is_identity());
452
453 let interpolated_50 = TransformOperations::interpolate(&from, &to, 0.5);
454 assert_matrix(interpolated_50.value(), 0.75, 0.75, 12.5, 12.5);
455
456 let interpolated_100 = TransformOperations::interpolate(&from, &to, 1.0);
457 assert_matrix(interpolated_100.value(), 0.5, 0.5, 25.0, 25.0);
458 }
459
460 #[test]
461 fn order_of_operations_is_preserved_one_prefix() {
462 let from = TransformOperations::parse("scale(1)").unwrap();
463 let to = TransformOperations::parse("scale(0.5,0.5) translate(50px,50px)").unwrap();
464
465 let interpolated_0 = TransformOperations::interpolate(&from, &to, 0.0);
466 assert!(interpolated_0.is_identity());
467
468 let interpolated_50 = TransformOperations::interpolate(&from, &to, 0.5);
469 assert_matrix(interpolated_50.value(), 0.75, 0.75, 25.0, 25.0);
470
471 let interpolated_100 = TransformOperations::interpolate(&from, &to, 1.0);
472 assert_matrix(interpolated_100.value(), 0.5, 0.5, 50.0, 50.0);
473 }
474
475 #[test]
476 fn transform_group_invalidates_when_child_collection_changes() {
477 let group = TransformGroup::new();
478 let transform = TranslateTransform::with_offset(10.0, 0.0);
479 assert_eq!(Matrix::IDENTITY, group.value());
480 group.children().add(transform.clone().upcast());
481 assert_ne!(Matrix::IDENTITY, group.value());
482 group.children().clear();
483 assert_eq!(Matrix::IDENTITY, group.value());
484 group.set_children(crate::media::Transforms::from_items([transform.upcast()]));
485 assert_ne!(Matrix::IDENTITY, group.value());
486 }
487
488 #[test]
489 fn parse_rejects_invalid_input() {
490 for data in ["", "foo(1)", "rotate(10)", "rotate(10px)", "translate(10deg)", "scaleX(1,2)", "matrix(1,2,3)",
491 "matrix(1,2,3,4,5,6,7)", "scale(1", "scale 1)", "scale(abc)", "rotate(1foo)"]
492 {
493 assert!(TransformOperations::parse(data).is_err(), "{data}");
494 }
495 assert!(TransformOperations::parse(" NONE ").unwrap().is_identity());
496 }
497}