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