motionloom 0.1.0

MotionLoom DSL parser and renderer for video effects, scene graphs, motion graphics, and world graphs.
Documentation
use image::{Rgba, RgbaImage, imageops::FilterType};

use crate::dsl::GraphScript;
use crate::scene::composition::draw_rgba_image;
use crate::scene::spatial::Affine2;

pub(crate) fn format_scene_number(value: f32) -> String {
    if !value.is_finite() {
        return "0".to_string();
    }
    let mut text = format!("{value:.4}");
    while text.contains('.') && text.ends_with('0') {
        text.pop();
    }
    if text.ends_with('.') {
        text.pop();
    }
    if text == "-0" {
        text = "0".to_string();
    }
    text
}

pub(crate) fn graph_logical_render_size(graph: &GraphScript) -> (u32, u32) {
    graph.size
}

pub(crate) fn graph_output_size(graph: &GraphScript) -> (u32, u32) {
    graph.render_size.unwrap_or(graph.size)
}

pub(crate) fn render_size_root_transform(
    output_size: (u32, u32),
    logical_size: (u32, u32),
) -> Affine2 {
    let output_w = output_size.0.max(1) as f32;
    let output_h = output_size.1.max(1) as f32;
    let logical_w = logical_size.0.max(1) as f32;
    let logical_h = logical_size.1.max(1) as f32;
    let scale = (output_w / logical_w).min(output_h / logical_h);
    let x = (output_w - logical_w * scale) * 0.5;
    let y = (output_h - logical_h * scale) * 0.5;
    Affine2::translate(x, y).mul(Affine2::scale(scale))
}

pub(crate) fn fit_logical_canvas_to_output(
    image: &RgbaImage,
    output_size: (u32, u32),
) -> RgbaImage {
    let output_w = output_size.0.max(1);
    let output_h = output_size.1.max(1);
    if image.width() == output_w && image.height() == output_h {
        return image.clone();
    }

    let logical_w = image.width().max(1) as f32;
    let logical_h = image.height().max(1) as f32;
    let scale = (output_w as f32 / logical_w).min(output_h as f32 / logical_h);
    let target_w = (logical_w * scale).round().max(1.0) as u32;
    let target_h = (logical_h * scale).round().max(1.0) as u32;
    let x = (output_w as f32 - target_w as f32) * 0.5;
    let y = (output_h as f32 - target_h as f32) * 0.5;
    let mut output = RgbaImage::from_pixel(output_w, output_h, Rgba([0, 0, 0, 255]));
    let scaled = image::imageops::resize(image, target_w, target_h, FilterType::Lanczos3);
    draw_rgba_image(&mut output, &scaled, x, y, 1.0);
    output
}