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}