Skip to main content

valo_codec/
backend.rs

1//! The contract a codec library implements to plug into an [`ImageLoader`](crate::ImageLoader).
2use crate::{DecodeError, DecodeOptions, ImageInfo};
3use std::future::Future;
4use std::pin::Pin;
5use std::sync::Arc;
6use std::time::Duration;
7use valo::PixelBuffer;
8
9/// Decoding is an answer a decoder is still working on.
10///
11/// Every answer here is a future because a browser hands back a promise for everything it
12/// decodes; a decoder that already has its answer returns [`std::future::ready`], which costs one
13/// poll. The future is made and driven on the thread that owns the decoder and never leaves it,
14/// so it carries no `Send` bound.
15pub type Decoding<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
16
17/// Decoder is one codec library, such as Apple's ImageIO or the portable `image` crate.
18///
19/// A loader tries its decoders in registration order and the first one that opens the bytes
20/// owns them for every frame. A decoder is a stateless registry shared across threads; the
21/// [`FrameReader`] it opens is per image and stays on the thread that opened it, so a reader may
22/// hold thread-affine platform objects.
23pub trait Decoder: Send + Sync {
24    /// `name` identifies this decoder in diagnostics when every decoder declines an image.
25    fn name(&self) -> &'static str;
26
27    /// `open` reads enough of the bytes to know the image's size and frame count.
28    ///
29    /// Return [`OpenError::Unsupported`] for a format or variant this decoder does not read, so
30    /// the next decoder gets its turn. Anything else stops the search.
31    fn open<'a>(
32        &'a self,
33        request: &'a OpenRequest,
34    ) -> Decoding<'a, Result<Box<dyn FrameReader>, OpenError>>;
35}
36
37/// OpenRequest is everything a decoder needs to open one image.
38#[derive(Clone)]
39pub struct OpenRequest {
40    /// `encoded` is the complete file. It is shared so a reader can keep it without copying.
41    pub encoded: Arc<[u8]>,
42
43    /// `options` bound the decode and say what size the caller wants.
44    pub options: DecodeOptions,
45
46    /// `device` is where a native decoder imports a texture it produced. Software decoders
47    /// ignore it.
48    pub device: wgpu::Device,
49}
50
51/// OpenError is why a decoder did not open an image.
52#[derive(Clone, Debug, PartialEq, Eq)]
53pub enum OpenError {
54    /// Unsupported means this decoder does not read the format or variant; the next decoder is
55    /// tried. The text names what was missing, for diagnostics.
56    Unsupported(String),
57
58    /// Failed means the image was recognised but cannot be decoded, or a limit was hit; no other
59    /// decoder is tried.
60    Failed(DecodeError),
61}
62
63impl From<DecodeError> for OpenError {
64    fn from(error: DecodeError) -> Self {
65        Self::Failed(error)
66    }
67}
68
69/// FrameReader is one opened image, yielding complete composited frames in order.
70///
71/// Animation composition — disposal, blending, partial frames — is the reader's job; every frame
72/// it returns is a full picture. Readers are used from one thread and dropped there.
73pub trait FrameReader {
74    /// `info` is the output size after [`DecodeOptions::max_size`], the frame count and how the
75    /// animation repeats. It is fixed at open and every frame must match it.
76    fn info(&self) -> ImageInfo;
77
78    /// `next_frame` decodes the next frame, wrapping to the first after the last.
79    ///
80    /// One frame is decoded at a time: a reader is asked again only once the answer it gave has
81    /// been waited for.
82    fn next_frame(&mut self) -> Decoding<'_, Result<DecodedFrame, DecodeError>>;
83}
84
85/// DecodedFrame is one complete frame as a decoder produced it, before it becomes an image.
86pub struct DecodedFrame {
87    /// `pixels` is where the frame lives: CPU memory or a texture on the request's device.
88    pub pixels: FramePixels,
89
90    /// `duration` is how long the frame stays on screen; zero for a still image or when the file
91    /// gives no delay.
92    pub duration: Duration,
93}
94
95/// FramePixels is what a decoder hands back — never an image; the loader makes those.
96pub enum FramePixels {
97    /// Cpu is owned samples in any layout [`PixelBuffer`] accepts; the loader uploads them.
98    Cpu(PixelBuffer),
99
100    /// Gpu is a texture already holding the frame on the request's device, so nothing is copied
101    /// back through the CPU. It must be 2D, single-sample, `Rgba8Unorm` or `Bgra8Unorm`, carry
102    /// `TEXTURE_BINDING`, and hold premultiplied sRGB samples. Whatever backs it must stay alive
103    /// until wgpu drops the texture; see `valo::import_metal_texture`.
104    Gpu(wgpu::Texture),
105}
106
107impl DecodedFrame {
108    /// `still` is a frame with no on-screen duration, the shape of every still image.
109    pub fn still(pixels: FramePixels) -> Self {
110        Self {
111            pixels,
112            duration: Duration::ZERO,
113        }
114    }
115}