Coverage Report

Created: 2026-06-10 07:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/image/src/animation.rs
Line
Count
Source
1
use std::cmp::Ordering;
2
use std::time::Duration;
3
4
use crate::error::ImageResult;
5
use crate::RgbaImage;
6
7
/// An implementation dependent iterator, reading the frames as requested
8
pub struct Frames<'a> {
9
    iterator: Box<dyn Iterator<Item = ImageResult<Frame>> + 'a>,
10
}
11
12
impl<'a> Frames<'a> {
13
    /// Creates a new `Frames` from an implementation specific iterator.
14
    #[must_use]
15
0
    pub fn new(iterator: Box<dyn Iterator<Item = ImageResult<Frame>> + 'a>) -> Self {
16
0
        Frames { iterator }
17
0
    }
18
19
    /// Steps through the iterator from the current frame until the end and pushes each frame into
20
    /// a `Vec`.
21
    /// If en error is encountered that error is returned instead.
22
    ///
23
    /// Note: This is equivalent to `Frames::collect::<ImageResult<Vec<Frame>>>()`
24
0
    pub fn collect_frames(self) -> ImageResult<Vec<Frame>> {
25
0
        self.collect()
26
0
    }
27
}
28
29
impl Iterator for Frames<'_> {
30
    type Item = ImageResult<Frame>;
31
32
0
    fn next(&mut self) -> Option<ImageResult<Frame>> {
33
0
        self.iterator.next()
34
0
    }
