use std::fmt;
use std::sync::Arc;
use alpha2_model::{LinearPrimaries, ProviderSource};
use alpha2_time::{FrameRate, MediaTime};
use serde::Serialize;
pub const MAX_NORMALIZED_DELAY_MS: u32 = 10;
pub const NORMALIZED_DELAY_MS: u32 = 100;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum Format {
Svg,
Gif,
Apng,
Webp,
PngSequence,
Lottie,
DotLottie,
Exr,
ExrSequence,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[non_exhaustive]
pub enum Timing {
Static,
Frames {
durations: Vec<i64>,
},
Rate {
rate: FrameRate,
frames: usize,
},
}
impl Timing {
#[must_use]
pub fn duration(&self) -> Option<MediaTime> {
match self {
Self::Static => None,
Self::Frames { durations } => Some(MediaTime::from_ticks(durations.iter().sum())),
Self::Rate { rate, frames } => Some(frame_start(*rate, *frames)),
}
}
#[must_use]
pub fn frame_count(&self) -> usize {
match self {
Self::Static => 1,
Self::Frames { durations } => durations.len(),
Self::Rate { frames, .. } => *frames,
}
}
#[must_use]
pub fn frame_at(&self, time: MediaTime) -> (usize, MediaTime, Option<MediaTime>) {
let ticks = time.ticks();
match self {
Self::Static => (0, MediaTime::from_ticks(i64::MIN), None),
Self::Frames { durations } => {
let mut start = 0_i64;
for (index, duration) in durations.iter().enumerate() {
let end = start + duration;
if ticks < end || index + 1 == durations.len() {
let first = if index == 0 { i64::MIN } else { start };
let last =
(index + 1 < durations.len()).then(|| MediaTime::from_ticks(end));
return (index, MediaTime::from_ticks(first), last);
}
start = end;
}
(0, MediaTime::from_ticks(i64::MIN), None)
}
Self::Rate { rate, frames } => {
let index = rate
.frame_at_or_before(time)
.map_or(usize::MAX, |index| {
usize::try_from(index.max(0)).unwrap_or(usize::MAX)
})
.min(frames - 1);
let first = if index == 0 {
MediaTime::from_ticks(i64::MIN)
} else {
frame_start(*rate, index)
};
let last = (index + 1 < *frames).then(|| frame_start(*rate, index + 1));
(index, first, last)
}
}
}
}
fn frame_start(rate: FrameRate, index: usize) -> MediaTime {
i64::try_from(index)
.ok()
.and_then(|index| rate.frame_start(index))
.unwrap_or(MediaTime::from_ticks(i64::MAX))
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[non_exhaustive]
pub enum Diagnostic {
NormalizedDelay {
frame: usize,
declared_ms: u32,
},
MissingFont {
families: String,
},
MissingImage {
href: String,
},
UnsupportedFeature {
feature: String,
path: String,
count: usize,
},
UnreadableImage {
href: String,
reason: String,
},
UnreadableItem {
item: String,
reason: String,
},
NonFiniteSamples {
count: usize,
},
OutOfHalfRange {
count: usize,
},
ReducedPrecision,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[non_exhaustive]
pub struct LinearColor {
pub primaries: Option<LinearPrimaries>,
pub chromaticities: [[f32; 2]; 4],
pub origin: ColorOrigin,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ColorOrigin {
File,
FormatDefault,
Project,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[non_exhaustive]
pub struct ProbeReport {
pub format: Format,
pub width: u32,
pub height: u32,
pub timing: Timing,
pub loop_count: Option<u32>,
pub items: Vec<String>,
pub item: Option<String>,
pub color: Option<LinearColor>,
pub diagnostics: Vec<Diagnostic>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum FramePixels {
Rgba8(Arc<[u8]>),
LinearF16(Arc<[u16]>),
}
impl FramePixels {
#[must_use]
pub fn byte_len(&self) -> usize {
match self {
Self::Rgba8(pixels) => pixels.len(),
Self::LinearF16(halves) => halves.len() * 2,
}
}
#[must_use]
pub fn rgba8(&self) -> Option<&[u8]> {
match self {
Self::Rgba8(pixels) => Some(pixels),
Self::LinearF16(_) => None,
}
}
#[must_use]
pub fn linear_f16(&self) -> Option<&[u16]> {
match self {
Self::LinearF16(halves) => Some(halves),
Self::Rgba8(_) => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct VisualFrame {
pub width: u32,
pub height: u32,
pub pixels: FramePixels,
pub index: usize,
pub start: MediaTime,
pub end: Option<MediaTime>,
pub diagnostics: Vec<Diagnostic>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ExtensionError {
Unsupported(String),
Malformed(String),
Budget(String),
MissingResource(String),
Mismatch(String),
}
impl fmt::Display for ExtensionError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Unsupported(message) => write!(formatter, "unsupported: {message}"),
Self::Malformed(message) => write!(formatter, "malformed: {message}"),
Self::Budget(message) => write!(formatter, "too large: {message}"),
Self::MissingResource(locator) => write!(formatter, "missing resource {locator}"),
Self::Mismatch(message) => write!(formatter, "mismatch: {message}"),
}
}
}
impl std::error::Error for ExtensionError {}
pub trait ResourceResolver: Send + Sync {
fn bytes(&self, locator: &str) -> Result<Arc<[u8]>, ExtensionError>;
fn fonts(&self) -> Vec<Arc<[u8]>> {
Vec::new()
}
}
pub type SharedResolver = Arc<dyn ResourceResolver>;
pub struct NoResources;
impl ResourceResolver for NoResources {
fn bytes(&self, locator: &str) -> Result<Arc<[u8]>, ExtensionError> {
Err(ExtensionError::MissingResource(locator.to_owned()))
}
}
pub trait VisualSource: Send {
fn report(&self) -> &ProbeReport;
fn render(&mut self, time: MediaTime, fit: (u32, u32)) -> Result<VisualFrame, ExtensionError>;
fn needs(&self, _time: MediaTime) -> Vec<String> {
Vec::new()
}
fn resident_bytes(&self) -> usize;
fn reader(&self) -> Option<Box<dyn VisualSource>> {
None
}
fn shared_bytes(&self) -> usize {
0
}
}
pub trait VisualProvider: Send + Sync {
fn name(&self) -> &'static str;
fn probe(&self, bytes: &[u8]) -> Option<Result<ProbeReport, ExtensionError>>;
fn open(
&self,
bytes: Arc<[u8]>,
source: &ProviderSource,
resolver: &dyn ResourceResolver,
) -> Result<Box<dyn VisualSource>, ExtensionError>;
}
pub fn premultiply(pixels: &mut [u8]) {
for pixel in pixels.as_chunks_mut::<4>().0 {
let alpha = u16::from(pixel[3]);
if alpha == 255 {
continue;
}
for channel in &mut pixel[..3] {
*channel = u8::try_from((u16::from(*channel) * alpha + 127) / 255).unwrap_or(255);
}
}
}