Skip to main content

alpha2_extensions/
api.rs

1//! The contract every provider keeps: what probing a file reports, how a
2//! source is asked for a frame, and what it answers.
3
4use std::fmt;
5use std::sync::Arc;
6
7use alpha2_model::{LinearPrimaries, ProviderSource};
8use alpha2_time::{FrameRate, MediaTime};
9use serde::Serialize;
10
11/// The longest delay not taken as meant: a frame declaring this or less
12/// (zero included) is shown for [`NORMALIZED_DELAY_MS`] and reported. A GIF
13/// counts delays in hundredths of a second, so its 0 and 1 are the delays
14/// normalized; APNG and WebP declare frame times exactly, and a longer delay
15/// (60 fps is 16 or 17 ms) plays as declared, in every format alike.
16pub const MAX_NORMALIZED_DELAY_MS: u32 = 10;
17/// What a delay of [`MAX_NORMALIZED_DELAY_MS`] or less becomes.
18pub const NORMALIZED_DELAY_MS: u32 = 100;
19
20/// A format a provider reads.
21#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize)]
22#[serde(rename_all = "snake_case")]
23#[non_exhaustive]
24pub enum Format {
25    /// A static SVG document.
26    Svg,
27    /// A GIF.
28    Gif,
29    /// An APNG.
30    Apng,
31    /// An animated WebP.
32    Webp,
33    /// A numbered PNG sequence.
34    PngSequence,
35    /// A Lottie JSON animation.
36    Lottie,
37    /// A dotLottie package of one or more Lottie animations.
38    DotLottie,
39    /// A still OpenEXR image.
40    Exr,
41    /// A numbered OpenEXR sequence.
42    ExrSequence,
43}
44
45/// When a source's frames are shown, in source time.
46#[derive(Clone, Debug, PartialEq, Serialize)]
47#[serde(tag = "kind", rename_all = "snake_case")]
48#[non_exhaustive]
49pub enum Timing {
50    /// One picture, whatever the time.
51    Static,
52    /// Frames shown for their own durations, in ticks, one after another.
53    Frames {
54        /// Each frame's duration: longer than [`MAX_NORMALIZED_DELAY_MS`], or
55        /// normalized to [`NORMALIZED_DELAY_MS`].
56        durations: Vec<i64>,
57    },
58    /// Frames at a fixed rate.
59    Rate {
60        /// The rate.
61        rate: FrameRate,
62        /// How many frames.
63        frames: usize,
64    },
65}
66
67impl Timing {
68    /// How long one cycle lasts, or `None` for a static picture.
69    #[must_use]
70    pub fn duration(&self) -> Option<MediaTime> {
71        match self {
72            Self::Static => None,
73            Self::Frames { durations } => Some(MediaTime::from_ticks(durations.iter().sum())),
74            Self::Rate { rate, frames } => Some(frame_start(*rate, *frames)),
75        }
76    }
77
78    /// How many frames there are.
79    #[must_use]
80    pub fn frame_count(&self) -> usize {
81        match self {
82            Self::Static => 1,
83            Self::Frames { durations } => durations.len(),
84            Self::Rate { frames, .. } => *frames,
85        }
86    }
87
88    /// The frame shown at `time` and the source time it covers: before the
89    /// first frame the first answers, past the last the last, as a video's
90    /// frames do. Repetition is the clip's, never the source's.
91    #[must_use]
92    pub fn frame_at(&self, time: MediaTime) -> (usize, MediaTime, Option<MediaTime>) {
93        let ticks = time.ticks();
94        match self {
95            Self::Static => (0, MediaTime::from_ticks(i64::MIN), None),
96            Self::Frames { durations } => {
97                let mut start = 0_i64;
98                for (index, duration) in durations.iter().enumerate() {
99                    let end = start + duration;
100                    if ticks < end || index + 1 == durations.len() {
101                        let first = if index == 0 { i64::MIN } else { start };
102                        let last =
103                            (index + 1 < durations.len()).then(|| MediaTime::from_ticks(end));
104                        return (index, MediaTime::from_ticks(first), last);
105                    }
106                    start = end;
107                }
108                (0, MediaTime::from_ticks(i64::MIN), None)
109            }
110            Self::Rate { rate, frames } => {
111                let index = rate
112                    .frame_at_or_before(time)
113                    .map_or(usize::MAX, |index| {
114                        usize::try_from(index.max(0)).unwrap_or(usize::MAX)
115                    })
116                    .min(frames - 1);
117                let first = if index == 0 {
118                    MediaTime::from_ticks(i64::MIN)
119                } else {
120                    frame_start(*rate, index)
121                };
122                let last = (index + 1 < *frames).then(|| frame_start(*rate, index + 1));
123                (index, first, last)
124            }
125        }
126    }
127}
128
129/// When frame `index` starts at `rate`: the engine's frame boundary,
130/// [`FrameRate::frame_start`], the tick its exact start falls in. Output
131/// frames are sampled there, so a source at the project's rate shows frame
132/// `k` at output frame `k`.
133fn frame_start(rate: FrameRate, index: usize) -> MediaTime {
134    i64::try_from(index)
135        .ok()
136        .and_then(|index| rate.frame_start(index))
137        .unwrap_or(MediaTime::from_ticks(i64::MAX))
138}
139
140/// Something a provider did that the user should know about.
141#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
142#[serde(tag = "kind", rename_all = "snake_case")]
143#[non_exhaustive]
144pub enum Diagnostic {
145    /// A frame's delay was [`MAX_NORMALIZED_DELAY_MS`] or less and is shown for
146    /// [`NORMALIZED_DELAY_MS`].
147    NormalizedDelay {
148        /// The frame's index.
149        frame: usize,
150        /// Its declared delay, in milliseconds.
151        declared_ms: u32,
152    },
153    /// Text names a font no registered font provides; it is not drawn.
154    MissingFont {
155        /// The font families the text asks for.
156        families: String,
157    },
158    /// An image the document references is not registered, or declares a
159    /// size larger than a provider opens; it is not drawn.
160    MissingImage {
161        /// The reference.
162        href: String,
163    },
164    /// A feature the provider does not draw, where it first appears and how
165    /// many times it does: it is left out, never approximated silently.
166    UnsupportedFeature {
167        /// What it is.
168        feature: String,
169        /// Where it first appears (a JSON pointer).
170        path: String,
171        /// How many places use it.
172        count: usize,
173    },
174    /// An image the document references is registered but cannot be drawn
175    /// (a format an SVG image is not drawn from, or an SVG that does not
176    /// parse), and why; it is not drawn.
177    UnreadableImage {
178        /// The reference.
179        href: String,
180        /// Why it cannot be drawn.
181        reason: String,
182    },
183    /// An item of a package that does not open, and why; the package's
184    /// others can still be chosen.
185    UnreadableItem {
186        /// The item.
187        item: String,
188        /// Why it does not open.
189        reason: String,
190    },
191    /// Samples that were NaN or infinite; each is shown as `0`.
192    NonFiniteSamples {
193        /// How many.
194        count: usize,
195    },
196    /// Finite 32-bit float samples beyond the largest half float; each is
197    /// shown as the largest half float of its sign.
198    OutOfHalfRange {
199        /// How many.
200        count: usize,
201    },
202    /// Channels stored as 32-bit floats are shown at half-float precision,
203    /// about three decimal digits.
204    ReducedPrecision,
205}
206
207/// What linear-light media's RGB is: the primaries it is in, and where that
208/// was learned.
209#[derive(Clone, Debug, PartialEq, Serialize)]
210#[non_exhaustive]
211pub struct LinearColor {
212    /// The named primaries the chromaticities are, if any.
213    pub primaries: Option<LinearPrimaries>,
214    /// The red, green, blue, and white chromaticities, CIE 1931 `[x, y]`.
215    pub chromaticities: [[f32; 2]; 4],
216    /// Where they come from.
217    pub origin: ColorOrigin,
218}
219
220/// Where a source's color description comes from.
221#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize)]
222#[serde(rename_all = "snake_case")]
223pub enum ColorOrigin {
224    /// The file declares it.
225    File,
226    /// The file does not say and the format defines a default.
227    FormatDefault,
228    /// The project says so, overriding the file.
229    Project,
230}
231
232/// What probing a file finds.
233#[derive(Clone, Debug, PartialEq, Serialize)]
234#[non_exhaustive]
235pub struct ProbeReport {
236    /// The format.
237    pub format: Format,
238    /// The canvas width, in pixels.
239    pub width: u32,
240    /// The canvas height, in pixels.
241    pub height: u32,
242    /// When its frames are shown.
243    pub timing: Timing,
244    /// How many times the file asks to be played, `0` for ever (a GIF's
245    /// repetitions plus its first play; once without a loop extension);
246    /// `None` when it does not say. The clip decides; this is a hint for
247    /// import.
248    pub loop_count: Option<u32>,
249    /// The items a package holds, such as a dotLottie's animations. Empty
250    /// for a file of one item.
251    pub items: Vec<String>,
252    /// The item of a package the report describes: the one a source opened
253    /// shows, or for a probe the first that opens (the others that do not
254    /// are reported). `None` for a file of one item.
255    pub item: Option<String>,
256    /// For linear-light media, what its RGB is; `None` for sRGB media.
257    pub color: Option<LinearColor>,
258    /// What the user should know.
259    pub diagnostics: Vec<Diagnostic>,
260}
261
262/// A frame's pixels, rows packed without padding, in the precision its
263/// source has. Shared, never copied, with what the source keeps: a frame a
264/// source answers again (a still, a frame it holds) costs no copy.
265#[derive(Clone, Debug, PartialEq, Eq)]
266#[non_exhaustive]
267pub enum FramePixels {
268    /// Premultiplied, sRGB-encoded RGBA8.
269    Rgba8(Arc<[u8]>),
270    /// Premultiplied linear-light RGBA as IEEE 754 half floats (their bits),
271    /// in BT.709 primaries with `1.0` at SDR white; values above `1.0` and
272    /// below `0.0` are kept.
273    LinearF16(Arc<[u16]>),
274}
275
276impl FramePixels {
277    /// The bytes the pixels take.
278    #[must_use]
279    pub fn byte_len(&self) -> usize {
280        match self {
281            Self::Rgba8(pixels) => pixels.len(),
282            Self::LinearF16(halves) => halves.len() * 2,
283        }
284    }
285
286    /// The RGBA8 pixels, or `None` for another precision.
287    #[must_use]
288    pub fn rgba8(&self) -> Option<&[u8]> {
289        match self {
290            Self::Rgba8(pixels) => Some(pixels),
291            Self::LinearF16(_) => None,
292        }
293    }
294
295    /// The linear half-float pixels, or `None` for another precision.
296    #[must_use]
297    pub fn linear_f16(&self) -> Option<&[u16]> {
298        match self {
299            Self::LinearF16(halves) => Some(halves),
300            Self::Rgba8(_) => None,
301        }
302    }
303}
304
305/// One frame of a source.
306#[derive(Clone, Debug, PartialEq, Eq)]
307#[non_exhaustive]
308pub struct VisualFrame {
309    /// Width in pixels.
310    pub width: u32,
311    /// Height in pixels.
312    pub height: u32,
313    /// The pixels.
314    pub pixels: FramePixels,
315    /// Which of the source's frames this is.
316    pub index: usize,
317    /// The first source time it is shown at (`i64::MIN` ticks for the
318    /// first frame, which also answers earlier times).
319    pub start: MediaTime,
320    /// The first source time it is no longer shown at, or `None` when it is
321    /// the last.
322    pub end: Option<MediaTime>,
323    /// What producing this frame found that the user should know, such as
324    /// non-finite samples in one frame of a sequence.
325    pub diagnostics: Vec<Diagnostic>,
326}
327
328/// Why a provider could not answer.
329#[derive(Clone, Debug, PartialEq, Eq)]
330#[non_exhaustive]
331pub enum ExtensionError {
332    /// No registered provider reads these bytes, or the provider asked for
333    /// is not in this build.
334    Unsupported(String),
335    /// The bytes are not a well-formed file of the format.
336    Malformed(String),
337    /// The file is larger than a provider opens.
338    Budget(String),
339    /// A resource the source needs is not registered.
340    MissingResource(String),
341    /// The media is not what the project says it is.
342    Mismatch(String),
343}
344
345impl fmt::Display for ExtensionError {
346    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
347        match self {
348            Self::Unsupported(message) => write!(formatter, "unsupported: {message}"),
349            Self::Malformed(message) => write!(formatter, "malformed: {message}"),
350            Self::Budget(message) => write!(formatter, "too large: {message}"),
351            Self::MissingResource(locator) => write!(formatter, "missing resource {locator}"),
352            Self::Mismatch(message) => write!(formatter, "mismatch: {message}"),
353        }
354    }
355}
356
357impl std::error::Error for ExtensionError {}
358
359/// Bytes a source needs besides its own: an image sequence's frames, and the
360/// fonts and images an SVG names. A provider never fetches anything else.
361/// It may be asked from inside a parser (usvg asks for each image it
362/// draws), which requires it to be shared across threads.
363pub trait ResourceResolver: Send + Sync {
364    /// The bytes registered under `locator`.
365    ///
366    /// # Errors
367    /// [`ExtensionError::MissingResource`] when none are registered.
368    fn bytes(&self, locator: &str) -> Result<Arc<[u8]>, ExtensionError>;
369
370    /// Every registered font file, for documents that draw text.
371    fn fonts(&self) -> Vec<Arc<[u8]>> {
372        Vec::new()
373    }
374}
375
376/// A resolver a source may keep: an image sequence reads each frame from it
377/// when it shows that frame, never all of them when it opens.
378pub type SharedResolver = Arc<dyn ResourceResolver>;
379
380/// A resolver with nothing registered.
381pub struct NoResources;
382
383impl ResourceResolver for NoResources {
384    fn bytes(&self, locator: &str) -> Result<Arc<[u8]>, ExtensionError> {
385        Err(ExtensionError::MissingResource(locator.to_owned()))
386    }
387}
388
389/// A source a provider opened: asked for frames by source time.
390pub trait VisualSource: Send {
391    /// What the file is.
392    fn report(&self) -> &ProbeReport;
393
394    /// The frame shown at `time`. A vector source is rasterized to fit
395    /// within `fit` (width, height), keeping its shape; a raster source
396    /// answers at its own size.
397    ///
398    /// # Errors
399    /// Why the frame cannot be produced.
400    fn render(&mut self, time: MediaTime, fit: (u32, u32)) -> Result<VisualFrame, ExtensionError>;
401
402    /// The resources rendering at `time` reads, by locator: those the source
403    /// does not already hold. A host that registers resources only while
404    /// they are used (a page's frames) registers these before the render
405    /// and releases them after.
406    fn needs(&self, _time: MediaTime) -> Vec<String> {
407        Vec::new()
408    }
409
410    /// The bytes the source holds between renders: decoded frames and
411    /// images it keeps, its last rendered frame, and its file's bytes when
412    /// it keeps them. A host that bounds what its open sources hold counts
413    /// this, and closes a source to free it.
414    fn resident_bytes(&self) -> usize;
415
416    /// Another reader of this open source, positioned on its own: what it
417    /// decodes and where it is decoding are its own, and what the source
418    /// decoded when it opened (its file, and for an animation its checked
419    /// frames and the frames kept between requests, within one frame
420    /// budget) is shared with this one and every other reader made from
421    /// either. `None` for a source whose readers need nothing of their own,
422    /// one that draws any time as cheaply: a host shares that source.
423    fn reader(&self) -> Option<Box<dyn VisualSource>> {
424        None
425    }
426
427    /// Of [`Self::resident_bytes`], what this source shares with the
428    /// readers made by [`Self::reader`]: the same bytes for each of them,
429    /// which a host counts once.
430    fn shared_bytes(&self) -> usize {
431        0
432    }
433}
434
435/// Reads one family of formats.
436pub trait VisualProvider: Send + Sync {
437    /// The provider's name, for diagnostics.
438    fn name(&self) -> &'static str;
439
440    /// What `bytes` are, or `None` when they are not this provider's.
441    ///
442    /// # Errors
443    /// Why bytes that look like this provider's format cannot be read.
444    fn probe(&self, bytes: &[u8]) -> Option<Result<ProbeReport, ExtensionError>>;
445
446    /// Opens `bytes` as the project's `source`, whose settings (such as
447    /// which item of a package it shows) the provider applies. Bytes that
448    /// are not this provider's, those [`Self::probe`] answers `None` for,
449    /// are told apart here, from the same signature checks: opening never
450    /// probes first, so a file is read once.
451    ///
452    /// # Errors
453    /// [`ExtensionError::Mismatch`] for bytes that are not this provider's,
454    /// or why they cannot be opened.
455    fn open(
456        &self,
457        bytes: Arc<[u8]>,
458        source: &ProviderSource,
459        resolver: &dyn ResourceResolver,
460    ) -> Result<Box<dyn VisualSource>, ExtensionError>;
461}
462
463/// Premultiplies straight RGBA8 in place, rounding to nearest.
464pub fn premultiply(pixels: &mut [u8]) {
465    for pixel in pixels.as_chunks_mut::<4>().0 {
466        let alpha = u16::from(pixel[3]);
467        if alpha == 255 {
468            continue;
469        }
470        for channel in &mut pixel[..3] {
471            *channel = u8::try_from((u16::from(*channel) * alpha + 127) / 255).unwrap_or(255);
472        }
473    }
474}