| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use std::{ |
| collections::HashMap, convert::TryFrom, fs, num::NonZeroU64, path::Path, time::Duration, |
| }; |
| |
| use forma::{ |
| BlendMode, Color, Composition, Fill, FillRule, Func, GeomPresTransform, GradientBuilder, |
| GradientType, Order, PathBuilder, Point, Props, Style, |
| }; |
| use rive_rs::{ |
| animation::LinearAnimationInstance, |
| layout::{self, Alignment, Fit}, |
| math::{self, Aabb, Mat}, |
| shapes::{paint::Color32, Command, CommandPath}, |
| Artboard, BinaryReader, File, Object, PaintColor, RenderPaint, Renderer, |
| }; |
| |
| use crate::{App, Keyboard}; |
| |
| fn to_linear(color: Color32) -> [f32; 4] { |
| fn conv(l: u8) -> f32 { |
| let l = f32::from(l) * 255.0f32.recip(); |
| |
| if l <= 0.04045 { |
| l * 12.92f32.recip() |
| } else { |
| ((l + 0.055) * 1.055f32.recip()).powf(2.4) |
| } |
| } |
| |
| [ |
| conv(color.red()), |
| conv(color.green()), |
| conv(color.blue()), |
| f32::from(color.alpha()) * 255.0f32.recip(), |
| ] |
| } |
| |
| fn to_forma_point(p: math::Vec) -> Point { |
| Point::new(p.x, p.y) |
| } |
| |
| fn to_forma_path(commands: &[Command]) -> forma::Path { |
| let mut builder = PathBuilder::default(); |
| |
| for command in commands { |
| match *command { |
| Command::MoveTo(p) => { |
| builder.move_to(to_forma_point(p)); |
| } |
| Command::LineTo(p) => { |
| builder.line_to(to_forma_point(p)); |
| } |
| Command::CubicTo(c0, c1, p) => { |
| builder.cubic_to(to_forma_point(c0), to_forma_point(c1), to_forma_point(p)); |
| } |
| Command::Close => {} |
| } |
| } |
| |
| builder.build() |
| } |
| |
| #[derive(Debug)] |
| struct FormaRenderer<'c, 't> { |
| composition: &'c mut Composition, |
| inverted_transforms: &'t mut HashMap<Order, Mat>, |
| order: u32, |
| } |
| |
| impl<'c, 't> Renderer for FormaRenderer<'c, 't> { |
| fn draw(&mut self, path: &CommandPath, transform: Mat, paint: &RenderPaint) { |
| let order = Order::new(self.order).unwrap(); |
| |
| let layer = self.composition.get_mut_or_insert_default(order); |
| |
| if path.user_tag.get().map(|tag| tag.get() - 1 == u64::from(self.order)).unwrap_or_default() |
| { |
| let cached_transform = |
| if let Some(inverted_transform) = self.inverted_transforms.get(&order).copied() { |
| transform * inverted_transform |
| } else { |
| transform |
| }; |
| |
| if let Ok(transform) = GeomPresTransform::try_from([ |
| cached_transform.scale_x, |
| cached_transform.shear_x, |
| cached_transform.shear_y, |
| cached_transform.scale_y, |
| cached_transform.translate_x, |
| cached_transform.translate_y, |
| ]) { |
| layer.set_transform(transform); |
| } else { |
| let forma_path = to_forma_path(&path.commands); |
| |
| if let Some(inverted_transform) = transform.invert() { |
| self.inverted_transforms.insert(order, inverted_transform); |
| } |
| |
| layer.clear().insert(&forma_path.transform(&[ |
| transform.scale_x, |
| transform.shear_x, |
| transform.translate_x, |
| transform.shear_y, |
| transform.scale_y, |
| transform.translate_y, |
| 0.0, |
| 0.0, |
| 1.0, |
| ])); |
| |
| path.user_tag.set(NonZeroU64::new(u64::from(self.order) + 1)); |
| } |
| } else { |
| let forma_path = to_forma_path(&path.commands); |
| |
| if let Some(inverted_transform) = transform.invert() { |
| self.inverted_transforms.insert(order, inverted_transform); |
| } |
| |
| layer.clear().insert(&forma_path.transform(&[ |
| transform.scale_x, |
| transform.shear_x, |
| transform.translate_x, |
| transform.shear_y, |
| transform.scale_y, |
| transform.translate_y, |
| 0.0, |
| 0.0, |
| 1.0, |
| ])); |
| |
| path.user_tag.set(NonZeroU64::new(u64::from(self.order) + 1)); |
| } |
| |
| let blend_mode = match paint.blend_mode { |
| rive_rs::shapes::paint::BlendMode::SrcOver => BlendMode::Over, |
| rive_rs::shapes::paint::BlendMode::Screen => BlendMode::Screen, |
| rive_rs::shapes::paint::BlendMode::Overlay => BlendMode::Overlay, |
| rive_rs::shapes::paint::BlendMode::Darken => BlendMode::Darken, |
| rive_rs::shapes::paint::BlendMode::Lighten => BlendMode::Lighten, |
| rive_rs::shapes::paint::BlendMode::ColorDodge => BlendMode::ColorDodge, |
| rive_rs::shapes::paint::BlendMode::ColorBurn => BlendMode::ColorBurn, |
| rive_rs::shapes::paint::BlendMode::HardLight => BlendMode::HardLight, |
| rive_rs::shapes::paint::BlendMode::SoftLight => BlendMode::SoftLight, |
| rive_rs::shapes::paint::BlendMode::Difference => BlendMode::Difference, |
| rive_rs::shapes::paint::BlendMode::Exclusion => BlendMode::Exclusion, |
| rive_rs::shapes::paint::BlendMode::Multiply => BlendMode::Multiply, |
| rive_rs::shapes::paint::BlendMode::Hue => BlendMode::Hue, |
| rive_rs::shapes::paint::BlendMode::Saturation => BlendMode::Saturation, |
| rive_rs::shapes::paint::BlendMode::Color => BlendMode::Color, |
| rive_rs::shapes::paint::BlendMode::Luminosity => BlendMode::Luminosity, |
| }; |
| |
| let fill_rule = match paint.fill_rule { |
| rive_rs::shapes::FillRule::NonZero => FillRule::NonZero, |
| rive_rs::shapes::FillRule::EvenOdd => FillRule::EvenOdd, |
| }; |
| |
| layer.set_props(match &paint.color { |
| PaintColor::Solid(color) => { |
| let [r, g, b, a] = to_linear(*color); |
| Props { |
| fill_rule, |
| func: Func::Draw(forma::Style { |
| fill: Fill::Solid(Color { r, g, b, a }), |
| is_clipped: paint.is_clipped, |
| blend_mode, |
| }), |
| } |
| } |
| PaintColor::Gradient(gradient) => { |
| let start = transform * gradient.start; |
| let end = transform * gradient.end; |
| |
| let mut builder = |
| GradientBuilder::new(Point::new(start.x, start.y), Point::new(end.x, end.y)); |
| builder.r#type(match gradient.r#type { |
| rive_rs::GradientType::Linear => GradientType::Linear, |
| rive_rs::GradientType::Radial => GradientType::Radial, |
| }); |
| |
| for &(color, stop) in &gradient.stops { |
| let [r, g, b, a] = to_linear(color); |
| builder.color_with_stop(Color { r, g, b, a }, stop); |
| } |
| |
| Props { |
| fill_rule, |
| func: Func::Draw(Style { |
| fill: Fill::Gradient(builder.build().unwrap()), |
| is_clipped: paint.is_clipped, |
| blend_mode, |
| }), |
| } |
| } |
| }); |
| |
| self.order += 1; |
| } |
| |
| fn clip(&mut self, path: &CommandPath, transform: Mat, layers: usize) { |
| let order = Order::new(self.order).unwrap(); |
| |
| let layer = self.composition.get_mut_or_insert_default(order); |
| |
| if path.user_tag.get().map(|tag| tag.get() - 1 == u64::from(self.order)).unwrap_or_default() |
| { |
| let cached_transform = |
| if let Some(inverted_transform) = self.inverted_transforms.get(&order).copied() { |
| transform * inverted_transform |
| } else { |
| transform |
| }; |
| |
| if let Ok(transform) = GeomPresTransform::try_from([ |
| cached_transform.scale_x, |
| cached_transform.shear_x, |
| cached_transform.shear_y, |
| cached_transform.scale_y, |
| cached_transform.translate_x, |
| cached_transform.translate_y, |
| ]) { |
| layer.set_transform(transform); |
| } else { |
| let forma_path = to_forma_path(&path.commands); |
| |
| if let Some(inverted_transform) = transform.invert() { |
| self.inverted_transforms.insert(order, inverted_transform); |
| } |
| |
| layer.clear().insert(&forma_path.transform(&[ |
| transform.scale_x, |
| transform.shear_x, |
| transform.translate_x, |
| transform.shear_y, |
| transform.scale_y, |
| transform.translate_y, |
| 0.0, |
| 0.0, |
| 1.0, |
| ])); |
| |
| path.user_tag.set(NonZeroU64::new(u64::from(self.order) + 1)); |
| } |
| } else { |
| let forma_path = to_forma_path(&path.commands); |
| |
| if let Some(inverted_transform) = transform.invert() { |
| self.inverted_transforms.insert(order, inverted_transform); |
| } |
| |
| layer.clear().insert(&forma_path.transform(&[ |
| transform.scale_x, |
| transform.shear_x, |
| transform.translate_x, |
| transform.shear_y, |
| transform.scale_y, |
| transform.translate_y, |
| 0.0, |
| 0.0, |
| 1.0, |
| ])); |
| |
| path.user_tag.set(NonZeroU64::new(u64::from(self.order) + 1)); |
| } |
| |
| layer.set_props(forma::Props { fill_rule: FillRule::NonZero, func: Func::Clip(layers) }); |
| |
| self.order += 1; |
| } |
| } |
| |
| pub struct Rive { |
| _file: File, |
| artboard: Object<Artboard>, |
| animation_instance: Option<LinearAnimationInstance>, |
| width: usize, |
| height: usize, |
| inverted_transforms: HashMap<Order, Mat>, |
| } |
| |
| impl Rive { |
| pub fn new<P: AsRef<Path>>(path: P) -> Self { |
| Self::from_buffer(fs::read(path).unwrap()) |
| } |
| |
| pub fn from_buffer(buffer: Vec<u8>) -> Self { |
| let mut reader = BinaryReader::new(&buffer); |
| let file = File::import(&mut reader).unwrap(); |
| |
| let artboard = file.artboard().unwrap(); |
| let artboard_ref = artboard.as_ref(); |
| artboard_ref.advance(0.0); |
| |
| let mut animations = artboard_ref.animations().map(LinearAnimationInstance::new); |
| let animation_instance = animations.next(); |
| |
| let width = artboard.as_ref().width() as usize; |
| let height = artboard.as_ref().height() as usize; |
| |
| Self { |
| _file: file, |
| artboard, |
| animation_instance, |
| width, |
| height, |
| inverted_transforms: HashMap::new(), |
| } |
| } |
| } |
| |
| impl App for Rive { |
| fn width(&self) -> usize { |
| self.width |
| } |
| |
| fn height(&self) -> usize { |
| self.height |
| } |
| |
| fn set_width(&mut self, width: usize) { |
| self.width = width; |
| } |
| |
| fn set_height(&mut self, height: usize) { |
| self.height = height; |
| } |
| |
| fn compose(&mut self, composition: &mut Composition, elapsed: Duration, _: &Keyboard) { |
| let elapsed = elapsed.as_secs_f32(); |
| |
| let mut forma_renderer = FormaRenderer { |
| composition, |
| inverted_transforms: &mut self.inverted_transforms, |
| order: 0, |
| }; |
| |
| let artboard_ref = self.artboard.as_ref(); |
| |
| if let Some(ref mut animation_instance) = self.animation_instance.as_mut() { |
| animation_instance.advance(0.0); |
| animation_instance.advance(elapsed); |
| animation_instance.apply(self.artboard.clone(), 1.0); |
| } |
| |
| artboard_ref.advance(elapsed); |
| artboard_ref.draw( |
| &mut forma_renderer, |
| layout::align( |
| Fit::Contain, |
| Alignment::center(), |
| Aabb::new(0.0, 0.0, self.width as f32, self.height as f32), |
| artboard_ref.bounds(), |
| ), |
| ); |
| |
| for (order, layer) in forma_renderer.composition.layers_mut() { |
| if order.as_u32() >= forma_renderer.order { |
| layer.disable(); |
| } |
| } |
| } |
| } |