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}