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