mod draw;
mod encode;
mod image;
mod path;
mod poster;
mod prepare;
#[cfg(test)]
mod tests;
use anyhow::{Result, anyhow, bail};
use std::panic::{AssertUnwindSafe, catch_unwind};
pub(crate) use encode::encode_png;
const MAX_SIDE: u32 = 2048;
const DEFAULT_FPS: u32 = 20;
const MAX_FPS: u32 = 120;
pub(crate) struct Renderer {
view_box: (f32, f32),
fps: u32,
frame_count: usize,
poster_frame: usize,
scene: prepare::Scene,
}
pub(crate) struct Frame {
pub width: u32,
pub height: u32,
pub rgba: Vec<u8>,
}
impl Renderer {
pub(crate) fn new(bytes: &[u8]) -> Result<Self> {
let animation = svga::Animation::from_bytes_with(bytes, &svga::Limits::default())
.map_err(|error| anyhow!("{error}"))?;
let params = animation.movie().params;
let (width, height) = (params.view_box_width, params.view_box_height);
if !(width.is_finite() && height.is_finite() && width > 0.0 && height > 0.0) {
bail!("svga_render_invalid_view_box");
}
let frame_count = usize::try_from(params.frames).unwrap_or(0);
if frame_count == 0 {
bail!("svga_render_no_frames");
}
let scene = prepare::scene(&animation)?;
Ok(Self {
view_box: (width, height),
fps: fps(params.fps),
frame_count,
poster_frame: poster::frame(&scene, frame_count),
scene,
})
}
pub(crate) fn canvas_size(&self) -> (u32, u32) {
let round = |side: f32| side.round().clamp(1.0, u32::MAX as f32) as u32;
(round(self.view_box.0), round(self.view_box.1))
}
pub(crate) fn frame_count(&self) -> usize {
self.frame_count
}
pub(crate) fn fps(&self) -> u32 {
self.fps
}
pub(crate) fn output_size(&self, max_side: u32) -> (u32, u32) {
let (width, height) = self.view_box;
let cap = max_side.clamp(1, MAX_SIDE) as f32;
let scale = (cap / width.max(height)).min(1.0);
let pixels = |side: f32| ((side * scale).round() as u32).clamp(1, MAX_SIDE);
(pixels(width), pixels(height))
}
pub(crate) fn render(&self, frame: usize, max_side: u32) -> Result<Frame> {
if frame >= self.frame_count {
bail!("svga_render_frame_out_of_range");
}
let (width, height) = self.output_size(max_side);
let scale = (
width as f32 / self.view_box.0,
height as f32 / self.view_box.1,
);
let drawn = catch_unwind(AssertUnwindSafe(|| {
draw::frame(&self.scene, frame, (width, height), scale)
}));
let pixmap = drawn
.map_err(|_| anyhow!("svga_render_panicked"))?
.ok_or_else(|| anyhow!("svga_render_canvas_allocation_failed"))?;
Ok(Frame {
width,
height,
rgba: pixmap.take_demultiplied(),
})
}
pub(crate) fn poster_frame(&self) -> usize {
self.poster_frame
}
}
fn fps(stored: i32) -> u32 {
match u32::try_from(stored) {
Ok(0) | Err(_) => DEFAULT_FPS,
Ok(fps) => fps.min(MAX_FPS),
}
}