Skip to main content

valo_codec/
loader.rs

1//! The host-facing API: a loader that decodes bytes, and the codec it opens for animations.
2use crate::service::DecodeService;
3use crate::{DecodeOptions, Decoder, FrameReader, ImageInfo, Pending};
4use std::cell::RefCell;
5use std::rc::Rc;
6use std::sync::Arc;
7use std::time::Duration;
8use valo::{Image, ImageContext};
9
10/// ImageLoader decodes encoded images into drawable [`Image`]s with registered decoders.
11///
12/// Decoders are tried in the order given; put the platform's native decoder first and a software
13/// one after it. The loader is cheap to clone and every clone shares the same decoders and, with
14/// `with_worker` (the `worker` feature), the same worker thread.
15#[derive(Clone)]
16pub struct ImageLoader {
17    service: Arc<DecodeService>,
18    runner: Runner,
19}
20
21#[derive(Clone)]
22enum Runner {
23    Inline,
24    #[cfg(all(feature = "worker", not(target_arch = "wasm32")))]
25    Worker(crate::worker::Worker),
26}
27
28impl ImageLoader {
29    /// `new` decodes on the polling thread: each [`Pending`] does its work inside its first poll.
30    ///
31    /// This is the shape for hosts without threads, such as the web, and for hosts whose executor
32    /// already runs on a background thread. Nothing is spawned.
33    pub fn new(images: ImageContext, decoders: Vec<Box<dyn Decoder>>) -> Self {
34        Self {
35            service: Arc::new(DecodeService::new(images, decoders)),
36            runner: Runner::Inline,
37        }
38    }
39
40    /// `with_worker` decodes and uploads on one dedicated thread, waking the poller when done.
41    ///
42    /// Requests run in order on that thread. Opened codecs live there too, so a decoder may hold
43    /// thread-affine state. The thread exits when the loader and every codec it opened are gone.
44    #[cfg(all(feature = "worker", not(target_arch = "wasm32")))]
45    pub fn with_worker(
46        images: ImageContext,
47        decoders: Vec<Box<dyn Decoder>>,
48    ) -> std::io::Result<Self> {
49        let service = Arc::new(DecodeService::new(images, decoders));
50        let worker = crate::worker::Worker::spawn(service.clone())?;
51        Ok(Self {
52            service,
53            runner: Runner::Worker(worker),
54        })
55    }
56
57    /// `decode` produces the first frame of `encoded` as an image: the still-image path.
58    pub fn decode(&self, encoded: Arc<[u8]>, options: DecodeOptions) -> Pending<Image> {
59        match &self.runner {
60            Runner::Inline => {
61                let service = self.service.clone();
62                Pending::inline(move || service.decode_still(encoded, options))
63            }
64            #[cfg(all(feature = "worker", not(target_arch = "wasm32")))]
65            Runner::Worker(worker) => worker.decode(encoded, options),
66        }
67    }
68
69    /// `open` reads the image's header and returns a [`Codec`] that decodes frames on demand.
70    ///
71    /// Use it when frame count or repetition matter — animations — or to defer the first decode.
72    pub fn open(&self, encoded: Arc<[u8]>, options: DecodeOptions) -> Pending<Codec> {
73        match &self.runner {
74            Runner::Inline => {
75                let service = self.service.clone();
76                Pending::inline(move || {
77                    let reader = service.open(encoded, options)?;
78                    Ok(Codec::inline(service, reader, options.mipmaps))
79                })
80            }
81            #[cfg(all(feature = "worker", not(target_arch = "wasm32")))]
82            Runner::Worker(worker) => worker.open(encoded, options),
83        }
84    }
85}
86
87/// Codec is an opened image whose frames are decoded one at a time, on request.
88///
89/// It is the shape of Flutter's `ui.Codec`: a still image is a codec with one frame, so a caller
90/// need not know in advance which it holds. Dropping the codec releases the decoder's state.
91pub struct Codec {
92    info: ImageInfo,
93    mipmaps: bool,
94    reader: Reader,
95}
96
97enum Reader {
98    Inline {
99        service: Arc<DecodeService>,
100        reader: Rc<RefCell<Box<dyn FrameReader>>>,
101    },
102    #[cfg(all(feature = "worker", not(target_arch = "wasm32")))]
103    Remote(crate::worker::RemoteReader),
104}
105
106impl Codec {
107    fn inline(service: Arc<DecodeService>, reader: Box<dyn FrameReader>, mipmaps: bool) -> Self {
108        Self {
109            info: reader.info(),
110            mipmaps,
111            reader: Reader::Inline {
112                service,
113                reader: Rc::new(RefCell::new(reader)),
114            },
115        }
116    }
117
118    #[cfg(all(feature = "worker", not(target_arch = "wasm32")))]
119    pub(crate) fn remote(
120        info: ImageInfo,
121        mipmaps: bool,
122        reader: crate::worker::RemoteReader,
123    ) -> Self {
124        Self {
125            info,
126            mipmaps,
127            reader: Reader::Remote(reader),
128        }
129    }
130
131    /// `info` is the frame size, frame count and repetition fixed when the image was opened.
132    pub fn info(&self) -> ImageInfo {
133        self.info
134    }
135
136    /// `next_frame` decodes the next frame, wrapping to the first after the last.
137    ///
138    /// The returned future owns what it needs and does not borrow the codec, so it can be held
139    /// across an await. Requests on one codec complete in the order they were made.
140    pub fn next_frame(&self) -> Pending<Frame> {
141        match &self.reader {
142            Reader::Inline { service, reader } => {
143                let service = service.clone();
144                let reader = reader.clone();
145                let mipmaps = self.mipmaps;
146                Pending::inline(move || service.next_frame(reader.borrow_mut().as_mut(), mipmaps))
147            }
148            #[cfg(all(feature = "worker", not(target_arch = "wasm32")))]
149            Reader::Remote(remote) => remote.next_frame(),
150        }
151    }
152}
153
154impl std::fmt::Debug for Codec {
155    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156        f.debug_struct("Codec").field("info", &self.info).finish()
157    }
158}
159
160/// Frame is one decoded frame, ready to draw.
161#[derive(Clone, Debug)]
162pub struct Frame {
163    /// `image` is the frame, at the size the codec's [`ImageInfo`] promised.
164    pub image: Image,
165
166    /// `duration` is how long the frame stays on screen; zero for a still image or when the file
167    /// gives no delay.
168    pub duration: Duration,
169}