rasterlottie 0.1.0

Pure Rust, headless Lottie rasterizer for deterministic server-side rendering
Documentation
use serde_json::Value;

use super::model_types::{BezierPath, BezierVertex};

pub fn f32_unit_to_u8(value: f32) -> u8 {
    (value.clamp(0.0, 1.0) * 255.0).round() as u8
}

pub fn color_components_to_rgba(values: &[f32]) -> Option<[u8; 4]> {
    if values.len() < 3 {
        return None;
    }

    Some([
        f32_unit_to_u8(values[0]),
        f32_unit_to_u8(values[1]),
        f32_unit_to_u8(values[2]),
        f32_unit_to_u8(values.get(3).copied().unwrap_or(1.0)),
    ])
}

pub fn parse_bezier_keyframe_value(value: &Value) -> Option<BezierPath> {
    match value {
        Value::Object(_) => parse_bezier_path(value),
        Value::Array(items) if items.len() == 1 => parse_bezier_path(items.first()?),
        _ => None,
    }
}

pub fn parse_bezier_path(value: &Value) -> Option<BezierPath> {
    let object = value.as_object()?;
    let vertices = parse_vec2_list(object.get("v")?)?;
    let in_tangents = parse_vec2_list(object.get("i")?)?;
    let out_tangents = parse_vec2_list(object.get("o")?)?;

    if vertices.is_empty()
        || vertices.len() != in_tangents.len()
        || vertices.len() != out_tangents.len()
    {
        return None;
    }

    Some(BezierPath {
        closed: object.get("c").and_then(value_as_bool).unwrap_or(false),
        vertices: vertices
            .into_iter()
            .zip(in_tangents)
            .zip(out_tangents)
            .map(|((vertex, in_tangent), out_tangent)| BezierVertex {
                vertex,
                in_tangent,
                out_tangent,
            })
            .collect(),
    })
}

fn parse_vec2_list(value: &Value) -> Option<Vec<[f32; 2]>> {
    value.as_array()?.iter().map(parse_vec2).collect()
}

fn parse_vec2(value: &Value) -> Option<[f32; 2]> {
    let items = value.as_array()?;
    if items.len() < 2 {
        return None;
    }

    Some([items[0].as_f64()? as f32, items[1].as_f64()? as f32])
}

fn value_as_bool(value: &Value) -> Option<bool> {
    match value {
        Value::Bool(value) => Some(*value),
        Value::Number(value) => value.as_i64().map(|number| number != 0),
        _ => None,
    }
}