inset_embedder/image.rs
1//! Flutter counterpart: dart:ui `instantiateImageCodec`, `Codec` and `FrameInfo`.
2//!
3//! Decoding belongs to the host, as it does in Flutter, and for the same reason: a platform's own
4//! codec reads formats no bundled decoder carries and can scale an image while it decodes. What
5//! crosses back is an [`Image`](crate::Image) the renderer can already draw, so the framework
6//! never handles pixels and never learns which codec answered.
7//!
8//! The shape is Dart's: opening bytes answers a codec, and the codec hands out one frame after
9//! another. Both answers are futures, so a host may decode on a worker thread and wake the
10//! framework when it is done, or decode locally. Dropping a future abandons its result; a host
11//! may skip queued work, but an already-running native decode or GPU command can still finish.
12
13use std::fmt::{self, Display};
14use std::future::Future;
15use std::pin::Pin;
16use std::time::Duration;
17
18use crate::Image;
19
20/// A codec being opened. Flutter's `Future<ui.Codec>`.
21pub type ImageCodecFuture =
22 Pin<Box<dyn Future<Output = Result<Box<dyn ImageCodec>, ImageDecodeError>>>>;
23
24/// A frame being decoded. Flutter's `Future<ui.FrameInfo>`.
25///
26/// It borrows the codec it came from until it arrives, which is what keeps one codec to one frame
27/// at a time.
28pub type ImageFrameFuture<'a> =
29 Pin<Box<dyn Future<Output = Result<ImageFrame, ImageDecodeError>> + 'a>>;
30
31/// One encoded image, opened and ready to give up its frames. Flutter dart:ui `Codec`.
32///
33/// A still image is a codec with one frame, so a caller never has to know in advance which it
34/// has. Dropping the codec is what tells the host the image is no longer wanted.
35pub trait ImageCodec {
36 /// How many frames the file holds; one for a still image.
37 fn frame_count(&self) -> u32;
38
39 /// What happens when the last frame has been shown.
40 fn repetition(&self) -> ImageRepetition;
41
42 /// The next frame, wrapping back to the first after the last, as Dart's does.
43 ///
44 /// A caller that waits for a frame holds the codec while it does, so nothing can ask the same
45 /// codec for two frames at once.
46 fn next_frame(&mut self) -> ImageFrameFuture<'_>;
47}
48
49/// One decoded frame, prepared and ready to draw. Flutter dart:ui `FrameInfo`.
50#[derive(Clone, Debug)]
51pub struct ImageFrame {
52 /// The frame itself, at whatever size the host produced.
53 pub image: Image,
54 /// How long this frame stays on screen.
55 ///
56 /// Zero for a still image, and for an animation whose file gives no delay — a caller showing
57 /// one picks its own, as browsers do.
58 pub duration: Duration,
59}
60
61/// How an animation repeats once it has played through.
62///
63/// Dart counts this as an integer, with minus one meaning forever and zero meaning once.
64#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
65pub enum ImageRepetition {
66 /// Play once and stop, which is also what a still image does.
67 #[default]
68 Once,
69 /// Play this many more times after the first pass.
70 Times(u32),
71 /// Never stop, which is what most animated files ask for.
72 Forever,
73}
74
75/// Why an image could not be read. Flutter reports these as an exception on the codec future.
76#[derive(Clone, Debug, PartialEq, Eq)]
77pub enum ImageDecodeError {
78 /// This host has no decoder at all.
79 NoDecoder,
80 /// No decoder here reads this format.
81 UnknownFormat,
82 /// The format was recognised and the bytes are damaged; the text is for a log, not a user.
83 Damaged(String),
84 /// The file decoded to nothing.
85 Empty,
86 /// A host or decoder failed without establishing that the encoded image is damaged.
87 Failed(String),
88}
89
90impl Display for ImageDecodeError {
91 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
92 match self {
93 ImageDecodeError::NoDecoder => formatter.write_str("this host decodes no images"),
94 ImageDecodeError::UnknownFormat => formatter.write_str("unrecognised image format"),
95 ImageDecodeError::Damaged(reason) => write!(formatter, "damaged image: {reason}"),
96 ImageDecodeError::Empty => formatter.write_str("image has no pixels"),
97 ImageDecodeError::Failed(reason) => write!(formatter, "image decode failed: {reason}"),
98 }
99 }
100}
101
102impl std::error::Error for ImageDecodeError {}