35
}
36
37
/// A single animation frame
38
#[derive(Clone)]
39
pub struct Frame {
40
    /// Delay between the frames in milliseconds
41
    delay: Delay,
42
    /// x offset
43
    left: u32,
44
    /// y offset
45
    top: u32,
46
    buffer: RgbaImage,
47
}
48
49
/// The delay of a frame relative to the previous one.
50
///
51
/// The ratio is reduced on construction which means equality comparisons is reliable even when
52
/// mixing different bases. Note however that there is an upper limit to the delays that can be
53
/// represented exactly when using [`Self::from_saturating_duration`] which depends on the
54
/// granularity of the interval.
55
///
56
/// ```
57
/// use image::Delay;
58
/// let delay_10ms = Delay::from_numer_denom_ms(10, 1);
59
/// let delay_10000us = Delay::from_numer_denom_ms(10_000, 1_000);
60
///
61
/// assert_eq!(delay_10ms, delay_10000us);
62
/// ```
63
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd)]
64
pub struct Delay {
65
    ratio: Ratio,
66
}
67
68
impl Frame {
69
    /// Constructs a new frame without any delay.
70
    #[must_use]
71
0
    pub fn new(buffer: RgbaImage) -> Frame {
72
0
        Frame {
73
0
            delay: Delay::from_ratio(Ratio { numer: 0, denom: 1 }),
74
0
            left: 0,
75
0
            top: 0,
76
0
            buffer,
77
0
        }
78
0
    }
79
80
    /// Constructs a new frame
81
    #[must_use]
82
0
    pub fn from_parts(buffer: RgbaImage, left: u32, top: u32, delay: Delay) -> Frame {
83
0
        Frame {
84
0
            delay,
85
0
            left,
86
0
            top,
87
0
            buffer,
88
0
        }
89
0
    }
90
91
    /// Delay of this frame
92
    #[must_use]
93
0
    pub fn delay(&self) -> Delay {
94
0
        self.delay
95
0
    }
96
97
    /// Returns the image buffer
98
    #[must_use]
99
0
    pub fn buffer(&self) -> &RgbaImage {
100
0
        &self.buffer
101
0
    }
102
103
    /// Returns a mutable image buffer
104
0
    pub fn buffer_mut(&mut self) -> &mut RgbaImage {
105
0
        &mut self.buffer
106
0
    }
107
108
    /// Returns the image buffer
109
    #[must_use]
110
0
    pub fn into_buffer(self) -> RgbaImage {
111
0
        self.buffer
112
0
    }
113
114
    /// Returns the x offset
115
    #[must_use]
116
0
    pub fn left(&self) -> u32 {
117
0
        self.left
118
0
    }
119
120
    /// Returns the y offset
121
    #[must_use]
122
0
    pub fn top(&self) -> u32 {
123
0
        self.top
124
0
    }
125
}
126
127
impl Delay {
128
    /// Create a delay from a ratio of milliseconds.
129
    ///
130
    /// # Examples
131
    ///
132
    /// ```
133
    /// use image::Delay;
134
    /// let delay_10ms = Delay::from_numer_denom_ms(10, 1);
135
    /// ```
136
    #[must_use]
137
160
    pub fn from_numer_denom_ms(numerator: u32, denominator: u32) -> Self {
138
160
        Delay {
139
160
            ratio: Ratio::new(numerator, denominator),
140
160
        }
141
160
    }
142
143
    /// Convert from a duration, clamped between 0 and an implemented defined maximum.
144
    ///
145
    /// The maximum is *at least* `i32::MAX` milliseconds. It should be noted that the accuracy of
146
    /// the result may be relative and very large delays have a coarse resolution.
147
    ///
148
    /// # Examples
149
    ///
150
    /// ```
151
    /// use std::time::Duration;
152
    /// use image::Delay;
153
    ///
154
    /// let duration = Duration::from_millis(20);
155
    /// let delay = Delay::from_saturating_duration(duration);
156
    /// ```
157
    #[must_use]
158
0
    pub fn from_saturating_duration(duration: Duration) -> Self {
159
        // A few notes: The largest number we can represent as a ratio is u32::MAX but we can
160
        // sometimes represent much smaller numbers.
161
        //
162
        // We can represent duration as `millis+a/b` (where a < b, b > 0).
163
        // We must thus bound b with `bĀ·millis + (b-1) <= u32::MAX` or
164
        // > `0 < b <= (u32::MAX + 1)/(millis + 1)`
165
        // Corollary: millis <= u32::MAX
166
167
        const MILLIS_BOUND: u128 = u32::MAX as u128;
168
169
0
        let millis = duration.as_millis().min(MILLIS_BOUND);
170
0
        let submillis = (duration.as_nanos() % 1_000_000) as u32;
171
172
0
        let max_b = if millis > 0 {
173
0
            ((MILLIS_BOUND + 1) / (millis + 1)) as u32
174
        } else {
175
0
            MILLIS_BOUND as u32
176
        };
177
0
        let millis = millis as u32;
178
179
0
        let (a, b) = Self::closest_bounded_fraction(max_b, submillis, 1_000_000);
180
0
        Self::from_numer_denom_ms(a + b * millis, b)
181
0
    }
182
183
    /// The numerator and denominator of the delay in milliseconds.
184
    ///
185
    /// This is guaranteed to be an exact conversion if the `Delay` was previously created with the
186
    /// `from_numer_denom_ms` constructor.
187
    #[must_use]
188
0
    pub fn numer_denom_ms(self) -> (u32, u32) {
189
0
        (self.ratio.numer, self.ratio.denom)
190
0
    }
191
192
2.65k
    pub(crate) fn from_ratio(ratio: Ratio) -> Self {
193
2.65k
        Delay { ratio }
194
2.65k
    }
195
196
0
    pub(crate) fn into_ratio(self) -> Ratio {
197
0
        self.ratio
198
0
    }
199
200
    /// Given some fraction, compute an approximation with denominator bounded.
201
    ///
202
    /// Note that `denom_bound` bounds nominator and denominator of all intermediate
203
    /// approximations and the end result.
204
0
    fn closest_bounded_fraction(denom_bound: u32, nom: u32, denom: u32) -> (u32, u32) {
205
        use std::cmp::Ordering::*;
206
0
        assert!(0 < denom);
207
0
        assert!(0 < denom_bound);
208
0
        assert!(nom < denom);
209
210
        // Avoid a few type troubles. All intermediate results are bounded by `denom_bound` which
211
        // is in turn bounded by u32::MAX. Representing with u64 allows multiplication of any two
212
        // values without fears of overflow.
213
214
        // Compare two fractions whose parts fit into a u32.
215
0
        fn compare_fraction((an, ad): (u64, u64), (bn, bd): (u64, u64)) -> Ordering {
216
0
            (an * bd).cmp(&(bn * ad))
217
0
        }
218
219
        // Computes the nominator of the absolute difference between two such fractions.
220
0
        fn abs_diff_nom((an, ad): (u64, u64), (bn, bd): (u64, u64)) -> u64 {
221
0
            let c0 = an * bd;
222
0
            let c1 = ad * bn;
223
224
0
            let d0 = c0.max(c1);
225
0
            let d1 = c0.min(c1);
226
0
            d0 - d1
227
0
        }
228
229
0
        let exact = (u64::from(nom), u64::from(denom));
230
        // The lower bound fraction, numerator and denominator.
231
0
        let mut lower = (0u64, 1u64);
232
        // The upper bound fraction, numerator and denominator.
233
0
        let mut upper = (1u64, 1u64);
234
        // The closest approximation for now.
235
0
        let mut guess = (u64::from(nom * 2 > denom), 1u64);
236
237
        // loop invariant: ad, bd <= denom_bound
238
        // iterates the Farey sequence.
239
        loop {
240
            // Break if we are done.
241
0
            if compare_fraction(guess, exact) == Equal {
242
0
                break;
243
0
            }
244
245
            // Break if next Farey number is out-of-range.
246
0
            if u64::from(denom_bound) - lower.1 < upper.1 {
247
0
                break;
248
0
            }
249
250
            // Next Farey approximation n between a and b
251
0
            let next = (lower.0 + upper.0, lower.1 + upper.1);
252
            // if F < n then replace the upper bound, else replace lower.
253
0
            if compare_fraction(exact, next) == Less {
254
0
                upper = next;
255
0
            } else {
256
0
                lower = next;
257
0
            }
258
259
            // Now correct the closest guess.
260
            // In other words, if |c - f| > |n - f| then replace it with the new guess.
261
            // This favors the guess with smaller denominator on equality.
262
263
            // |g - f| = |g_diff_nom|/(gd*fd);
264
0
            let g_diff_nom = abs_diff_nom(guess, exact);
265
            // |n - f| = |n_diff_nom|/(nd*fd);
266
0
            let n_diff_nom = abs_diff_nom(next, exact);
267
268
            // The difference |n - f| is smaller than |g - f| if either the integral part of the
269
            // fraction |n_diff_nom|/nd is smaller than the one of |g_diff_nom|/gd or if they are
270
            // the same but the fractional part is larger.
271
0
            if match (n_diff_nom / next.1).cmp(&(g_diff_nom / guess.1)) {
272
0
                Less => true,
273
0
                Greater => false,
274
                // Note that the nominator for the fractional part is smaller than its denominator
275
                // which is smaller than u32 and can't overflow the multiplication with the other
276
                // denominator, that is we can compare these fractions by multiplication with the
277
                // respective other denominator.
278
                Equal => {
279
0
                    compare_fraction(
280
0
                        (n_diff_nom % next.1, next.1),
281
0
                        (g_diff_nom % guess.1, guess.1),
282
0
                    ) == Less
283
                }
284
0
            } {
285
0
                guess = next;
286
0
            }
287
        }
288
289
0
        (guess.0 as u32, guess.1 as u32)
290
0
    }
291
}
292
293
impl From<Delay> for Duration {
294
0
    fn from(delay: Delay) -> Self {
295
0
        let ratio = delay.into_ratio();
296
0
        let ms = ratio.to_integer();
297
0
        let rest = ratio.numer % ratio.denom;
298
0
        let nanos = (u64::from(rest) * 1_000_000) / u64::from(ratio.denom);
299
0
        Duration::from_millis(ms.into()) + Duration::from_nanos(nanos)
300
0
    }
301
}
302
303
#[inline]
304
2.81k
const fn gcd(mut a: u32, mut b: u32) -> u32 {
305
5.63k
    while b != 0 {
306
2.81k
        (a, b) = (b, a.rem_euclid(b));
307
2.81k
    }
308
2.81k
    a
309
2.81k
}
310
311
#[derive(Copy, Clone, Debug)]
312
pub(crate) struct Ratio {
313
    numer: u32,
314
    denom: u32,
315
}
316
317
impl Ratio {
318
    #[inline]
319
2.81k
    pub(crate) fn new(numerator: u32, denominator: u32) -> Self {
320
2.81k
        assert_ne!(denominator, 0);
321
2.81k
        let divisor = gcd(numerator, denominator);
322
2.81k
        Self {
323
2.81k
            numer: numerator / divisor,
324
2.81k
            denom: denominator / divisor,
325
2.81k
        }
326
2.81k
    }
327
328
    #[inline]
329
0
    pub(crate) fn to_integer(self) -> u32 {
330
0
        self.numer / self.denom
331
0
    }
332
}
333
334
impl PartialEq for Ratio {
335
0
    fn eq(&self, other: &Self) -> bool {
336
0
        self.cmp(other) == Ordering::Equal
337
0
    }
338
}
339
340
impl Eq for Ratio {}
341
342
impl PartialOrd for Ratio {
343
0
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
344
0
        Some(self.cmp(other))
345
0
    }
