From 5e1f5e5b070e2201eb17a04f11a971512a175ff5 Mon Sep 17 00:00:00 2001 From: "Spencer C. Imbleau" Date: Mon, 25 Mar 2024 21:31:08 -0400 Subject: [PATCH] Prep release (#15) Prepare for release --------- Co-authored-by: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> Co-authored-by: Kaur Kuut --- CHANGELOG.md | 15 ++++++++ Cargo.toml | 1 - README.md | 20 ++++++++++ examples/scenes/src/download.rs | 15 ++++++-- src/import/converters.rs | 67 ++++++++++++++++++++++++++++++--- src/import/mod.rs | 1 - src/import/util.rs | 62 ------------------------------ src/lib.rs | 47 ++++++++++++++++++++++- 8 files changed, 155 insertions(+), 73 deletions(-) create mode 100644 CHANGELOG.md delete mode 100644 src/import/util.rs diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ec9d848 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog + + + +## Unreleased + +## 0.1.0 (2024-03-26) + +- Initial release diff --git a/Cargo.toml b/Cargo.toml index cb75730..b01b2d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" version = "0.1.0" license = "Apache-2.0 OR MIT" repository = "https://github.com/linebender/velato" -publish = false [package] name = "velato" description = "A Lottie integration for vello." diff --git a/README.md b/README.md index c3b4f9b..4b9b9d3 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,26 @@ Several Lottie features are not yet supported, including: - Split rotations - Split positions +## Usage + +Velato makes it simple to encode Lottie as a [`vello::Scene`](https://docs.rs/vello/*/vello/struct.Scene.html). + +```rust +// Parse your lottie file +let lottie = include_str!("../lottie.json"); +let composition = velato::Composition::from_str(lottie).expect("valid file"); + +// Render to a scene +let mut new_scene = vello::Scene::new(); + +// Render to a scene! +let mut renderer = velato::Renderer::new(); +let frame = 0.0; // Arbitrary number chosen. Ensure it's a valid frame! +let transform = vello::kurbo::Affine::IDENTITY; +let alpha = 1.0; +renderer.render(&composition, frame, transform, alpha, &mut new_scene); +``` + ## Examples ### Cross platform (Winit) diff --git a/examples/scenes/src/download.rs b/examples/scenes/src/download.rs index ed933b3..065cfb1 100644 --- a/examples/scenes/src/download.rs +++ b/examples/scenes/src/download.rs @@ -58,6 +58,7 @@ impl Download { println!( "Would you like to download a set of default lottie files? These files are:" ); + let mut total_bytes = 0; for download in &downloads { let builtin = download.builtin.as_ref().unwrap(); println!( @@ -68,12 +69,11 @@ impl Download { builtin.license, builtin.info ); + total_bytes += builtin.expected_size; } // For rustfmt, split prompt into its own line - const PROMPT: &str = - "Would you like to download a set of default lottie files, as explained above?"; - accepted = Confirm::new(PROMPT).with_default(false).prompt()?; + accepted = download_prompt(total_bytes)?; } else { println!("Nothing to download! All default downloads already created"); } @@ -148,6 +148,15 @@ impl Download { } } +fn download_prompt(total_bytes: u64) -> Result { + let prompt = format!( + "Would you like to download a set of default lottie files, as explained above? ({})", + byte_unit::Byte::from_bytes(total_bytes.into()).get_appropriate_unit(false) + ); + let accepted = Confirm::new(&prompt).with_default(false).prompt()?; + Ok(accepted) +} + struct LottieDownload { name: String, url: String, diff --git a/src/import/converters.rs b/src/import/converters.rs index 76449a3..d16ca04 100644 --- a/src/import/converters.rs +++ b/src/import/converters.rs @@ -1,12 +1,10 @@ // Copyright 2024 the Velato Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use std::collections::HashMap; - use super::builders::{setup_layer_base, setup_precomp_layer, setup_shape_layer}; use super::defaults::{FLOAT_VALUE_ONE_HUNDRED, FLOAT_VALUE_ZERO, MULTIDIM_ONE, POSITION_ZERO}; -use crate::import::util::calc_stops; use crate::runtime::model::animated::{self, Position}; +use crate::runtime::model::Easing; use crate::runtime::model::{ self, Content, Draw, EasingHandle, GroupTransform, Layer, SplineToPath, Time, Tween, Value, }; @@ -19,6 +17,7 @@ use crate::schema::animated_properties::split_vector::SplitVector; use crate::schema::constants::gradient_type::GradientType; use crate::schema::helpers::int_boolean::BoolInt; use crate::{schema, Composition}; +use std::collections::HashMap; use vello::kurbo::{Cap, Join, Point, Size, Vec2}; use vello::peniko::{BlendMode, Color, Mix}; @@ -333,7 +332,7 @@ fn conv_gradient_colors( match &value.colors.animated_property.value { Static(value) => runtime::model::ColorStops::Fixed({ let mut stops = runtime::model::fixed::ColorStops::new(); - let raw = calc_stops(value, count); + let raw = conv_stops(value, count); for values in raw { stops.push( ( @@ -364,7 +363,7 @@ fn conv_gradient_colors( hold, }); - let stops = calc_stops(&value.value, count) + let stops = conv_stops(&value.value, count) .into_iter() .flatten() .collect::>(); @@ -776,3 +775,61 @@ pub fn conv_size(value: &MultiDimensional) -> Value { ) }) } + +pub fn conv_stops(value: &[f64], count: usize) -> Vec<[f64; 5]> { + let mut stops: Vec<[f64; 5]> = Vec::new(); + let mut alpha_stops: Vec<(f64, f64)> = Vec::new(); + for chunk in value.chunks_exact(4) { + stops.push([chunk[0], chunk[1], chunk[2], chunk[3], 1.0]); + if stops.len() >= count { + // there is alpha data at the end of the list, which is a sequence + // of (offset, alpha) pairs + for chunk in value.chunks_exact(2).skip(count * 2) { + let offset = chunk[0]; + let alpha = chunk[1]; + alpha_stops.push((offset, alpha)); + } + + for stop in stops.iter_mut() { + let mut last: Option<(f64, f64)> = None; + for &(b, alpha_b) in alpha_stops.iter() { + if let Some((a, alpha_a)) = last.take() { + let x = stop[0]; + let t = normalize_to_range(a, b, x); + + let alpha_interp = alpha_a.tween(&alpha_b, t, &Easing::LERP); + let alpha_interp = if (x >= a && x <= b) && (t <= 0.25) && (x <= 0.1) { + alpha_a + } else { + alpha_interp + }; // todo: this is a hack to get alpha rendering with a + // falloff similar to lottiefiles' + + let alpha_interp = if (x >= a && x <= b) && (t >= 0.75) && (x >= 0.9) { + alpha_b + } else { + alpha_interp + }; // todo: this is a hack to get alpha rendering with a + // falloff similar to lottiefiles' + + stop[4] = stop[4].min(alpha_interp); + } + last = Some((b, alpha_b)); + } + } + break; + } + } + + stops +} + +pub fn normalize_to_range(a: f64, b: f64, x: f64) -> f64 { + if a == b { + // Avoid division by zero if a and b are the same + return 0.0; + } + + // Calculate the normalized value + (x - a) / (b - a) +} diff --git a/src/import/mod.rs b/src/import/mod.rs index c316352..abe5f6b 100644 --- a/src/import/mod.rs +++ b/src/import/mod.rs @@ -4,6 +4,5 @@ mod builders; mod converters; mod defaults; -mod util; pub use converters::conv_animation; diff --git a/src/import/util.rs b/src/import/util.rs deleted file mode 100644 index 9b7039c..0000000 --- a/src/import/util.rs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2024 the Velato Authors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -use crate::runtime::model::{Easing, Tween}; - -pub fn normalize_to_range(a: f64, b: f64, x: f64) -> f64 { - if a == b { - // Avoid division by zero if a and b are the same - return 0.0; - } - - // Calculate the normalized value - (x - a) / (b - a) -} - -pub fn calc_stops(value: &[f64], count: usize) -> Vec<[f64; 5]> { - let mut stops: Vec<[f64; 5]> = Vec::new(); - let mut alpha_stops: Vec<(f64, f64)> = Vec::new(); - for chunk in value.chunks_exact(4) { - stops.push([chunk[0], chunk[1], chunk[2], chunk[3], 1.0]); - if stops.len() >= count { - // there is alpha data at the end of the list, which is a sequence - // of (offset, alpha) pairs - for chunk in value.chunks_exact(2).skip(count * 2) { - let offset = chunk[0]; - let alpha = chunk[1]; - alpha_stops.push((offset, alpha)); - } - - for stop in stops.iter_mut() { - let mut last: Option<(f64, f64)> = None; - for &(b, alpha_b) in alpha_stops.iter() { - if let Some((a, alpha_a)) = last.take() { - let x = stop[0]; - let t = normalize_to_range(a, b, x); - - let alpha_interp = alpha_a.tween(&alpha_b, t, &Easing::LERP); - let alpha_interp = if (x >= a && x <= b) && (t <= 0.25) && (x <= 0.1) { - alpha_a - } else { - alpha_interp - }; // todo: this is a hack to get alpha rendering with a - // falloff similar to lottiefiles' - - let alpha_interp = if (x >= a && x <= b) && (t >= 0.75) && (x >= 0.9) { - alpha_b - } else { - alpha_interp - }; // todo: this is a hack to get alpha rendering with a - // falloff similar to lottiefiles' - - stop[4] = stop[4].min(alpha_interp); - } - last = Some((b, alpha_b)); - } - } - break; - } - } - - stops -} diff --git a/src/lib.rs b/src/lib.rs index c8859e8..58f49a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,53 @@ // Copyright 2024 the Velato Authors // SPDX-License-Identifier: Apache-2.0 OR MIT +//! Render a Lottie animation to a Vello [`Scene`](crate::vello::Scene). +//! +//! However, this is also intended to be the preferred integration between Vello and [Lottie](https://lottie.github.io/lottie-spec/), so [consider +//! contributing](https://github.com/linebender/velato) if you need a feature which is missing. +//! +//! This crate also re-exports [`vello`], to make handling dependency versions easier. +//! +//! ## Usage +//! +//! ```no_run +//! # use std::str::FromStr; +//! use velato::vello; +//! +//! // Parse your lottie file +//! let lottie = include_str!("../examples/assets/google_fonts/Tiger.json"); +//! let composition = velato::Composition::from_str(lottie).expect("valid file"); +//! +//! // Render to a scene +//! let mut new_scene = vello::Scene::new(); +//! +//! // Render to a scene! +//! let mut renderer = velato::Renderer::new(); +//! let frame = 0.0; // Arbitrary number chosen. Ensure it's a valid frame! +//! let transform = vello::kurbo::Affine::IDENTITY; +//! let alpha = 1.0; +//! renderer.render(&composition, frame, transform, alpha, &mut new_scene); +//! ``` +//! +//! # Unsupported features +//! +//! Missing features include: +//! - Non-linear easings +//! - Position keyframe (`ti`, `to`) easing +//! - Time remapping (`tm`) +//! - Text +//! - Image embedding +//! - Advanced shapes (stroke dash, zig-zag, etc.) +//! - Advanced effects (motion blur, drop shadows, etc.) +//! - Correct color stop handling +//! - Split rotations +//! - Split positions + pub(crate) mod import; pub(crate) mod runtime; pub(crate) mod schema; -pub use runtime::{Composition, Renderer}; +// Re-export vello +pub use vello; + +pub use runtime::{model, Composition, Renderer};