1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//! For adding frames to the encoder
//!
//! [`gifski::new()`][crate::new] returns the [`Collector`] that collects animation frames,
//! and a [`Writer`][crate::Writer] that performs compression and I/O.

pub use imgref::ImgVec;
pub use rgb::{RGB8, RGBA8};

use crate::error::GifResult;
use crossbeam_channel::Sender;

#[cfg(feature = "png")]
use std::path::PathBuf;

pub(crate) enum FrameSource {
    Pixels(ImgVec<RGBA8>),
    #[cfg(feature = "png")]
    PngData(Vec<u8>),
    #[cfg(all(feature = "png", not(target_arch = "wasm32")))]
    Path(PathBuf),
}

pub(crate) struct InputFrame {
    /// The pixels to resize and encode
    pub frame: FrameSource,
    /// Time in seconds when to display the frame. First frame should start at 0.
    pub presentation_timestamp: f64,
    pub frame_index: usize,
}

pub(crate) struct InputFrameResized {
    /// The pixels to encode
    pub frame: ImgVec<RGBA8>,
    /// The same as above, but with smart blur applied (for denoiser)
    pub frame_blurred: ImgVec<RGB8>,
    /// Time in seconds when to display the frame. First frame should start at 0.
    pub presentation_timestamp: f64,
}

/// Collect frames that will be encoded
///
/// Note that writing will finish only when the collector is dropped.
/// Collect frames on another thread, or call `drop(collector)` before calling `writer.write()`!
pub struct Collector {
    pub(crate) queue: Sender<InputFrame>,
}

impl Collector {
    /// Frame index starts at 0.
    ///
    /// Set each frame (index) only once, but you can set them in any order. However, out-of-order frames
    /// will be buffered in RAM, and big gaps in frame indices will cause high memory usage.
    ///
    /// Presentation timestamp is time in seconds (since file start at 0) when this frame is to be displayed.
    ///
    /// If the first frame doesn't start at pts=0, the delay will be used for the last frame.
    ///
    /// If this function appears to be stuck after a few frames, it's because [`crate::Writer::write()`] is not running.
    #[cfg_attr(debug_assertions, track_caller)]
    pub fn add_frame_rgba(&self, frame_index: usize, frame: ImgVec<RGBA8>, presentation_timestamp: f64) -> GifResult<()> {
        debug_assert!(frame_index == 0 || presentation_timestamp > 0.);
        self.queue.send(InputFrame {
            frame_index,
            frame: FrameSource::Pixels(frame),
            presentation_timestamp,
        })?;
        Ok(())
    }

    /// Decode a frame from in-memory PNG-compressed data.
    ///
    /// Frame index starts at 0.
    /// Set each frame (index) only once, but you can set them in any order. However, out-of-order frames
    /// will be buffered in RAM, and big gaps in frame indices will cause high memory usage.
    ///
    /// Presentation timestamp is time in seconds (since file start at 0) when this frame is to be displayed.
    ///
    /// If the first frame doesn't start at pts=0, the delay will be used for the last frame.
    ///
    /// If this function appears to be stuck after a few frames, it's because [`crate::Writer::write()`] is not running.
    #[cfg(feature = "png")]
    #[inline]
    pub fn add_frame_png_data(&self, frame_index: usize, png_data: Vec<u8>, presentation_timestamp: f64) -> GifResult<()> {
        self.queue.send(InputFrame {
            frame: FrameSource::PngData(png_data),
            presentation_timestamp,
            frame_index,
        })?;
        Ok(())
    }

    /// Read and decode a PNG file from disk.
    ///
    /// Frame index starts at 0.
    /// Set each frame (index) only once, but you can set them in any order.
    ///
    /// Presentation timestamp is time in seconds (since file start at 0) when this frame is to be displayed.
    ///
    /// If the first frame doesn't start at pts=0, the delay will be used for the last frame.
    ///
    /// If this function appears to be stuck after a few frames, it's because [`crate::Writer::write()`] is not running.
    #[cfg(feature = "png")]
    pub fn add_frame_png_file(&self, frame_index: usize, path: PathBuf, presentation_timestamp: f64) -> GifResult<()> {
        self.queue.send(InputFrame {
            frame: FrameSource::Path(path),
            presentation_timestamp,
            frame_index,
        })?;
        Ok(())
    }
}