///|
/// Timeline metrics used to compare edits before exporting.
pub(all) struct TimelineMetrics {
frames : Int
duration_cs : Int
minimum_delay_cs : Int
maximum_delay_cs : Int
mean_delay_cs : Int
empty_frames : Int
active_pixels : Int
} derive(Debug)
///|
pub fn TimelineMetrics::frames(self : TimelineMetrics) -> Int {
self.frames
}
///|
pub fn TimelineMetrics::duration(self : TimelineMetrics) -> Int {
self.duration_cs
}
///|
pub fn TimelineMetrics::minimum_delay(self : TimelineMetrics) -> Int {
self.minimum_delay_cs
}
///|
pub fn TimelineMetrics::maximum_delay(self : TimelineMetrics) -> Int {
self.maximum_delay_cs
}
///|
pub fn TimelineMetrics::mean_delay(self : TimelineMetrics) -> Int {
self.mean_delay_cs
}
///|
pub fn TimelineMetrics::empty_frames(self : TimelineMetrics) -> Int {
self.empty_frames
}
///|
pub fn TimelineMetrics::active_pixels(self : TimelineMetrics) -> Int {
self.active_pixels
}
///|
/// Calculate duration, delay distribution, and visible pixel totals.
pub fn Animation::timeline_metrics(self : Animation) -> TimelineMetrics {
let mut duration = 0
let mut minimum = 65535
let mut maximum = 0
let mut empty = 0
let mut active = 0
for frame in self.frames {
let delay = frame.delay_cs()
duration += delay
if delay < minimum {
minimum = delay
}
if delay > maximum {
maximum = delay
}
for color in frame.canvas().pixels {
if color.alpha() == 0 {
empty += 1
} else {
active += 1
}
}
}
let count = self.frame_count()
TimelineMetrics::{
frames: count,
duration_cs: duration,
minimum_delay_cs: if count == 0 {
0
} else {
minimum
},
maximum_delay_cs: maximum,
mean_delay_cs: if count == 0 {
0
} else {
duration / count
},
empty_frames: empty,
active_pixels: active,
}
}
///|
/// Return an animation's effective frames per second in milli-Hz.
pub fn Animation::effective_fps_milli(self : Animation) -> Int {
let duration = self.duration_cs()
if duration <= 0 {
0
} else {
self.frame_count() * 100000 / duration
}
}
///|
/// Return the first frame whose delay exceeds a threshold.
pub fn Animation::first_slow_frame(
self : Animation,
threshold_cs : Int,
) -> Int? {
let threshold = if threshold_cs < 0 { 0 } else { threshold_cs }
for index, frame in self.frames {
if frame.delay_cs() > threshold {
return Some(index)
}
}
None
}
///|
/// Compare timeline structure without comparing pixels.
pub fn compare_timeline(left : Animation, right : Animation) -> (Int, Int, Int) {
let a = left.timeline_metrics()
let b = right.timeline_metrics()
(
b.frames() - a.frames(),
b.duration() - a.duration(),
b.mean_delay() - a.mean_delay(),
)
}
///|
/// Return a copy with pathological delays normalized to a playback floor.
pub fn Animation::with_delay_floor(
self : Animation,
floor_cs : Int,
) -> Animation {
let floor = if floor_cs < 0 {
0
} else if floor_cs > 65535 {
65535
} else {
floor_cs
}
let frames : Array[Frame] = []
for frame in self.frames {
frames.push(
if frame.delay_cs() < floor {
frame.with_delay(delay_cs=floor)
} else {
frame
},
)
}
Animation::{ ..self, frames, }
}