1pub mod animatable;
2pub mod modifiers;
3pub mod renderer;
4#[cfg(feature = "expressions")]
5pub mod expressions;
6
7use animatable::Animator;
8#[cfg(feature = "expressions")]
9use expressions::ExpressionEvaluator;
10use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _};
11use glam::{Mat3, Mat4, Vec2, Vec3, Vec4};
12use kurbo::{BezPath, Point, Shape as _};
13use lottie_data::model::{self as data, LottieJson};
14use modifiers::{
15 GeometryModifier, OffsetPathModifier, PuckerBloatModifier, TwistModifier, WiggleModifier,
16 ZigZagModifier,
17};
18pub use renderer::*;
19use std::collections::{HashMap, HashSet};
20use std::f64::consts::PI;
21use std::sync::Arc;
22
23#[derive(Clone)]
24struct PendingGeometry {
25 kind: GeometryKind,
26 transform: Mat3,
27}
28
29#[derive(Clone)]
30enum GeometryKind {
31 Path(BezPath),
32 Rect { size: Vec2, pos: Vec2, radius: f32 },
33 Polystar(PolystarParams),
34 Ellipse { size: Vec2, pos: Vec2 },
35 Merge(Vec<PendingGeometry>, MergeMode),
36}
37
38impl PendingGeometry {
39 fn to_shape_geometry(&self, builder: &SceneGraphBuilder) -> ShapeGeometry {
40 match &self.kind {
41 GeometryKind::Merge(geoms, mode) => {
42 let shapes = geoms.iter().map(|g| g.to_shape_geometry(builder)).collect();
43 ShapeGeometry::Boolean {
44 mode: *mode,
45 shapes,
46 }
47 }
48 _ => ShapeGeometry::Path(self.to_path(builder)),
49 }
50 }
51
52 fn to_path(&self, builder: &SceneGraphBuilder) -> BezPath {
53 let mut path = match &self.kind {
54 GeometryKind::Path(p) => p.clone(),
55 GeometryKind::Merge(geoms, _) => {
56 let mut p = BezPath::new();
57 for g in geoms {
58 p.extend(g.to_path(builder));
59 }
60 p
61 }
62 GeometryKind::Rect { size, pos, radius } => {
63 let half = *size / 2.0;
64 let rect = kurbo::Rect::new(
65 (pos.x - half.x) as f64,
66 (pos.y - half.y) as f64,
67 (pos.x + half.x) as f64,
68 (pos.y + half.y) as f64,
69 );
70 if *radius > 0.0 {
71 rect.to_rounded_rect(*radius as f64).to_path(0.1)
72 } else {
73 rect.to_path(0.1)
74 }
75 }
76 GeometryKind::Ellipse { size, pos } => {
77 let half = *size / 2.0;
78 let ellipse = kurbo::Ellipse::new(
79 (pos.x as f64, pos.y as f64),
80 (half.x as f64, half.y as f64),
81 0.0,
82 );
83 ellipse.to_path(0.1)
84 }
85 GeometryKind::Polystar(params) => builder.generate_polystar_path(params),
86 };
87
88 let m = self.transform.to_cols_array();
89 let affine = kurbo::Affine::new([
90 m[0] as f64,
91 m[1] as f64,
92 m[3] as f64,
93 m[4] as f64,
94 m[6] as f64,
95 m[7] as f64,
96 ]);
97 path.apply_affine(affine);
98 path
99 }
100}
101
102#[derive(Clone, Copy)]
103struct PolystarParams {
104 pos: Vec2,
105 outer_radius: f32,
106 inner_radius: f32,
107 outer_roundness: f32,
108 inner_roundness: f32,
109 rotation: f32,
110 points: f32,
111 kind: u8, corner_radius: f32, }
114
115pub enum ImageSource {
116 Data(Vec<u8>), }
118
119pub trait TextMeasurer: Send + Sync {
120 fn measure(&self, text: &str, font_family: &str, size: f32) -> f32;
122}
123
124pub struct LottieAsset {
126 pub model: LottieJson,
127 pub width: f32,
128 pub height: f32,
129 pub duration_frames: f32,
130 pub frame_rate: f32,
131 pub assets: HashMap<String, ImageSource>,
132 pub text_measurer: Option<Box<dyn TextMeasurer>>,
133}
134
135impl LottieAsset {
136 pub fn from_model(model: LottieJson) -> Self {
137 let width = model.w as f32;
138 let height = model.h as f32;
139 let frame_rate = model.fr;
140 let duration_frames = model.op - model.ip;
141
142 Self {
143 model,
144 width,
145 height,
146 duration_frames,
147 frame_rate,
148 assets: HashMap::new(),
149 text_measurer: None,
150 }
151 }
152
153 pub fn set_text_measurer(&mut self, measurer: Box<dyn TextMeasurer>) {
154 self.text_measurer = Some(measurer);
155 }
156
157 pub fn set_asset(&mut self, id: String, data: Vec<u8>) {
158 self.assets.insert(id, ImageSource::Data(data));
159 }
160}
161
162pub struct LottiePlayer {
163 pub asset: Option<Arc<LottieAsset>>,
164 pub current_frame: f32,
165 #[cfg(feature = "expressions")]
166 pub expression_evaluator: Option<ExpressionEvaluator>,
167}
168
169impl LottiePlayer {
170 pub fn new() -> Self {
171 #[cfg(feature = "expressions")]
172 let expression_evaluator = Some(ExpressionEvaluator::new());
173 Self {
174 asset: None,
175 current_frame: 0.0,
176 #[cfg(feature = "expressions")]
177 expression_evaluator,
178 }
179 }
180
181 pub fn load(&mut self, asset: Arc<LottieAsset>) {
182 self.current_frame = asset.model.ip; self.asset = Some(asset);
184 }
185
186 pub fn load_json(&mut self, data: LottieJson) {
188 let asset = Arc::new(LottieAsset::from_model(data));
189 self.load(asset);
190 }
191
192 pub fn advance(&mut self, dt: f32) {
193 if let Some(asset) = &self.asset {
194 let frames = dt * asset.frame_rate;
196 self.current_frame += frames;
197
198 if self.current_frame >= asset.model.op {
200 let duration = asset.model.op - asset.model.ip;
201 self.current_frame = asset.model.ip
202 + (self.current_frame - asset.model.op) % duration;
203 }
204 }
205 }
206
207 pub fn render_tree(&mut self) -> RenderTree {
208 if let Some(asset) = &self.asset {
209 #[cfg(feature = "expressions")]
210 let evaluator = self.expression_evaluator.as_mut();
211 #[cfg(not(feature = "expressions"))]
212 let evaluator: Option<&mut ()> = None;
213
214 let mut builder = SceneGraphBuilder::new(
215 asset,
216 self.current_frame,
217 );
218 builder.build(evaluator)
219 } else {
220 RenderTree::mock_sample()
222 }
223 }
224}
225
226struct SceneGraphBuilder<'a> {
227 asset: &'a LottieAsset,
228 frame: f32,
229 model_assets_map: HashMap<String, &'a data::Asset>,
230}
231
232impl<'a> SceneGraphBuilder<'a> {
233 fn new(
234 asset: &'a LottieAsset,
235 frame: f32,
236 ) -> Self {
237 let mut model_assets_map = HashMap::new();
238 for a in &asset.model.assets {
239 model_assets_map.insert(a.id.clone(), a);
240 }
241 Self {
242 asset,
243 frame,
244 model_assets_map,
245 }
246 }
247
248 fn build(&mut self, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> RenderTree {
249 let mut layer_map = HashMap::new();
250 for layer in &self.asset.model.layers {
251 if let Some(ind) = layer.ind {
252 layer_map.insert(ind, layer);
253 }
254 }
255
256 let (view_matrix, projection_matrix) = self.get_camera_matrices(&self.asset.model.layers, &layer_map, evaluator.as_deref_mut());
257
258 let root_node = self.build_composition(&self.asset.model.layers, &layer_map, evaluator);
259
260 RenderTree {
261 width: self.asset.width,
262 height: self.asset.height,
263 root: root_node,
264 view_matrix,
265 projection_matrix,
266 }
267 }
268
269 fn get_camera_matrices(&self, layers: &'a [data::Layer], map: &HashMap<u32, &'a data::Layer>, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> (Mat4, Mat4) {
270 let mut camera_layer = None;
272 for layer in layers {
273 if layer.ty == 13 {
274 if self.frame >= layer.ip && self.frame < layer.op {
276 camera_layer = Some(layer);
277 break; }
279 }
280 }
281
282 if let Some(cam) = camera_layer {
283 let cam_transform = self.resolve_transform(cam, map, evaluator.as_deref_mut());
285 let view_matrix = cam_transform.inverse();
286
287 let pe = if let Some(prop) = &cam.pe {
289 Animator::resolve(prop, self.frame - cam.st, |v| *v, 0.0, evaluator, self.asset.frame_rate)
290 } else {
291 0.0
292 };
293
294 let perspective = if pe > 0.0 { pe } else { 1000.0 }; let fov = 2.0 * (self.asset.height / (2.0 * perspective)).atan();
301
302 let aspect = self.asset.width / self.asset.height;
303 let near = 0.1;
304 let far = 10000.0;
305
306 let projection_matrix = Mat4::perspective_rh(fov, aspect, near, far);
307
308 (view_matrix, projection_matrix)
309
310 } else {
311 (Mat4::IDENTITY, Mat4::IDENTITY)
313 }
314 }
315
316 fn build_composition(&mut self, layers: &'a [data::Layer], layer_map: &HashMap<u32, &'a data::Layer>, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> RenderNode {
317 let mut nodes = Vec::new();
318 let mut consumed_indices = HashSet::new();
319 let len = layers.len();
320
321 for i in (0..len).rev() {
322 if consumed_indices.contains(&i) {
323 continue;
324 }
325
326 let layer = &layers[i];
327
328 if let Some(tt) = layer.tt {
329 if i > 0 {
330 let matte_idx = i - 1;
331 if !consumed_indices.contains(&matte_idx) {
332 consumed_indices.insert(matte_idx);
333 let matte_layer = &layers[matte_idx];
334
335 if let Some(mut content_node) = self.process_layer(layer, layer_map, evaluator.as_deref_mut()) {
336 if let Some(matte_node) = self.process_layer(matte_layer, layer_map, evaluator.as_deref_mut()) {
337 let mode = match tt {
338 1 => MatteMode::Alpha,
339 2 => MatteMode::AlphaInverted,
340 3 => MatteMode::Luma,
341 4 => MatteMode::LumaInverted,
342 _ => MatteMode::Alpha,
343 };
344 content_node.matte = Some(Box::new(Matte {
345 mode,
346 node: matte_node,
347 }));
348 }
349 nodes.push(content_node);
350 }
351 continue;
352 }
353 }
354 }
355
356 if let Some(node) = self.process_layer(layer, layer_map, evaluator.as_deref_mut()) {
357 nodes.push(node);
358 }
359 }
360
361 RenderNode {
362 transform: Mat4::IDENTITY,
363 alpha: 1.0,
364 blend_mode: BlendMode::Normal,
365 content: NodeContent::Group(nodes),
366 masks: vec![], styles: vec![],
367 matte: None,
368 effects: vec![],
369 is_adjustment_layer: false,
370 }
371 }
372
373 fn process_layer(
374 &mut self,
375 layer: &'a data::Layer,
376 layer_map: &HashMap<u32, &'a data::Layer>,
377 #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>,
378 #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>,
379 ) -> Option<RenderNode> {
380 let is_adjustment_layer = layer.ao == Some(1);
381
382 if self.frame < layer.ip || self.frame >= layer.op {
383 return None;
384 }
385
386 let transform = self.resolve_transform(layer, layer_map, evaluator.as_deref_mut());
387
388 let opacity = Animator::resolve(&layer.ks.o, self.frame - layer.st, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
389
390 let content = if let Some(shapes) = &layer.shapes {
391 let shape_nodes = self.process_shapes(shapes, self.frame - layer.st, evaluator.as_deref_mut());
392 NodeContent::Group(shape_nodes)
393 } else if let Some(text_data) = &layer.t {
394 let doc = Animator::resolve(
396 &text_data.d,
397 self.frame - layer.st,
398 |v| v.clone(),
399 data::TextDocument::default(),
400 evaluator.as_deref_mut(), self.asset.frame_rate
401 );
402
403 let base_fill_color = Vec4::new(doc.fc[0], doc.fc[1], doc.fc[2], 1.0);
404 let base_stroke_color = if let Some(sc) = &doc.sc {
405 Some(Vec4::new(sc[0], sc[1], sc[2], 1.0))
406 } else {
407 None
408 };
409
410 let chars: Vec<char> = doc.t.chars().collect();
411 let char_count = chars.len();
412
413 let mut glyphs = Vec::with_capacity(char_count);
414
415 for &c in &chars {
416 let g = RenderGlyph {
417 character: c,
418 pos: Vec3::ZERO,
419 scale: Vec3::ONE,
420 rotation: Vec3::ZERO,
421 tracking: 0.0,
422 alpha: 1.0,
423 fill: Some(Fill {
424 paint: Paint::Solid(base_fill_color),
425 opacity: 1.0,
426 rule: FillRule::NonZero,
427 }),
428 stroke: if let Some(col) = base_stroke_color {
429 Some(Stroke {
430 paint: Paint::Solid(col),
431 width: doc.sw.unwrap_or(1.0),
432 opacity: 1.0,
433 cap: LineCap::Round,
434 join: LineJoin::Round,
435 miter_limit: None,
436 dash: None,
437 })
438 } else {
439 None
440 },
441 };
442 glyphs.push(g);
443 }
444
445 if let Some(animators) = &text_data.a {
446 for animator in animators {
447 let sel = &animator.s;
448 let start_val = Animator::resolve(
449 sel.s.as_ref().unwrap_or(&data::Property::default()),
450 self.frame - layer.st,
451 |v| *v,
452 0.0,
453 evaluator.as_deref_mut(), self.asset.frame_rate
454 );
455 let end_val = Animator::resolve(
456 sel.e.as_ref().unwrap_or(&data::Property::default()),
457 self.frame - layer.st,
458 |v| *v,
459 100.0,
460 evaluator.as_deref_mut(), self.asset.frame_rate
461 );
462 let offset_val = Animator::resolve(
463 sel.o.as_ref().unwrap_or(&data::Property::default()),
464 self.frame - layer.st,
465 |v| *v,
466 0.0,
467 evaluator.as_deref_mut(), self.asset.frame_rate
468 );
469
470 let start_idx = char_count as f32 * start_val / 100.0;
471 let end_idx = char_count as f32 * end_val / 100.0;
472 let offset_idx = char_count as f32 * offset_val / 100.0;
473
474 let style = &animator.a;
475 let p_delta = Animator::resolve(
476 style.p.as_ref().unwrap_or(&data::Property::default()),
477 self.frame - layer.st,
478 |v| Vec3::from(v.0),
479 Vec3::ZERO,
480 evaluator.as_deref_mut(), self.asset.frame_rate
481 );
482 let s_val = Animator::resolve(
483 style.s.as_ref().unwrap_or(&data::Property::default()),
484 self.frame - layer.st,
485 |v| Vec3::from(v.0) / 100.0,
486 Vec3::ONE,
487 evaluator.as_deref_mut(), self.asset.frame_rate
488 );
489 let o_val = Animator::resolve(
490 style.o.as_ref().unwrap_or(&data::Property::default()),
491 self.frame - layer.st,
492 |v| *v,
493 100.0,
494 evaluator.as_deref_mut(), self.asset.frame_rate
495 );
496 let r_val = Animator::resolve(
498 style.r.as_ref().unwrap_or(&data::Property::default()),
499 self.frame - layer.st,
500 |v| *v,
501 0.0,
502 evaluator.as_deref_mut(), self.asset.frame_rate
503 );
504
505 let t_val = Animator::resolve(
507 style.t.as_ref().unwrap_or(&data::Property::default()),
508 self.frame - layer.st,
509 |v| *v,
510 0.0,
511 evaluator.as_deref_mut(), self.asset.frame_rate
512 );
513
514 let fc_val = if let Some(fc_prop) = &style.fc {
515 Some(Animator::resolve(
516 fc_prop,
517 self.frame - layer.st,
518 |v| Vec4::from_slice(v),
519 Vec4::ONE,
520 evaluator.as_deref_mut(), self.asset.frame_rate
521 ))
522 } else {
523 None
524 };
525
526 let sc_val = if let Some(sc_prop) = &style.sc {
527 Some(Animator::resolve(
528 sc_prop,
529 self.frame - layer.st,
530 |v| Vec4::from_slice(v),
531 Vec4::ONE,
532 evaluator.as_deref_mut(), self.asset.frame_rate
533 ))
534 } else {
535 None
536 };
537
538 for (i, glyph) in glyphs.iter_mut().enumerate() {
539 let idx = i as f32;
540 let effective_start = start_idx + offset_idx;
541 let effective_end = end_idx + offset_idx;
542
543 let overlap_start = idx.max(effective_start);
544 let overlap_end = (idx + 1.0).min(effective_end);
545
546 let factor = (overlap_end - overlap_start).max(0.0).min(1.0);
547
548 if factor > 0.0 {
549 glyph.pos += p_delta * factor;
550
551 let scale_factor = Vec3::ONE + (s_val - Vec3::ONE) * factor;
553 glyph.scale *= scale_factor;
554
555 glyph.rotation.z += r_val.to_radians() * factor;
557
558 glyph.tracking += t_val * factor;
559
560 let target_alpha = o_val / 100.0;
561 let alpha_mult = 1.0 + (target_alpha - 1.0) * factor;
562 glyph.alpha *= alpha_mult;
563
564 if let Some(target_color) = fc_val {
565 if let Some(fill) = &mut glyph.fill {
566 if let Paint::Solid(current_color) = &mut fill.paint {
567 *current_color = current_color.lerp(target_color, factor);
568 }
569 }
570 }
571
572 if let Some(target_color) = sc_val {
573 if let Some(stroke) = &mut glyph.stroke {
574 if let Paint::Solid(current_color) = &mut stroke.paint {
575 *current_color = current_color.lerp(target_color, factor);
576 }
577 }
578 }
579 }
580 }
581 }
582 }
583
584 if let Some(measurer) = self.asset.text_measurer.as_deref() {
586 let box_size = doc.sz.map(|v| Vec2::from_slice(&v));
587 let box_pos = doc.ps.map(|v| Vec2::from_slice(&v)).unwrap_or(Vec2::ZERO);
588 let tracking_val = doc.tr;
589
590 if let Some(sz) = box_size {
591 let box_width = sz.x;
593 let mut lines: Vec<Vec<usize>> = Vec::new();
594 let mut current_line: Vec<usize> = Vec::new();
595 let mut current_line_width = 0.0;
596
597 let mut i = 0;
598 while i < glyphs.len() {
599 let start = i;
600 let mut end = i;
601 let mut word_width = 0.0;
602
603 while end < glyphs.len() {
604 let g = &glyphs[end];
605 let char_str = g.character.to_string();
606 let w = measurer.measure(&char_str, &doc.f, doc.s);
607 let advance = w + tracking_val + g.tracking;
608 word_width += advance;
609 let is_space = g.character == ' ';
610 let is_newline = g.character == '\n';
611 end += 1;
612 if is_space || is_newline {
613 break;
614 }
615 }
616
617 let is_newline = if end > 0 { glyphs[end-1].character == '\n' } else { false };
618
619 if is_newline {
620 for k in start..end { current_line.push(k); }
621 lines.push(current_line);
622 current_line = Vec::new();
623 current_line_width = 0.0;
624 } else {
625 if !current_line.is_empty() && current_line_width + word_width > box_width {
626 lines.push(current_line);
627 current_line = Vec::new();
628 current_line_width = 0.0;
629 }
630 for k in start..end { current_line.push(k); }
631 current_line_width += word_width;
632 }
633 i = end;
634 }
635 if !current_line.is_empty() { lines.push(current_line); }
636
637 let mut current_y = box_pos.y;
638 for line_indices in lines {
639 let mut line_width = 0.0;
640 let mut advances = Vec::new();
641
642 for &idx in &line_indices {
643 let g = &glyphs[idx];
644 let w = measurer.measure(&g.character.to_string(), &doc.f, doc.s);
645 let advance = w + tracking_val + g.tracking;
646 advances.push(advance);
647 line_width += advance;
648 }
649
650 let align_width = line_width;
651 let start_x = match doc.j {
652 1 => box_width - align_width,
653 2 => (box_width - align_width) / 2.0,
654 _ => 0.0,
655 };
656
657 let mut x = box_pos.x + start_x;
658 for (k, &idx) in line_indices.iter().enumerate() {
659 let g = &mut glyphs[idx];
660 g.pos += Vec3::new(x, current_y, 0.0);
661 x += advances[k];
662 }
663 current_y += doc.lh;
664 }
665
666 } else {
667 let mut current_y = 0.0;
669 let mut lines: Vec<Vec<usize>> = Vec::new();
670 let mut current_line = Vec::new();
671
672 for (i, g) in glyphs.iter().enumerate() {
673 if g.character == '\n' {
674 lines.push(current_line);
675 current_line = Vec::new();
676 } else {
677 current_line.push(i);
678 }
679 }
680 lines.push(current_line);
681
682 for line_indices in lines {
683 let mut line_width = 0.0;
684 let mut advances = Vec::new();
685 for &idx in &line_indices {
686 let g = &glyphs[idx];
687 let w = measurer.measure(&g.character.to_string(), &doc.f, doc.s);
688 let advance = w + tracking_val + g.tracking;
689 advances.push(advance);
690 line_width += advance;
691 }
692
693 let start_x = match doc.j {
694 1 => -line_width,
695 2 => -line_width / 2.0,
696 _ => 0.0,
697 };
698
699 let mut x = start_x;
700 for (k, &idx) in line_indices.iter().enumerate() {
701 let g = &mut glyphs[idx];
702 g.pos += Vec3::new(x, current_y, 0.0);
703 x += advances[k];
704 }
705 current_y += doc.lh;
706 }
707 }
708 } else {
709 let fixed_width = 10.0;
710 let mut x = 0.0;
711 let mut y = 0.0;
712 for g in &mut glyphs {
713 if g.character == '\n' {
714 x = 0.0;
715 y += doc.lh;
716 } else {
717 g.pos += Vec3::new(x, y, 0.0);
718 x += fixed_width + doc.tr + g.tracking;
719 }
720 }
721 }
722
723 NodeContent::Text(Text {
724 glyphs,
725 font_family: doc.f,
726 size: doc.s,
727 justify: match doc.j {
728 1 => Justification::Right,
729 2 => Justification::Center,
730 _ => Justification::Left,
731 },
732 tracking: doc.tr,
733 line_height: doc.lh,
734 })
735 } else if let Some(ref_id) = &layer.ref_id {
736 if let Some(asset) = self.model_assets_map.get(ref_id) {
737 if let Some(layers) = &asset.layers {
738 let local_frame = if let Some(tm_prop) = &layer.tm {
739 let tm_sec = Animator::resolve(tm_prop, self.frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
740 tm_sec * self.asset.frame_rate
741 } else {
742 self.frame - layer.st
743 };
744
745 let mut sub_layer_map = HashMap::new();
746 for l in layers {
747 if let Some(ind) = l.ind {
748 sub_layer_map.insert(ind, l);
749 }
750 }
751
752 let mut sub_builder = SceneGraphBuilder::new(
753 self.asset,
754 local_frame,
755 );
756 let root = sub_builder.build_composition(layers, &sub_layer_map, evaluator.as_deref_mut());
757 root.content
758 } else {
759 let data = if let Some(ImageSource::Data(bytes)) =
760 self.asset.assets.get(&asset.id)
761 {
762 Some(bytes.clone())
763 } else if let Some(p) = &asset.p {
764 if p.starts_with("data:image/") && p.contains(";base64,") {
765 let split: Vec<&str> = p.splitn(2, ',').collect();
766 if split.len() > 1 {
767 match BASE64_STANDARD.decode(split[1]) {
768 Ok(bytes) => Some(bytes),
769 Err(_) => None
770 }
771 } else {
772 None
773 }
774 } else {
775 if let Ok(bytes) = std::fs::read(p) {
776 Some(bytes)
777 } else {
778 None
779 }
780 }
781 } else {
782 None
783 };
784
785 NodeContent::Image(Image {
786 data,
787 width: asset.w.unwrap_or(100),
788 height: asset.h.unwrap_or(100),
789 id: Some(asset.id.clone()),
790 })
791 }
792 } else {
793 NodeContent::Group(vec![])
794 }
795 } else if let Some(color) = &layer.color {
796 let w = layer.sw.unwrap_or(100) as f64;
797 let h = layer.sh.unwrap_or(100) as f64;
798 let mut path = BezPath::new();
799 path.move_to((0.0, 0.0));
800 path.line_to((w, 0.0));
801 path.line_to((w, h));
802 path.line_to((0.0, h));
803 path.close_path();
804
805 let c_str = color.trim_start_matches('#');
806 let r = u8::from_str_radix(&c_str[0..2], 16).unwrap_or(0) as f32 / 255.0;
807 let g = u8::from_str_radix(&c_str[2..4], 16).unwrap_or(0) as f32 / 255.0;
808 let b = u8::from_str_radix(&c_str[4..6], 16).unwrap_or(0) as f32 / 255.0;
809
810 NodeContent::Shape(renderer::Shape {
811 geometry: renderer::ShapeGeometry::Path(path),
812 fill: Some(Fill {
813 paint: Paint::Solid(Vec4::new(r, g, b, 1.0)),
814 opacity: 1.0,
815 rule: FillRule::NonZero,
816 }),
817 stroke: None,
818 trim: None,
819 })
820 } else {
821 NodeContent::Group(vec![])
822 };
823
824 let masks = if let Some(props) = &layer.masks_properties {
825 self.process_masks(props, self.frame - layer.st, evaluator.as_deref_mut())
826 } else {
827 vec![]
828 };
829
830 let effects = self.process_effects(layer, evaluator.as_deref_mut());
831 let styles = self.process_layer_styles(layer, evaluator.as_deref_mut());
832
833 Some(RenderNode {
834 transform,
835 alpha: opacity,
836 blend_mode: BlendMode::Normal,
837 content,
838 masks,
839 matte: None,
840 effects,
841 styles,
842 is_adjustment_layer,
843 })
844 }
845
846 fn process_layer_styles(&self, layer: &data::Layer, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Vec<LayerStyle> {
847 let mut styles = Vec::new();
848 if let Some(sy_list) = &layer.sy {
849 for sy in sy_list {
850 let ty = sy.ty.unwrap_or(8);
851 let mut kind = None;
852 if ty == 0 { kind = Some("DropShadow"); }
853 else if ty == 1 { kind = Some("InnerShadow"); }
854 else if ty == 2 { kind = Some("OuterGlow"); }
855 else if let Some(nm) = &sy.nm {
856 if nm.contains("Stroke") { kind = Some("Stroke"); }
857 }
858
859 if kind.is_none() {
860 if ty == 3 || ty == 8 {
861 kind = Some("Stroke");
862 }
863 }
864
865 if let Some(k) = kind {
866 match k {
867 "DropShadow" => {
868 let color = self.resolve_json_vec4_arr(&sy.c, self.frame - layer.st, evaluator.as_deref_mut());
869 let opacity = Animator::resolve(&sy.o, self.frame - layer.st, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
870 let angle = Animator::resolve(&sy.a, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
871 let distance = Animator::resolve(&sy.d, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
872 let size = Animator::resolve(&sy.s, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
873 let spread = Animator::resolve(&sy.ch, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
874 styles.push(LayerStyle::DropShadow {
875 color, opacity, angle, distance, size, spread
876 });
877 },
878 "InnerShadow" => {
879 let color = self.resolve_json_vec4_arr(&sy.c, self.frame - layer.st, evaluator.as_deref_mut());
880 let opacity = Animator::resolve(&sy.o, self.frame - layer.st, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
881 let angle = Animator::resolve(&sy.a, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
882 let distance = Animator::resolve(&sy.d, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
883 let size = Animator::resolve(&sy.s, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
884 let choke = Animator::resolve(&sy.ch, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
885 styles.push(LayerStyle::InnerShadow {
886 color, opacity, angle, distance, size, choke
887 });
888 },
889 "OuterGlow" => {
890 let color = self.resolve_json_vec4_arr(&sy.c, self.frame - layer.st, evaluator.as_deref_mut());
891 let opacity = Animator::resolve(&sy.o, self.frame - layer.st, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
892 let size = Animator::resolve(&sy.s, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
893 let range = Animator::resolve(&sy.ch, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
894 styles.push(LayerStyle::OuterGlow {
895 color, opacity, size, range
896 });
897 },
898 "Stroke" => {
899 let color = self.resolve_json_vec4_arr(&sy.c, self.frame - layer.st, evaluator.as_deref_mut());
900 let opacity = Animator::resolve(&sy.o, self.frame - layer.st, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
901 let width = Animator::resolve(&sy.s, self.frame - layer.st, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
902 styles.push(LayerStyle::Stroke {
903 color, width, opacity
904 });
905 },
906 _ => {}
907 }
908 }
909 }
910 }
911 styles
912 }
913
914 fn process_effects(&self, layer: &data::Layer, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Vec<Effect> {
915 let mut effects = Vec::new();
916 if let Some(ef_list) = &layer.ef {
917 for ef in ef_list {
918 if let Some(en) = ef.en { if en == 0 { continue; } }
919 let ty = ef.ty.unwrap_or(0);
920 let values = if let Some(vals) = &ef.ef { vals } else { continue; };
921
922 match ty {
923 20 => {
924 let black = self.find_effect_vec4(values, 0, "Black", layer, evaluator.as_deref_mut());
925 let white = self.find_effect_vec4(values, 1, "White", layer, evaluator.as_deref_mut());
926 let amount = self.find_effect_scalar(values, 2, "Intensity", layer, evaluator.as_deref_mut()) / 100.0;
927 effects.push(Effect::Tint { black, white, amount });
928 }
929 21 => {
930 let color = self.find_effect_vec4(values, 2, "Color", layer, evaluator.as_deref_mut());
931 let opacity = self.find_effect_scalar(values, 6, "Opacity", layer, evaluator.as_deref_mut()) / 100.0;
932 effects.push(Effect::Fill { color, opacity });
933 }
934 22 => {
935 let color = self.find_effect_vec4(values, 3, "Color", layer, evaluator.as_deref_mut());
936 let width = self.find_effect_scalar(values, 4, "Brush Size", layer, evaluator.as_deref_mut());
937 let opacity = self.find_effect_scalar(values, 6, "Opacity", layer, evaluator.as_deref_mut()) / 100.0;
938 let all_masks_val = self.find_effect_scalar(values, 9999, "All Masks", layer, evaluator.as_deref_mut());
939 let all_masks = all_masks_val > 0.5;
940 let mut mask_idx_val = self.find_effect_scalar(values, 9999, "Path", layer, evaluator.as_deref_mut());
941 if mask_idx_val < 0.5 { mask_idx_val = self.find_effect_scalar(values, 9999, "Mask", layer, evaluator.as_deref_mut()); }
942 let mask_index = if mask_idx_val >= 0.5 { Some(mask_idx_val.round() as usize) } else { None };
943 effects.push(Effect::Stroke { color, width, opacity, mask_index, all_masks });
944 }
945 23 => {
946 let highlights = self.find_effect_vec4(values, 0, "bright", layer, evaluator.as_deref_mut());
947 let midtones = self.find_effect_vec4(values, 1, "mid", layer, evaluator.as_deref_mut());
948 let shadows = self.find_effect_vec4(values, 2, "dark", layer, evaluator.as_deref_mut());
949 effects.push(Effect::Tritone { highlights, midtones, shadows });
950 }
951 24 => {
952 let in_black = self.find_effect_scalar(values, 3, "inblack", layer, evaluator.as_deref_mut());
953 let in_white = self.find_effect_scalar(values, 4, "inwhite", layer, evaluator.as_deref_mut());
954 let gamma = self.find_effect_scalar(values, 5, "gamma", layer, evaluator.as_deref_mut());
955 let out_black = self.find_effect_scalar(values, 6, "outblack", layer, evaluator.as_deref_mut());
956 let out_white = self.find_effect_scalar(values, 7, "outwhite", layer, evaluator.as_deref_mut());
957 effects.push(Effect::Levels { in_black, in_white, gamma, out_black, out_white });
958 }
959 _ => {}
960 }
961 }
962 }
963 effects
964 }
965
966 fn find_effect_scalar(&self, values: &[data::EffectValue], index: usize, name_hint: &str, layer: &data::Layer, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> f32 {
967 if let Some(v) = values.get(index) {
968 if let Some(prop) = &v.v {
969 return self.resolve_json_scalar(prop, self.frame - layer.st, evaluator.as_deref_mut());
970 }
971 }
972 for v in values {
973 if let Some(nm) = &v.nm {
974 if nm.contains(name_hint) {
975 if let Some(prop) = &v.v {
976 return self.resolve_json_scalar(prop, self.frame - layer.st, evaluator.as_deref_mut());
977 }
978 }
979 }
980 }
981 0.0
982 }
983
984 fn find_effect_vec4(&self, values: &[data::EffectValue], index: usize, name_hint: &str, layer: &data::Layer, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Vec4 {
985 if let Some(v) = values.get(index) {
986 if let Some(prop) = &v.v {
987 return self.resolve_json_vec4(prop, self.frame - layer.st, evaluator.as_deref_mut());
988 }
989 }
990 for v in values {
991 if let Some(nm) = &v.nm {
992 if nm.contains(name_hint) {
993 if let Some(prop) = &v.v {
994 return self.resolve_json_vec4(prop, self.frame - layer.st, evaluator.as_deref_mut());
995 }
996 }
997 }
998 }
999 Vec4::ZERO
1000 }
1001
1002 fn resolve_json_scalar(&self, prop: &data::Property<serde_json::Value>, frame: f32, #[cfg(feature = "expressions")] evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> f32 {
1003 Animator::resolve(prop, frame, |v| {
1004 if let Some(n) = v.as_f64() { n as f32 }
1005 else if let Some(arr) = v.as_array() { arr.get(0).and_then(|x| x.as_f64()).unwrap_or(0.0) as f32 }
1006 else { 0.0 }
1007 }, 0.0, evaluator, self.asset.frame_rate)
1008 }
1009
1010 fn resolve_json_vec4(&self, prop: &data::Property<serde_json::Value>, frame: f32, #[cfg(feature = "expressions")] evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Vec4 {
1011 Animator::resolve(prop, frame, |v| {
1012 if let Some(arr) = v.as_array() {
1013 let r = arr.get(0).and_then(|x| x.as_f64()).unwrap_or(0.0) as f32;
1014 let g = arr.get(1).and_then(|x| x.as_f64()).unwrap_or(0.0) as f32;
1015 let b = arr.get(2).and_then(|x| x.as_f64()).unwrap_or(0.0) as f32;
1016 let a = arr.get(3).and_then(|x| x.as_f64()).unwrap_or(1.0) as f32;
1017 Vec4::new(r, g, b, a)
1018 } else { Vec4::ZERO }
1019 }, Vec4::ZERO, evaluator, self.asset.frame_rate)
1020 }
1021
1022 fn resolve_json_vec4_arr(&self, prop: &data::Property<Vec<f32>>, frame: f32, #[cfg(feature = "expressions")] evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Vec4 {
1023 Animator::resolve(prop, frame, |v| {
1024 if v.len() >= 4 { Vec4::new(v[0], v[1], v[2], v[3]) }
1025 else if v.len() >= 3 { Vec4::new(v[0], v[1], v[2], 1.0) }
1026 else { Vec4::ZERO }
1027 }, Vec4::ONE, evaluator, self.asset.frame_rate)
1028 }
1029
1030 fn resolve_transform(&self, layer: &data::Layer, map: &HashMap<u32, &data::Layer>, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Mat4 {
1031 let local = self.get_layer_transform(layer, evaluator.as_deref_mut());
1032 if let Some(parent_ind) = layer.parent {
1033 if let Some(parent) = map.get(&parent_ind) {
1034 return self.resolve_transform(parent, map, evaluator) * local;
1035 }
1036 }
1037 local
1038 }
1039
1040 fn get_layer_transform(&self, layer: &data::Layer, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Mat4 {
1041 let t_frame = self.frame - layer.st;
1042 let ks = &layer.ks;
1043
1044 let is_3d = layer.ddd.unwrap_or(0) == 1 || layer.ty == 13;
1045
1046 if layer.ty == 13 {
1048 let pos = match &ks.p {
1050 data::PositionProperty::Unified(p) => Animator::resolve(p, t_frame, |v| Vec3::from(v.0), Vec3::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate),
1051 data::PositionProperty::Split { x, y, z } => {
1052 let px = Animator::resolve(x, t_frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1053 let py = Animator::resolve(y, t_frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1054 let pz = if let Some(z_prop) = z { Animator::resolve(z_prop, t_frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate) } else { 0.0 };
1055 Vec3::new(px, py, pz)
1056 }
1057 };
1058
1059 let anchor = Animator::resolve(&ks.a, t_frame, |v| Vec3::from(v.0), Vec3::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1061
1062 let up = Vec3::new(0.0, -1.0, 0.0); let view = Mat4::look_at_rh(pos, anchor, up);
1075
1076 let rz = Animator::resolve(&ks.rz, t_frame, |v| v.to_radians(), 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1081 return view.inverse() * Mat4::from_rotation_z(rz);
1086 }
1087
1088 let mut anchor = Animator::resolve(&ks.a, t_frame, |v| Vec3::from(v.0), Vec3::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1089
1090 let mut pos = match &ks.p {
1091 data::PositionProperty::Unified(p) => {
1092 Animator::resolve(p, t_frame, |v| Vec3::from(v.0), Vec3::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate)
1093 }
1094 data::PositionProperty::Split { x, y, z } => {
1095 let px = Animator::resolve(x, t_frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1096 let py = Animator::resolve(y, t_frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1097 let pz = if let Some(z_prop) = z {
1098 Animator::resolve(z_prop, t_frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate)
1099 } else { 0.0 };
1100 Vec3::new(px, py, pz)
1101 }
1102 };
1103
1104 let scale = Animator::resolve(&ks.s, t_frame, |v| Vec3::from(v.0) / 100.0, Vec3::ONE, evaluator.as_deref_mut(), self.asset.frame_rate);
1105
1106 let rz = Animator::resolve(&ks.rz, t_frame, |v| v.to_radians(), 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1107 let mut rx = 0.0;
1108 let mut ry = 0.0;
1109 if let Some(p) = &ks.rx { rx = Animator::resolve(p, t_frame, |v| v.to_radians(), 0.0, evaluator.as_deref_mut(), self.asset.frame_rate); }
1110 if let Some(p) = &ks.ry { ry = Animator::resolve(p, t_frame, |v| v.to_radians(), 0.0, evaluator.as_deref_mut(), self.asset.frame_rate); }
1111
1112 let mut orientation = if let Some(or) = &ks.or {
1113 Animator::resolve(or, t_frame, |v| Vec3::from(v.0), Vec3::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate)
1114 } else {
1115 Vec3::ZERO
1116 };
1117
1118 if !is_3d {
1120 pos.z = 0.0;
1121 rx = 0.0;
1122 ry = 0.0;
1123 orientation = Vec3::ZERO; anchor.z = 0.0;
1126 }
1127
1128 let mat_t = Mat4::from_translation(pos);
1130
1131 let mat_or = Mat4::from_euler(glam::EulerRot::YXZ, orientation.y.to_radians(), orientation.x.to_radians(), orientation.z.to_radians());
1134
1135 let mat_rx = Mat4::from_rotation_x(rx);
1137 let mat_ry = Mat4::from_rotation_y(ry);
1138 let mat_rz = Mat4::from_rotation_z(rz);
1139
1140 let mat_r = mat_or * mat_rx * mat_ry * mat_rz;
1141
1142 let mat_s = Mat4::from_scale(scale);
1143 let mat_a = Mat4::from_translation(-anchor);
1144
1145 mat_t * mat_r * mat_s * mat_a
1146 }
1147
1148 fn get_shape_transform_2d(&self, ks: &data::Transform, frame: f32, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Mat3 {
1150 let anchor_3d = Animator::resolve(&ks.a, frame, |v| Vec3::from(v.0), Vec3::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1152 let anchor = Vec2::new(anchor_3d.x, anchor_3d.y);
1153
1154 let pos = match &ks.p {
1156 data::PositionProperty::Unified(p) => {
1157 let v3 = Animator::resolve(p, frame, |v| Vec3::from(v.0), Vec3::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1158 Vec2::new(v3.x, v3.y)
1159 }
1160 data::PositionProperty::Split { x, y, .. } => {
1161 let px = Animator::resolve(x, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1162 let py = Animator::resolve(y, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1163 Vec2::new(px, py)
1164 }
1165 };
1166
1167 let s3 = Animator::resolve(&ks.s, frame, |v| Vec3::from(v.0) / 100.0, Vec3::ONE, evaluator.as_deref_mut(), self.asset.frame_rate);
1169 let scale = Vec2::new(s3.x, s3.y);
1170
1171 let r = Animator::resolve(&ks.rz, frame, |v| v.to_radians(), 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1173
1174 let mat_t = Mat3::from_translation(pos);
1175 let mat_r = Mat3::from_rotation_z(r);
1176 let mat_s = Mat3::from_scale(scale);
1177 let mat_a = Mat3::from_translation(-anchor);
1178
1179 mat_t * mat_r * mat_s * mat_a
1180 }
1181
1182 fn process_shapes(&self, shapes: &'a [data::Shape], frame: f32, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Vec<RenderNode> {
1183 let mut processed_nodes = Vec::new();
1184 let mut active_geometries: Vec<PendingGeometry> = Vec::new();
1185
1186 let mut trim: Option<Trim> = None;
1187 for item in shapes {
1188 if let data::Shape::Trim(t) = item {
1189 let s = Animator::resolve(&t.s, frame, |v| *v / 100.0, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1190 let e = Animator::resolve(&t.e, frame, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1191 let o = Animator::resolve(&t.o, frame, |v| *v / 360.0, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1192 trim = Some(Trim { start: s, end: e, offset: o });
1193 }
1194 }
1195
1196 for item in shapes {
1197 match item {
1198 data::Shape::MergePaths(mp) => {
1199 if !active_geometries.is_empty() {
1200 let mode = match mp.mm {
1201 1 => MergeMode::Merge, 2 => MergeMode::Add, 3 => MergeMode::Subtract,
1202 4 => MergeMode::Intersect, 5 => MergeMode::Exclude, _ => MergeMode::Merge,
1203 };
1204 let merged = PendingGeometry {
1205 kind: GeometryKind::Merge(active_geometries.clone(), mode),
1206 transform: Mat3::IDENTITY,
1207 };
1208 active_geometries.clear();
1209 active_geometries.push(merged);
1210 }
1211 }
1212 data::Shape::Path(p) => {
1213 let path = self.convert_path(p, frame, evaluator.as_deref_mut());
1214 active_geometries.push(PendingGeometry {
1215 kind: GeometryKind::Path(path),
1216 transform: Mat3::IDENTITY,
1217 });
1218 }
1219 data::Shape::Rect(r) => {
1220 let size = Animator::resolve(&r.s, frame, |v| Vec2::from_slice(v), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1221 let pos = Animator::resolve(&r.p, frame, |v| Vec2::from_slice(v), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1222 let radius = Animator::resolve(&r.r, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1223 active_geometries.push(PendingGeometry {
1224 kind: GeometryKind::Rect { size, pos, radius },
1225 transform: Mat3::IDENTITY,
1226 });
1227 }
1228 data::Shape::Ellipse(e) => {
1229 let size = Animator::resolve(&e.s, frame, |v| Vec2::from_slice(v), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1230 let pos = Animator::resolve(&e.p, frame, |v| Vec2::from_slice(v), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1231 active_geometries.push(PendingGeometry {
1232 kind: GeometryKind::Ellipse { size, pos },
1233 transform: Mat3::IDENTITY,
1234 });
1235 }
1236 data::Shape::Polystar(sr) => {
1237 let pos = match &sr.p {
1238 data::PositionProperty::Unified(p) => Animator::resolve(p, 0.0, |v| Vec2::from_slice(&v.0[0..2]), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate),
1239 data::PositionProperty::Split { x, y, .. } => {
1240 let px = Animator::resolve(x, 0.0, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1241 let py = Animator::resolve(y, 0.0, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1242 Vec2::new(px, py)
1243 }
1244 };
1245 let or = Animator::resolve(&sr.or, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1246 let os = Animator::resolve(&sr.os, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1247 let r = Animator::resolve(&sr.r, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1248 let pt = Animator::resolve(&sr.pt, frame, |v| *v, 5.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1249 let ir = if let Some(prop) = &sr.ir { Animator::resolve(prop, 0.0, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate) } else { 0.0 };
1250 let is = if let Some(prop) = &sr.is { Animator::resolve(prop, 0.0, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate) } else { 0.0 };
1251
1252 active_geometries.push(PendingGeometry {
1253 kind: GeometryKind::Polystar(PolystarParams {
1254 pos, outer_radius: or, inner_radius: ir, outer_roundness: os,
1255 inner_roundness: is, rotation: r, points: pt, kind: sr.sy, corner_radius: 0.0,
1256 }),
1257 transform: Mat3::IDENTITY,
1258 });
1259 }
1260 data::Shape::Transform(tr) => {
1261 let local = self.get_shape_transform_2d(&tr.t, frame, evaluator.as_deref_mut());
1263 for geom in &mut active_geometries {
1264 geom.transform = local * geom.transform;
1265 }
1266 }
1267 data::Shape::RoundCorners(rd) => {
1268 let r = Animator::resolve(&rd.r, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1269 if r > 0.0 {
1270 for geom in &mut active_geometries {
1271 match &mut geom.kind {
1272 GeometryKind::Rect { radius, .. } => *radius += r,
1273 GeometryKind::Polystar(p) => p.corner_radius += r,
1274 _ => {}
1275 }
1276 }
1277 }
1278 }
1279 data::Shape::ZigZag(zz) => {
1280 let ridges = Animator::resolve(&zz.r, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1281 let size = Animator::resolve(&zz.s, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1282 let pt = Animator::resolve(&zz.pt, frame, |v| *v, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1283 let modifier = ZigZagModifier { ridges, size, smooth: pt > 1.5 };
1284 self.apply_modifier_to_active(&mut active_geometries, &modifier);
1285 }
1286 data::Shape::PuckerBloat(pb) => {
1287 let amount = Animator::resolve(&pb.a, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1288 let modifier = PuckerBloatModifier { amount, center: Vec2::ZERO };
1289 self.apply_modifier_to_active(&mut active_geometries, &modifier);
1290 }
1291 data::Shape::Twist(tw) => {
1292 let angle = Animator::resolve(&tw.a, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1293 let center = Animator::resolve(&tw.c, frame, |v| Vec2::from_slice(v), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1294 let modifier = TwistModifier { angle, center };
1295 self.apply_modifier_to_active(&mut active_geometries, &modifier);
1296 }
1297 data::Shape::OffsetPath(op) => {
1298 let amount = Animator::resolve(&op.a, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1299 let miter_limit = op.ml.unwrap_or(4.0);
1300 let line_join = op.lj;
1301 let modifier = OffsetPathModifier { amount, miter_limit, line_join };
1302 self.apply_modifier_to_active(&mut active_geometries, &modifier);
1303 }
1304 data::Shape::WigglePath(wg) => {
1305 let speed = Animator::resolve(&wg.s, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1306 let size = Animator::resolve(&wg.w, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1307 let correlation = Animator::resolve(&wg.r, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1308 let seed_prop = Animator::resolve(&wg.sh, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1309 let mut modifier = WiggleModifier { seed: seed_prop, time: frame / 60.0, speed: speed / self.asset.frame_rate, amount: size, correlation };
1310 modifier.time = frame;
1311 modifier.speed = speed / self.asset.frame_rate;
1312 self.apply_modifier_to_active(&mut active_geometries, &modifier);
1313 }
1314 data::Shape::Repeater(rp) => {
1315 let copies = Animator::resolve(&rp.c, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1316 let offset = Animator::resolve(&rp.o, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1317 let t_anchor_3d = Animator::resolve(&rp.tr.t.a, frame, |v| Vec3::from(v.0), Vec3::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1318 let t_anchor = Vec2::new(t_anchor_3d.x, t_anchor_3d.y);
1319
1320 let t_pos = match &rp.tr.t.p {
1321 data::PositionProperty::Unified(p) => Animator::resolve(p, 0.0, |v| Vec2::from_slice(&v.0[0..2]), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate),
1322 data::PositionProperty::Split { x, y, .. } => {
1323 let px = Animator::resolve(x, 0.0, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1324 let py = Animator::resolve(y, 0.0, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1325 Vec2::new(px, py)
1326 }
1327 };
1328 let t_scale_3d = Animator::resolve(&rp.tr.t.s, 0.0, |v| Vec3::from(v.0) / 100.0, Vec3::ONE, evaluator.as_deref_mut(), self.asset.frame_rate);
1329 let t_scale = Vec2::new(t_scale_3d.x, t_scale_3d.y);
1330
1331 let t_rot = Animator::resolve(&rp.tr.t.rz, frame, |v| v.to_radians(), 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1332
1333 let start_opacity = Animator::resolve(&rp.tr.so, frame, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1334 let end_opacity = Animator::resolve(&rp.tr.eo, frame, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1335
1336 self.apply_repeater(copies, offset, t_anchor, t_pos, t_scale, t_rot, start_opacity, end_opacity, &mut active_geometries, &mut processed_nodes);
1337 }
1338 data::Shape::Fill(f) => {
1339 let color = Animator::resolve(&f.c, frame, |v| Vec4::from_slice(v), Vec4::ONE, evaluator.as_deref_mut(), self.asset.frame_rate);
1340 let opacity = Animator::resolve(&f.o, frame, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1341 for geom in &active_geometries {
1342 let path = self.convert_geometry(geom);
1343 processed_nodes.push(RenderNode {
1344 transform: Mat4::IDENTITY,
1345 alpha: 1.0, blend_mode: BlendMode::Normal,
1346 content: NodeContent::Shape(renderer::Shape {
1347 geometry: path,
1348 fill: Some(Fill { paint: Paint::Solid(color), opacity, rule: FillRule::NonZero }),
1349 stroke: None, trim: trim.clone(),
1350 }),
1351 masks: vec![], styles: vec![], matte: None, effects: vec![], is_adjustment_layer: false,
1352 });
1353 }
1354 }
1355 data::Shape::GradientFill(gf) => {
1356 let start = Animator::resolve(&gf.s, frame, |v| Vec2::from_slice(v), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1357 let end = Animator::resolve(&gf.e, frame, |v| Vec2::from_slice(v), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1358 let opacity = Animator::resolve(&gf.o, frame, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1359 let raw_stops = Animator::resolve(&gf.g.k, frame, |v| v.clone(), Vec::new(), evaluator.as_deref_mut(), self.asset.frame_rate);
1360 let stops = parse_gradient_stops(&raw_stops, gf.g.p as usize);
1361 let kind = if gf.t == 1 { GradientKind::Linear } else { GradientKind::Radial };
1362 for geom in &active_geometries {
1363 let path = self.convert_geometry(geom);
1364 processed_nodes.push(RenderNode {
1365 transform: Mat4::IDENTITY,
1366 alpha: 1.0, blend_mode: BlendMode::Normal,
1367 content: NodeContent::Shape(renderer::Shape {
1368 geometry: path,
1369 fill: Some(Fill { paint: Paint::Gradient(Gradient { kind, stops: stops.clone(), start, end }), opacity, rule: FillRule::NonZero }),
1370 stroke: None, trim: trim.clone(),
1371 }),
1372 masks: vec![], styles: vec![], matte: None, effects: vec![], is_adjustment_layer: false,
1373 });
1374 }
1375 }
1376 data::Shape::GradientStroke(gs) => {
1377 let start = Animator::resolve(&gs.s, frame, |v| Vec2::from_slice(v), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1378 let end = Animator::resolve(&gs.e, frame, |v| Vec2::from_slice(v), Vec2::ZERO, evaluator.as_deref_mut(), self.asset.frame_rate);
1379 let width = Animator::resolve(&gs.w, frame, |v| *v, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1380 let opacity = Animator::resolve(&gs.o, frame, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1381 let raw_stops = Animator::resolve(&gs.g.k, frame, |v| v.clone(), Vec::new(), evaluator.as_deref_mut(), self.asset.frame_rate);
1382 let stops = parse_gradient_stops(&raw_stops, gs.g.p as usize);
1383 let kind = if gs.t == 1 { GradientKind::Linear } else { GradientKind::Radial };
1384 let dash = self.resolve_dash(&gs.d, frame, evaluator.as_deref_mut());
1385 for geom in &active_geometries {
1386 let path = self.convert_geometry(geom);
1387 processed_nodes.push(RenderNode {
1388 transform: Mat4::IDENTITY,
1389 alpha: 1.0, blend_mode: BlendMode::Normal,
1390 content: NodeContent::Shape(renderer::Shape {
1391 geometry: path,
1392 fill: None,
1393 stroke: Some(Stroke { paint: Paint::Gradient(Gradient { kind, stops: stops.clone(), start, end }), width, opacity, cap: match gs.lc { 1 => LineCap::Butt, 3 => LineCap::Square, _ => LineCap::Round }, join: match gs.lj { 1 => LineJoin::Miter, 3 => LineJoin::Bevel, _ => LineJoin::Round }, miter_limit: gs.ml, dash: dash.clone() }),
1394 trim: trim.clone(),
1395 }),
1396 masks: vec![], styles: vec![], matte: None, effects: vec![], is_adjustment_layer: false,
1397 });
1398 }
1399 }
1400 data::Shape::Stroke(s) => {
1401 let color = Animator::resolve(&s.c, frame, |v| Vec4::from_slice(v), Vec4::ONE, evaluator.as_deref_mut(), self.asset.frame_rate);
1402 let width = Animator::resolve(&s.w, frame, |v| *v, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1403 let opacity = Animator::resolve(&s.o, frame, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1404 let dash = self.resolve_dash(&s.d, frame, evaluator.as_deref_mut());
1405 for geom in &active_geometries {
1406 let path = self.convert_geometry(geom);
1407 processed_nodes.push(RenderNode {
1408 transform: Mat4::IDENTITY,
1409 alpha: 1.0, blend_mode: BlendMode::Normal,
1410 content: NodeContent::Shape(renderer::Shape {
1411 geometry: path,
1412 fill: None,
1413 stroke: Some(Stroke { paint: Paint::Solid(color), width, opacity, cap: match s.lc { 1 => LineCap::Butt, 3 => LineCap::Square, _ => LineCap::Round }, join: match s.lj { 1 => LineJoin::Miter, 3 => LineJoin::Bevel, _ => LineJoin::Round }, miter_limit: s.ml, dash: dash.clone() }),
1414 trim: trim.clone(),
1415 }),
1416 masks: vec![], styles: vec![], matte: None, effects: vec![], is_adjustment_layer: false,
1417 });
1418 }
1419 }
1420 data::Shape::Group(g) => {
1421 let group_nodes = self.process_shapes(&g.it, frame, evaluator.as_deref_mut());
1422 processed_nodes.push(RenderNode {
1423 transform: Mat4::IDENTITY,
1424 alpha: 1.0, blend_mode: BlendMode::Normal,
1425 content: NodeContent::Group(group_nodes),
1426 masks: vec![], styles: vec![], matte: None, effects: vec![], is_adjustment_layer: false,
1427 });
1428 }
1429 _ => {}
1430 }
1431 }
1432 processed_nodes
1433 }
1434
1435 fn resolve_dash(&self, props: &[data::DashProperty], frame: f32, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Option<DashPattern> {
1436 if props.is_empty() { return None; }
1437 let mut array = Vec::new();
1438 let mut offset = 0.0;
1439 for prop in props {
1440 match prop.n.as_deref() {
1441 Some("o") => offset = Animator::resolve(&prop.v, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate),
1442 Some("d") | Some("v") | Some("g") => array.push(Animator::resolve(&prop.v, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate)),
1443 _ => {}
1444 }
1445 }
1446 if !array.is_empty() {
1447 if array.len() % 2 != 0 { let clone = array.clone(); array.extend(clone); }
1448 let total: f32 = array.iter().sum();
1449 if total > 0.0 { offset = (offset % total + total) % total; } else { offset = 0.0; }
1450 Some(DashPattern { array, offset })
1451 } else {
1452 None
1453 }
1454 }
1455
1456 fn apply_repeater(
1457 &self,
1458 copies: f32,
1459 _offset: f32,
1460 anchor: Vec2,
1461 pos: Vec2,
1462 scale: Vec2,
1463 rot: f32,
1464 start_op: f32,
1465 end_op: f32,
1466 geoms: &mut Vec<PendingGeometry>,
1467 nodes: &mut Vec<RenderNode>,
1468 ) {
1469 let num_copies = copies.round() as usize;
1470 if num_copies <= 1 { return; }
1471
1472 let original_geoms = geoms.clone();
1473 let original_nodes = nodes.clone();
1474
1475 let mat_t = Mat3::from_translation(pos);
1476 let mat_r = Mat3::from_rotation_z(rot); let mat_s = Mat3::from_scale(scale);
1478 let mat_a = Mat3::from_translation(-anchor);
1479 let mat_pre_a = Mat3::from_translation(anchor);
1480
1481 let pivot_transform = mat_pre_a * mat_r * mat_s * mat_a;
1482 let step_transform = mat_t * pivot_transform;
1483
1484 let mat_t4 = Mat4::from_translation(Vec3::new(pos.x, pos.y, 0.0));
1488 let mat_r4 = Mat4::from_rotation_z(rot);
1489 let mat_s4 = Mat4::from_scale(Vec3::new(scale.x, scale.y, 1.0));
1490 let mat_a4 = Mat4::from_translation(Vec3::new(-anchor.x, -anchor.y, 0.0));
1491 let mat_pre_a4 = Mat4::from_translation(Vec3::new(anchor.x, anchor.y, 0.0));
1492 let step_transform4 = mat_t4 * mat_pre_a4 * mat_r4 * mat_s4 * mat_a4;
1493
1494 geoms.clear();
1495 nodes.clear();
1496
1497 for i in 0..num_copies {
1498 let t = if num_copies > 1 { i as f32 / (num_copies as f32 - 1.0) } else { 0.0 };
1499 let op = start_op + (end_op - start_op) * t;
1500
1501 let mut copy_transform = Mat3::IDENTITY;
1502 let mut copy_transform4 = Mat4::IDENTITY;
1503 for _ in 0..i {
1504 copy_transform = copy_transform * step_transform;
1505 copy_transform4 = copy_transform4 * step_transform4;
1506 }
1507
1508 for geom in &original_geoms {
1509 let mut g = geom.clone();
1510 g.transform = copy_transform * g.transform;
1511 geoms.push(g);
1512 }
1513
1514 for node in &original_nodes {
1515 let mut n = node.clone();
1516 n.transform = copy_transform4 * n.transform;
1517 n.alpha *= op;
1518 nodes.push(n);
1519 }
1520 }
1521 }
1522
1523 fn apply_modifier_to_active(
1524 &self,
1525 active: &mut Vec<PendingGeometry>,
1526 modifier: &impl GeometryModifier,
1527 ) {
1528 for geom in active.iter_mut() {
1529 let mut path = geom.to_path(self);
1530 modifier.modify(&mut path);
1531 geom.transform = Mat3::IDENTITY;
1532 geom.kind = GeometryKind::Path(path);
1533 }
1534 }
1535
1536 fn convert_geometry(&self, geom: &PendingGeometry) -> ShapeGeometry {
1537 geom.to_shape_geometry(self)
1538 }
1539
1540 fn generate_polystar_path(&self, params: &PolystarParams) -> BezPath {
1541 let mut path = BezPath::new();
1542 let num_points = params.points.round();
1543 if num_points < 3.0 { return path; }
1544
1545 let is_star = params.kind == 1;
1546 let has_roundness = params.outer_roundness.abs() > 0.01 || (is_star && params.inner_roundness.abs() > 0.01);
1547 let total_points = if is_star { num_points * 2.0 } else { num_points } as usize;
1548 let current_angle = (params.rotation - 90.0).to_radians();
1549 let angle_step = 2.0 * PI / total_points as f64;
1550
1551 if has_roundness {
1552 let mut elements = Vec::with_capacity(total_points);
1553 for i in 0..total_points {
1554 let (r, roundness) = if is_star {
1555 if i % 2 == 0 { (params.outer_radius, params.outer_roundness) } else { (params.inner_radius, params.inner_roundness) }
1556 } else {
1557 (params.outer_radius, params.outer_roundness)
1558 };
1559
1560 let angle = current_angle as f64 + angle_step * i as f64;
1561 let cos_a = angle.cos();
1562 let sin_a = angle.sin();
1563 let x = params.pos.x as f64 + r as f64 * cos_a;
1564 let y = params.pos.y as f64 + r as f64 * sin_a;
1565 let vertex = Point::new(x, y);
1566
1567 let tx = -sin_a; let ty = cos_a;
1568 let tangent = kurbo::Vec2::new(tx, ty);
1569 let cp_d = r as f64 * angle_step * roundness as f64 * 0.01;
1570 let in_cp = vertex - tangent * cp_d;
1571 let out_cp = vertex + tangent * cp_d;
1572 elements.push((vertex, in_cp, out_cp));
1573 }
1574 if elements.is_empty() { return path; }
1575 path.move_to(elements[0].0);
1576 let len = elements.len();
1577 for i in 0..len {
1578 let curr_idx = i;
1579 let next_idx = (i + 1) % len;
1580 let curr_out_cp = elements[curr_idx].2;
1581 let next_in_cp = elements[next_idx].1;
1582 let next_vertex = elements[next_idx].0;
1583 path.curve_to(curr_out_cp, next_in_cp, next_vertex);
1584 }
1585 path.close_path();
1586 return path;
1587 }
1588
1589 let mut vertices = Vec::with_capacity(total_points);
1590 for i in 0..total_points {
1591 let r = if is_star { if i % 2 == 0 { params.outer_radius } else { params.inner_radius } } else { params.outer_radius };
1592 let angle = current_angle as f64 + angle_step * i as f64;
1593 let x = params.pos.x as f64 + r as f64 * angle.cos();
1594 let y = params.pos.y as f64 + r as f64 * angle.sin();
1595 vertices.push(Point::new(x, y));
1596 }
1597
1598 let radius = params.corner_radius as f64;
1599 if radius <= 0.1 {
1600 if !vertices.is_empty() {
1601 path.move_to(vertices[0]);
1602 for v in vertices.iter().skip(1) { path.line_to(*v); }
1603 path.close_path();
1604 }
1605 return path;
1606 }
1607
1608 let len = vertices.len();
1609 for i in 0..len {
1610 let prev = vertices[(i + len - 1) % len];
1611 let curr = vertices[i];
1612 let next = vertices[(i + 1) % len];
1613 let v1 = prev - curr;
1614 let v2 = next - curr;
1615 let len1 = v1.hypot();
1616 let len2 = v2.hypot();
1617
1618 if len1 < 0.001 || len2 < 0.001 {
1619 if i == 0 { path.move_to(curr); } else { path.line_to(curr); }
1620 continue;
1621 }
1622
1623 let u1 = v1 * (1.0 / len1);
1624 let u2 = v2 * (1.0 / len2);
1625 let dot = (u1.x * u2.x + u1.y * u2.y).clamp(-1.0, 1.0);
1626 let angle = dot.acos();
1627 let dist = if angle.abs() < 0.001 { 0.0 } else { radius / (angle / 2.0).tan() };
1628 let max_d = (len1.min(len2)) * 0.5;
1629 let d = dist.min(max_d);
1630 let p_start = curr + u1 * d;
1631 let p_end = curr + u2 * d;
1632
1633 if i == 0 { path.move_to(p_start); } else { path.line_to(p_start); }
1634 path.quad_to(curr, p_end);
1635 }
1636 path.close_path();
1637 path
1638 }
1639
1640 fn convert_path(&self, p: &data::PathShape, frame: f32, #[cfg(feature = "expressions")] evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> BezPath {
1641 let path_data = Animator::resolve(&p.ks, frame, |v| v.clone(), data::BezierPath::default(), evaluator, self.asset.frame_rate);
1642 self.convert_bezier_path(&path_data)
1643 }
1644
1645 fn convert_bezier_path(&self, path_data: &data::BezierPath) -> BezPath {
1646 let mut bp = BezPath::new();
1647 if path_data.v.is_empty() { return bp; }
1648 let start = path_data.v[0];
1649 bp.move_to(Point::new(start[0] as f64, start[1] as f64));
1650 for i in 0..path_data.v.len() {
1651 let next_idx = (i + 1) % path_data.v.len();
1652 if next_idx == 0 && !path_data.c { break; }
1653 let p0 = path_data.v[i];
1654 let p1 = path_data.v[next_idx];
1655 let o = if i < path_data.o.len() { path_data.o[i] } else { [0.0, 0.0] };
1656 let in_ = if next_idx < path_data.i.len() { path_data.i[next_idx] } else { [0.0, 0.0] };
1657 let cp1 = [p0[0] + o[0], p0[1] + o[1]];
1658 let cp2 = [p1[0] + in_[0], p1[1] + in_[1]];
1659 bp.curve_to(Point::new(cp1[0] as f64, cp1[1] as f64), Point::new(cp2[0] as f64, cp2[1] as f64), Point::new(p1[0] as f64, p1[1] as f64));
1660 }
1661 if path_data.c { bp.close_path(); }
1662 bp
1663 }
1664
1665 fn process_masks(&self, masks_props: &[data::MaskProperties], frame: f32, #[cfg(feature = "expressions")] mut evaluator: Option<&mut ExpressionEvaluator>, #[cfg(not(feature = "expressions"))] mut evaluator: Option<&mut ()>) -> Vec<Mask> {
1666 let mut masks = Vec::new();
1667 for m in masks_props {
1668 let mode = match m.mode.as_deref() {
1669 Some("n") => MaskMode::None, Some("a") => MaskMode::Add, Some("s") => MaskMode::Subtract,
1670 Some("i") => MaskMode::Intersect, Some("l") => MaskMode::Lighten, Some("d") => MaskMode::Darken,
1671 Some("f") => MaskMode::Difference, _ => continue,
1672 };
1673 let path_data = Animator::resolve(&m.pt, frame, |v| v.clone(), data::BezierPath::default(), evaluator.as_deref_mut(), self.asset.frame_rate);
1674 let geometry = self.convert_bezier_path(&path_data);
1675 let opacity = Animator::resolve(&m.o, frame, |v| *v / 100.0, 1.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1676 let expansion = Animator::resolve(&m.x, frame, |v| *v, 0.0, evaluator.as_deref_mut(), self.asset.frame_rate);
1677 let inverted = m.inv;
1678 masks.push(Mask { mode, geometry, opacity, expansion, inverted });
1679 }
1680 masks
1681 }
1682}
1683
1684struct ColorStop { t: f32, r: f32, g: f32, b: f32 }
1686struct AlphaStop { t: f32, a: f32 }
1687
1688fn parse_gradient_stops(raw: &[f32], color_count: usize) -> Vec<GradientStop> {
1689 let mut stops = Vec::new();
1690 if raw.is_empty() { return stops; }
1691 let mut color_stops = Vec::new();
1692 let mut alpha_stops = Vec::new();
1693 let color_data_len = color_count * 4;
1694 for chunk in raw.iter().take(color_data_len).collect::<Vec<_>>().chunks(4) {
1695 if chunk.len() == 4 { color_stops.push(ColorStop { t: *chunk[0], r: *chunk[1], g: *chunk[2], b: *chunk[3] }); }
1696 }
1697 if raw.len() > color_data_len {
1698 for chunk in raw[color_data_len..].chunks(2) {
1699 if chunk.len() == 2 { alpha_stops.push(AlphaStop { t: chunk[0], a: chunk[1] }); }
1700 }
1701 }
1702 if alpha_stops.is_empty() {
1703 for c in color_stops { stops.push(GradientStop { offset: c.t, color: Vec4::new(c.r, c.g, c.b, 1.0) }); }
1704 return stops;
1705 }
1706 let mut unique_t: Vec<f32> = Vec::new();
1707 for c in &color_stops { unique_t.push(c.t); }
1708 for a in &alpha_stops { unique_t.push(a.t); }
1709 unique_t.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1710 unique_t.dedup();
1711 for t in unique_t {
1712 let (r, g, b) = interpolate_color(&color_stops, t);
1713 let a = interpolate_alpha(&alpha_stops, t);
1714 stops.push(GradientStop { offset: t, color: Vec4::new(r, g, b, a) });
1715 }
1716 stops
1717}
1718
1719fn interpolate_color(stops: &[ColorStop], t: f32) -> (f32, f32, f32) {
1720 if stops.is_empty() { return (1.0, 1.0, 1.0); }
1721 if t <= stops[0].t { return (stops[0].r, stops[0].g, stops[0].b); }
1722 if t >= stops.last().unwrap().t { let last = stops.last().unwrap(); return (last.r, last.g, last.b); }
1723 for i in 0..stops.len() - 1 {
1724 let s1 = &stops[i];
1725 let s2 = &stops[i + 1];
1726 if t >= s1.t && t <= s2.t {
1727 let range = s2.t - s1.t;
1728 let ratio = if range == 0.0 { 0.0 } else { (t - s1.t) / range };
1729 return (s1.r + (s2.r - s1.r) * ratio, s1.g + (s2.g - s1.g) * ratio, s1.b + (s2.b - s1.b) * ratio);
1730 }
1731 }
1732 (1.0, 1.0, 1.0)
1733}
1734
1735fn interpolate_alpha(stops: &[AlphaStop], t: f32) -> f32 {
1736 if stops.is_empty() { return 1.0; }
1737 if t <= stops[0].t { return stops[0].a; }
1738 if t >= stops.last().unwrap().t { return stops.last().unwrap().a; }
1739 for i in 0..stops.len() - 1 {
1740 let s1 = &stops[i];
1741 let s2 = &stops[i + 1];
1742 if t >= s1.t && t <= s2.t {
1743 let range = s2.t - s1.t;
1744 let ratio = if range == 0.0 { 0.0 } else { (t - s1.t) / range };
1745 return s1.a + (s2.a - s1.a) * ratio;
1746 }
1747 }
1748 1.0
1749}
1750
1751#[cfg(test)]
1752mod tests {
1753 use super::*;
1754 use lottie_data::model as data;
1755
1756 #[test]
1757 fn test_camera_transform() {
1758 let camera_layer = data::Layer {
1759 ty: 13,
1760 ind: Some(1),
1761 parent: None,
1762 nm: Some("Camera".to_string()),
1763 ip: 0.0,
1764 op: 60.0,
1765 st: 0.0,
1766 ks: data::Transform {
1767 p: data::PositionProperty::Unified(data::Property {
1768 k: data::Value::Static(data::Vec3DefaultZero([0.0, 0.0, -500.0])),
1769 ..Default::default()
1770 }),
1771 a: data::Property {
1772 k: data::Value::Static(data::Vec3DefaultZero([0.0, 0.0, 0.0])),
1773 ..Default::default()
1774 },
1775 ..Default::default()
1776 },
1777 pe: Some(data::Property {
1778 k: data::Value::Static(1000.0),
1779 ..Default::default()
1780 }),
1781 ddd: Some(1),
1782 ao: None, tm: None, masks_properties: None, tt: None, ef: None, sy: None,
1783 ref_id: None, w: None, h: None, color: None, sw: None, sh: None,
1784 shapes: None, t: None,
1785 };
1786
1787 let model = LottieJson {
1788 v: None, ip: 0.0, op: 60.0, fr: 60.0, w: 1000, h: 1000,
1789 layers: vec![camera_layer],
1790 assets: vec![],
1791 };
1792
1793 let mut player = LottiePlayer::new();
1794 player.load_json(model);
1795 let tree = player.render_tree();
1796
1797 let vm = tree.view_matrix;
1798 let p_world = Vec4::new(0.0, 0.0, 0.0, 1.0);
1800 let p_view = vm * p_world;
1801
1802 assert!((p_view.z - (-500.0)).abs() < 1.0, "Expected Z=-500, got {}", p_view.z);
1806 }
1807}