346
}
347
348
impl Ord for Ratio {
349
0
    fn cmp(&self, other: &Self) -> Ordering {
350
        // The following comparison can be simplified:
351
        // a / b <cmp> c / d
352
        // We multiply both sides by `b`:
353
        // a <cmp> c * b / d
354
        // We multiply both sides by `d`:
355
        // a * d <cmp> c * b
356
357
0
        let a: u32 = self.numer;
358
0
        let b: u32 = self.denom;
359
0
        let c: u32 = other.numer;
360
0
        let d: u32 = other.denom;
361
362
        // We cast the types from `u32` to `u64` in order
363
        // to not overflow the multiplications.
364
365
0
        (u64::from(a) * u64::from(d)).cmp(&(u64::from(c) * u64::from(b)))
366
0
    }
367
}
368
369
#[cfg(test)]
370
mod tests {
371
    use super::{Delay, Duration, Ratio};
372
373
    #[test]
374
    fn simple() {
375
        let second = Delay::from_numer_denom_ms(1000, 1);
376
        assert_eq!(Duration::from(second), Duration::from_secs(1));
377
    }
378
379
    #[test]
380
    fn fps_30() {
381
        let thirtieth = Delay::from_numer_denom_ms(1000, 30);
382
        let duration = Duration::from(thirtieth);
383
        assert_eq!(duration.as_secs(), 0);
384
        assert_eq!(duration.subsec_millis(), 33);
385
        assert_eq!(duration.subsec_nanos(), 33_333_333);
386
    }
387
388
    #[test]
389
    fn duration_outlier() {
390
        let oob = Duration::from_secs(0xFFFF_FFFF);
391
        let delay = Delay::from_saturating_duration(oob);
392
        assert_eq!(delay.numer_denom_ms(), (0xFFFF_FFFF, 1));
393
    }
394
395
    #[test]
396
    fn duration_approx() {
397
        let oob = Duration::from_millis(0xFFFF_FFFF) + Duration::from_micros(1);
398
        let delay = Delay::from_saturating_duration(oob);
399
        assert_eq!(delay.numer_denom_ms(), (0xFFFF_FFFF, 1));
400
401
        let inbounds = Duration::from_millis(0xFFFF_FFFF) - Duration::from_micros(1);
402
        let delay = Delay::from_saturating_duration(inbounds);
403
        assert_eq!(delay.numer_denom_ms(), (0xFFFF_FFFF, 1));
404
405
        let fine =
406
            Duration::from_millis(0xFFFF_FFFF / 1000) + Duration::from_micros(0xFFFF_FFFF % 1000);
407
        let delay = Delay::from_saturating_duration(fine);
408
        // Funnily, 0xFFFF_FFFF is divisble by 5, thus we compare with a `Ratio`.
409
        assert_eq!(delay.into_ratio(), Ratio::new(0xFFFF_FFFF, 1000));
410
    }
411
412
    #[test]
413
    fn precise() {
414
        // The ratio has only 32 bits in the numerator, too imprecise to get more than 11 digits
415
        // correct. But it may be expressed as 1_000_000/3 instead.
416
        let exceed = Duration::from_secs(333) + Duration::from_nanos(333_333_333);
417
        let delay = Delay::from_saturating_duration(exceed);
418
        assert_eq!(Duration::from(delay), exceed);
419
    }
420
421
    #[test]
422
    fn small() {
423
        // Not quite a delay of `1 ms`.
424
        let delay = Delay::from_numer_denom_ms(1 << 16, (1 << 16) + 1);
425
        let duration = Duration::from(delay);
426
        assert_eq!(duration.as_millis(), 0);
427
        // Not precisely the original but should be smaller than 0.
428
        let delay = Delay::from_saturating_duration(duration);
429
        assert_eq!(delay.into_ratio().to_integer(), 0);
430
    }
431
}