Skip to main content

affine/
transform.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::ratio::{Exact, Ratio, Round};
6use zr::static_assert;
7
8pub struct Saturate;
9#[allow(non_upper_case_globals)]
10impl Saturate {
11    pub const No: bool = false;
12    pub const Yes: bool = true;
13}
14
15/// A small helper struct which represents a 1 dimensional affine transformation
16/// from a signed 64 bit space A, to a signed 64 bit space B.  Conceptually, this
17/// is the function...
18///
19/// f(a) = b = (a * scale) + offset
20///
21/// Internally, however, the exact function used is
22///
23/// f(a) = b = (((a - A_offset) * B_scale) / A_scale) + B_offset
24///
25/// Where the offsets involved are 64 bit signed integers, and the scale factors
26/// are 32 bit unsigned integers.
27///
28/// Overflow/Underflow saturation behavior is as follows.
29/// The transformation operation is divided into three stages.
30///
31/// 1) Offset by A_offset
32/// 2) Scale by (B_scale / A_scale)
33/// 3) Offset by B_offset
34///
35/// Each stage is saturated independently.  That is to say, if the result of
36/// stage #1 is clamped at int64::min, this is the input value which will be fed
37/// into stage #2.  The calculations are *not* done with infinite precision and
38/// then clamped at the end.
39///
40/// TODO(johngro): Reconsider this.  Clamping at intermediate stages can make it
41/// more difficult to understand that saturation happened at all, and might be
42/// important to a client.  It may be better to either signal explicitly that
43/// this happened, or to extend the precision of the operation in the rare slow
44/// path so that saturation behavior occurs only at the end of the op, and
45/// produces a correct result if the transform would have saturated at an
46/// intermediate step, but got brought back into range by a subsequent operation.
47///
48/// Saturation is enabled by default, but may be disabled by choosing the
49/// Saturate::No form of Apply/ApplyInverse.  When saturation behavior is
50/// disabled, the results of a transformation where over/underflow occurs at any
51/// stage is undefined.
52#[repr(C)]
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub struct Transform {
55    a_offset: i64,
56    b_offset: i64,
57    ratio: Ratio,
58}
59
60static_assert!(core::mem::size_of::<Transform>() == 24);
61static_assert!(core::mem::align_of::<Transform>() == 8);
62
63// Transform::default() produces the identity transform
64impl Default for Transform {
65    fn default() -> Self {
66        Transform { a_offset: 0, b_offset: 0, ratio: Ratio::default() }
67    }
68}
69
70// TODO(https://fxbug.dev/42082948)
71impl Transform {
72    /// Constructs a new Transform.
73    pub fn new(a_offset: i64, b_offset: i64, ratio: Ratio) -> Self {
74        Transform { a_offset, b_offset, ratio }
75    }
76
77    // Construct a linear transformation (zero offsets) from a ratio
78    pub fn new_linear(ratio: Ratio) -> Self {
79        Transform { a_offset: 0, b_offset: 0, ratio }
80    }
81
82    pub fn invertible(&self) -> bool {
83        self.ratio.invertible()
84    }
85
86    pub fn a_offset(&self) -> i64 {
87        self.a_offset
88    }
89
90    pub fn b_offset(&self) -> i64 {
91        self.b_offset
92    }
93
94    pub fn ratio(&self) -> Ratio {
95        self.ratio
96    }
97
98    pub fn numerator(&self) -> u32 {
99        self.ratio.numerator()
100    }
101
102    pub fn denominator(&self) -> u32 {
103        self.ratio.denominator()
104    }
105
106    // Construct and return a transform which is the inverse of this transform.
107    pub fn inverse(&self) -> Self {
108        Transform { a_offset: self.b_offset, b_offset: self.a_offset, ratio: self.ratio.inverse() }
109    }
110
111    // Applies a transformation from A -> B
112    pub fn apply_static<const SATURATE: bool>(
113        a_offset: i64,
114        b_offset: i64,
115        ratio: Ratio,
116        val: i64,
117    ) -> i64 {
118        if SATURATE {
119            let sub = val.saturating_sub(a_offset);
120            let scaled = ratio.scale::<{ Round::DOWN }>(sub);
121            scaled.saturating_add(b_offset)
122        } else {
123            // TODO(johngro): the multiplication by the ratio operation here
124            // actually implements saturation behavior.  If we want this
125            // operation to actually perform no saturation checks at all, we
126            // need to make a Saturate::No version of Ratio::Scale.
127            let sub = val.wrapping_sub(a_offset);
128            let scaled = ratio.scale::<{ Round::DOWN }>(sub);
129            scaled.wrapping_add(b_offset)
130        }
131    }
132
133    // Applies the inverse transformation B -> A
134    pub fn apply_inverse_static<const SATURATE: bool>(
135        a_offset: i64,
136        b_offset: i64,
137        ratio: Ratio,
138        val: i64,
139    ) -> i64 {
140        Self::apply_static::<SATURATE>(b_offset, a_offset, ratio.inverse(), val)
141    }
142
143    // Applies the transformation
144    pub fn apply<const SATURATE: bool>(&self, val: i64) -> i64 {
145        Self::apply_static::<SATURATE>(self.a_offset, self.b_offset, self.ratio, val)
146    }
147
148    // Applies the inverse transformation
149    pub fn apply_inverse<const SATURATE: bool>(&self, val: i64) -> i64 {
150        debug_assert!(self.ratio.denominator() != 0);
151        Self::apply_inverse_static::<SATURATE>(self.a_offset, self.b_offset, self.ratio, val)
152    }
153
154    // Composes two timeline functions B->C and A->B producing A->C. If exact is
155    // Exact::Yes, asserts on loss of precision.
156    //
157    // During composition, the saturation behavior is as follows
158    //
159    // 1) The intermediate offset (bc.a_offset - ab.b_offset) will be saturated
160    //    before distribution to the offsets ac.
161    // 2) Both offsets of ac will be saturated as ab.a_offset and bc.b_offset
162    //    are combined with the distributed intermediate offset.
163    pub fn compose(bc: &Transform, ab: &Transform, exact: Exact) -> Transform {
164        Transform {
165            a_offset: ab.a_offset,
166            b_offset: bc.apply::<{ Saturate::Yes }>(ab.b_offset),
167            ratio: Ratio::product(ab.ratio, bc.ratio, exact),
168        }
169    }
170}
171
172// Operators
173
174/// Composes two timeline functions B->C and A->B producing A->C.
175///
176/// Panics on loss of precision.
177impl core::ops::Mul for Transform {
178    type Output = Self;
179    fn mul(self, rhs: Self) -> Self::Output {
180        Transform::compose(&self, &rhs, Exact::Yes)
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187
188    #[test]
189    fn test_construction() {
190        let t = Transform::default();
191        assert_eq!(t.a_offset(), 0);
192        assert_eq!(t.b_offset(), 0);
193        assert_eq!(t.numerator(), 1);
194        assert_eq!(t.denominator(), 1);
195
196        struct TestVector {
197            a_offset: i64,
198            b_offset: i64,
199            n: u32,
200            d: u32,
201        }
202
203        let valid_vectors = [
204            TestVector { a_offset: 12345, b_offset: 98764, n: 3, d: 2 },
205            TestVector { a_offset: -12345, b_offset: 98764, n: 247, d: 931 },
206            TestVector { a_offset: -12345, b_offset: -98764, n: 48000, d: 44100 },
207            TestVector { a_offset: 12345, b_offset: -98764, n: 1000007, d: 1000000 },
208            TestVector { a_offset: 12345, b_offset: 98764, n: 0, d: 1000000 },
209        ];
210
211        for v in &valid_vectors {
212            let ratio = Ratio::new(v.n, v.d);
213
214            let t_linear = Transform::new_linear(ratio);
215            assert_eq!(t_linear.a_offset(), 0);
216            assert_eq!(t_linear.b_offset(), 0);
217            assert_eq!(t_linear.numerator(), ratio.numerator());
218            assert_eq!(t_linear.denominator(), ratio.denominator());
219
220            let t_affine = Transform::new(v.a_offset, v.b_offset, ratio);
221            assert_eq!(t_affine.a_offset(), v.a_offset);
222            assert_eq!(t_affine.b_offset(), v.b_offset);
223            assert_eq!(t_affine.numerator(), ratio.numerator());
224            assert_eq!(t_affine.denominator(), ratio.denominator());
225        }
226    }
227
228    #[test]
229    fn test_inverse() {
230        struct TestVector {
231            a_offset: i64,
232            b_offset: i64,
233            n: u32,
234            d: u32,
235        }
236
237        let test_vectors = [
238            TestVector { a_offset: 12345, b_offset: 98764, n: 3, d: 2 },
239            TestVector { a_offset: -12345, b_offset: 98764, n: 247, d: 931 },
240            TestVector { a_offset: -12345, b_offset: -98764, n: 48000, d: 44100 },
241            TestVector { a_offset: 12345, b_offset: -98764, n: 1000007, d: 1000000 },
242        ];
243
244        for v in &test_vectors {
245            let ratio = Ratio::new(v.n, v.d);
246            let t = Transform::new(v.a_offset, v.b_offset, ratio);
247
248            if t.invertible() {
249                let res = t.inverse();
250                assert_eq!(t.a_offset(), res.b_offset());
251                assert_eq!(t.b_offset(), res.a_offset());
252                assert_eq!(t.numerator(), res.denominator());
253                assert_eq!(t.denominator(), res.numerator());
254                assert_eq!(t.ratio().inverse().numerator(), res.ratio().numerator());
255                assert_eq!(t.ratio().inverse().denominator(), res.ratio().denominator());
256            }
257        }
258
259        let t_non_inv = Transform::new(12345, 98764, Ratio::new(0, 1000000));
260        assert!(!t_non_inv.invertible());
261    }
262
263    #[test]
264    fn test_apply() {
265        struct TestVector {
266            a_offset: i64,
267            b_offset: i64,
268            n: u32,
269            d: u32,
270            val: i64,
271            expected: i64,
272            expect_ovfl: bool,
273        }
274
275        let test_vectors = [
276            TestVector {
277                a_offset: 0,
278                b_offset: 0,
279                n: 1,
280                d: 1,
281                val: 12345,
282                expected: 12345,
283                expect_ovfl: false,
284            },
285            TestVector {
286                a_offset: 50,
287                b_offset: 0,
288                n: 1,
289                d: 1,
290                val: 12345,
291                expected: 12295,
292                expect_ovfl: false,
293            },
294            TestVector {
295                a_offset: 0,
296                b_offset: -50,
297                n: 1,
298                d: 1,
299                val: 12345,
300                expected: 12295,
301                expect_ovfl: false,
302            },
303            TestVector {
304                a_offset: 50,
305                b_offset: -50,
306                n: 1,
307                d: 1,
308                val: 12345,
309                expected: 12245,
310                expect_ovfl: false,
311            },
312            TestVector {
313                a_offset: 50,
314                b_offset: 50,
315                n: 1,
316                d: 1,
317                val: 12345,
318                expected: 12345,
319                expect_ovfl: false,
320            },
321            TestVector {
322                a_offset: 0,
323                b_offset: 0,
324                n: 48000,
325                d: 44100,
326                val: 12345,
327                expected: 13436,
328                expect_ovfl: false,
329            },
330            TestVector {
331                a_offset: 50,
332                b_offset: 0,
333                n: 48000,
334                d: 44100,
335                val: 12345,
336                expected: 13382,
337                expect_ovfl: false,
338            },
339            TestVector {
340                a_offset: 0,
341                b_offset: -54,
342                n: 48000,
343                d: 44100,
344                val: 12345,
345                expected: 13382,
346                expect_ovfl: false,
347            },
348            TestVector {
349                a_offset: 50,
350                b_offset: -54,
351                n: 48000,
352                d: 44100,
353                val: 12345,
354                expected: 13328,
355                expect_ovfl: false,
356            },
357            TestVector {
358                a_offset: 50,
359                b_offset: 54,
360                n: 48000,
361                d: 44100,
362                val: 12345,
363                expected: 13436,
364                expect_ovfl: false,
365            },
366            TestVector {
367                a_offset: -100,
368                b_offset: -17,
369                n: 1,
370                d: 1,
371                val: i64::MAX - 1,
372                expected: i64::MAX - 17,
373                expect_ovfl: true,
374            },
375            TestVector {
376                a_offset: 100,
377                b_offset: 17,
378                n: 1,
379                d: 1,
380                val: i64::MIN + 1,
381                expected: i64::MIN + 17,
382                expect_ovfl: true,
383            },
384            TestVector {
385                a_offset: 0,
386                b_offset: -17,
387                n: 3,
388                d: 1,
389                val: i64::MAX / 2,
390                expected: i64::MAX - 17,
391                expect_ovfl: true,
392            },
393            TestVector {
394                a_offset: 0,
395                b_offset: 17,
396                n: 3,
397                d: 1,
398                val: i64::MIN / 2,
399                expected: i64::MIN + 17,
400                expect_ovfl: true,
401            },
402            TestVector {
403                a_offset: 0,
404                b_offset: 17,
405                n: 1,
406                d: 1,
407                val: i64::MAX - 10,
408                expected: i64::MAX,
409                expect_ovfl: true,
410            },
411            TestVector {
412                a_offset: 0,
413                b_offset: -17,
414                n: 1,
415                d: 1,
416                val: i64::MIN + 10,
417                expected: i64::MIN,
418                expect_ovfl: true,
419            },
420        ];
421
422        for v in &test_vectors {
423            let t = Transform::new(v.a_offset, v.b_offset, Ratio::new(v.n, v.d));
424
425            let res_sat_static = Transform::apply_static::<{ Saturate::Yes }>(
426                t.a_offset(),
427                t.b_offset(),
428                t.ratio(),
429                v.val,
430            );
431            assert_eq!(res_sat_static, v.expected);
432
433            if !v.expect_ovfl {
434                let res_nosat_static = Transform::apply_static::<{ Saturate::No }>(
435                    t.a_offset(),
436                    t.b_offset(),
437                    t.ratio(),
438                    v.val,
439                );
440                assert_eq!(res_nosat_static, v.expected);
441            }
442
443            let res_sat_obj = t.apply::<{ Saturate::Yes }>(v.val);
444            assert_eq!(res_sat_obj, v.expected);
445
446            if !v.expect_ovfl {
447                let res_nosat_obj = t.apply::<{ Saturate::No }>(v.val);
448                assert_eq!(res_nosat_obj, v.expected);
449            }
450
451            if t.invertible() {
452                let t_inv = t.inverse();
453
454                let res_sat_inv_static = Transform::apply_inverse_static::<{ Saturate::Yes }>(
455                    t_inv.a_offset(),
456                    t_inv.b_offset(),
457                    t_inv.ratio(),
458                    v.val,
459                );
460                assert_eq!(res_sat_inv_static, v.expected);
461
462                if !v.expect_ovfl {
463                    let res_nosat_inv_static = Transform::apply_inverse_static::<{ Saturate::No }>(
464                        t_inv.a_offset(),
465                        t_inv.b_offset(),
466                        t_inv.ratio(),
467                        v.val,
468                    );
469                    assert_eq!(res_nosat_inv_static, v.expected);
470                }
471
472                let res_sat_inv_obj = t_inv.apply_inverse::<{ Saturate::Yes }>(v.val);
473                assert_eq!(res_sat_inv_obj, v.expected);
474
475                if !v.expect_ovfl {
476                    let res_nosat_inv_obj = t_inv.apply_inverse::<{ Saturate::No }>(v.val);
477                    assert_eq!(res_nosat_inv_obj, v.expected);
478                }
479            }
480        }
481    }
482
483    #[test]
484    fn test_compose() {
485        struct TestVector {
486            ab: Transform,
487            bc: Transform,
488            ac: Transform,
489            is_exact: Exact,
490        }
491
492        let test_vectors = [
493            TestVector {
494                ab: Transform::new(0, 0, Ratio::new(1, 1)),
495                bc: Transform::new(0, 0, Ratio::new(1, 1)),
496                ac: Transform::new(0, 0, Ratio::new(1, 1)),
497                is_exact: Exact::Yes,
498            },
499            TestVector {
500                ab: Transform::new(0, 0, Ratio::new(1, 1)),
501                bc: Transform::new(12345, 98765, Ratio::new(17, 7)),
502                ac: Transform::new(0, 68784, Ratio::new(17, 7)),
503                is_exact: Exact::Yes,
504            },
505            TestVector {
506                ab: Transform::new(12345, 98765, Ratio::new(17, 7)),
507                bc: Transform::new(0, 0, Ratio::new(1, 1)),
508                ac: Transform::new(12345, 98765, Ratio::new(17, 7)),
509                is_exact: Exact::Yes,
510            },
511            TestVector {
512                ab: Transform::new(34327, 86539, Ratio::new(1000007, 1000000)),
513                bc: Transform::new(728376, -34265, Ratio::new(48000, 44100)),
514                ac: Transform::new(34327, -732864, Ratio::new(1000007, 918750)),
515                is_exact: Exact::Yes,
516            },
517            TestVector {
518                ab: Transform::new(0, i64::MAX - 5, Ratio::new(1, 1)),
519                bc: Transform::new(-100, 0, Ratio::new(1, 1)),
520                ac: Transform::new(0, i64::MAX, Ratio::new(1, 1)),
521                is_exact: Exact::Yes,
522            },
523            TestVector {
524                ab: Transform::new(0, i64::MIN + 5, Ratio::new(1, 1)),
525                bc: Transform::new(100, 0, Ratio::new(1, 1)),
526                ac: Transform::new(0, i64::MIN, Ratio::new(1, 1)),
527                is_exact: Exact::Yes,
528            },
529            TestVector {
530                ab: Transform::new(0, 100, Ratio::new(1, 1)),
531                bc: Transform::new(0, i64::MAX - 5, Ratio::new(1, 1)),
532                ac: Transform::new(0, i64::MAX, Ratio::new(1, 1)),
533                is_exact: Exact::Yes,
534            },
535            TestVector {
536                ab: Transform::new(0, -100, Ratio::new(1, 1)),
537                bc: Transform::new(0, i64::MIN + 5, Ratio::new(1, 1)),
538                ac: Transform::new(0, i64::MIN, Ratio::new(1, 1)),
539                is_exact: Exact::Yes,
540            },
541            TestVector {
542                ab: Transform::new(0, 0, Ratio::new(3465653567, 2327655023)),
543                bc: Transform::new(0, 0, Ratio::new(1291540343, 3698423317)),
544                ac: Transform::new(0, 0, Ratio::new(317609835, 610852072)),
545                is_exact: Exact::No,
546            },
547            TestVector {
548                ab: Transform::new(0, 20, Ratio::new(3465653567, 2327655023)),
549                bc: Transform::new(-3698423317 + 20, 5, Ratio::new(1291540343, 3698423317)),
550                ac: Transform::new(0, 1291540343 + 5, Ratio::new(317609835, 610852072)),
551                is_exact: Exact::No,
552            },
553        ];
554
555        for v in &test_vectors {
556            if v.is_exact == Exact::Yes {
557                let res_static = Transform::compose(&v.bc, &v.ab, Exact::Yes);
558                assert_eq!(res_static, v.ac);
559
560                let res_op = v.bc * v.ab;
561                assert_eq!(res_op, v.ac);
562            }
563
564            let res_inexact = Transform::compose(&v.bc, &v.ab, Exact::No);
565            assert_eq!(res_inexact, v.ac);
566        }
567    }
568}