blob: df1ecb9b04fd14efa0045204e084cd57053eb593 [file] [log] [blame]
// 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();
}
}
}
}