Skip to main content

ass_renderer/pipeline/transform/
mod.rs

1//! Transform animation support for \t tags
2
3mod parse;
4
5#[cfg(feature = "nostd")]
6use alloc::vec::Vec;
7#[cfg(not(feature = "nostd"))]
8use std::vec::Vec;
9
10use parse::{apply_acceleration_curve, parse_animatable_tags};
11
12/// Transform animation data
13#[derive(Debug, Clone)]
14pub struct TransformAnimation {
15    /// Start time in milliseconds (relative to line start)
16    pub start_ms: u32,
17    /// End time in milliseconds (relative to line start)
18    pub end_ms: u32,
19    /// Acceleration factor (1.0 = linear, >1 = ease-in, <1 = ease-out)
20    pub accel: f32,
21    /// Tags to animate to
22    pub target_tags: Vec<AnimatableTag>,
23}
24
25/// Tags that can be animated
26#[derive(Debug, Clone)]
27pub enum AnimatableTag {
28    /// Font size
29    FontSize(f32),
30    /// Font scale X
31    FontScaleX(f32),
32    /// Font scale Y  
33    FontScaleY(f32),
34    /// Font spacing
35    FontSpacing(f32),
36    /// Font rotation Z
37    FontRotationZ(f32),
38    /// Font rotation X (3D)
39    FontRotationX(f32),
40    /// Font rotation Y (3D)
41    FontRotationY(f32),
42    /// Primary color
43    PrimaryColor([u8; 4]),
44    /// Secondary color
45    SecondaryColor([u8; 4]),
46    /// Outline color
47    OutlineColor([u8; 4]),
48    /// Shadow color
49    ShadowColor([u8; 4]),
50    /// Alpha
51    Alpha(u8),
52    /// Border width
53    BorderWidth(f32),
54    /// Shadow depth
55    ShadowDepth(f32),
56    /// Blur
57    Blur(f32),
58}
59
60impl TransformAnimation {
61    /// Parse transform tag arguments
62    pub fn parse(args: &str) -> Option<Self> {
63        // Transform can have formats:
64        // \t(tags) - animate over full duration
65        // \t(accel,tags) - with acceleration
66        // \t(t1,t2,tags) - with time range
67        // \t(t1,t2,accel,tags) - with time range and acceleration
68
69        let args = args.trim();
70        if !args.starts_with('(') || !args.ends_with(')') {
71            return None;
72        }
73
74        let inner = &args[1..args.len() - 1];
75
76        // Find where the override tags start (the first backslash). Everything
77        // before it is the numeric parameter list, which is empty for the
78        // `\t(<tags>)` form (tags begin at index 0). No backslash means there are
79        // no tags to animate, so this is not a transform.
80        let tag_start = inner.find('\\')?;
81
82        let params = &inner[..tag_start];
83        let tags = &inner[tag_start..];
84
85        // Parse parameters - need to be careful since last param might be tags not accel
86        let parts: Vec<&str> = params
87            .trim_end_matches(',')
88            .split(',')
89            .map(|s| s.trim())
90            .collect();
91
92        let (start_ms, end_ms, accel) =
93            if parts.is_empty() || (parts.len() == 1 && parts[0].is_empty()) {
94                // No parameters, just tags
95                (0, 0, 1.0)
96            } else if parts.len() == 1 {
97                // Could be acceleration or time1
98                if let Ok(val) = parts[0].parse::<f32>() {
99                    if val < 10.0 {
100                        // Likely acceleration (values typically 0.1 to 5)
101                        (0, 0, val)
102                    } else {
103                        // Likely time value
104                        (0, val as u32, 1.0)
105                    }
106                } else {
107                    (0, 0, 1.0)
108                }
109            } else if parts.len() == 2 {
110                // Two params: t1,t2
111                let t1 = parts[0].parse::<u32>().unwrap_or(0);
112                let t2 = parts[1].parse::<u32>().unwrap_or(0);
113                (t1, t2, 1.0)
114            } else {
115                // Three or more params: t1,t2,accel
116                let t1 = parts[0].parse::<u32>().unwrap_or(0);
117                let t2 = parts[1].parse::<u32>().unwrap_or(0);
118                // Only parse third param as accel if it's a valid float
119                let accel = parts[2].parse::<f32>().unwrap_or(1.0);
120                (t1, t2, accel)
121            };
122
123        // Parse target tags
124        let target_tags = parse_animatable_tags(tags);
125
126        if target_tags.is_empty() {
127            return None;
128        }
129
130        Some(TransformAnimation {
131            start_ms,
132            end_ms,
133            accel,
134            target_tags,
135        })
136    }
137
138    /// Calculate interpolation progress for the current time (all in milliseconds
139    /// relative to the event start).
140    ///
141    /// `full_duration_ms` is the event's duration: a `\t` with no explicit end time
142    /// (`\t(tags)` or `\t(accel,tags)`) animates across the whole event, matching
143    /// libass, rather than snapping straight to the target.
144    pub fn calculate_progress(&self, current_ms: u32, full_duration_ms: u32) -> f32 {
145        let end_ms = if self.end_ms > 0 {
146            self.end_ms
147        } else {
148            full_duration_ms
149        };
150
151        if current_ms <= self.start_ms {
152            return 0.0;
153        }
154        if end_ms <= self.start_ms || current_ms >= end_ms {
155            return 1.0;
156        }
157
158        let linear_progress = (current_ms - self.start_ms) as f32 / (end_ms - self.start_ms) as f32;
159
160        // Apply acceleration curve
161        apply_acceleration_curve(linear_progress, self.accel)
162    }
163}
164
165/// Interpolate between two values based on progress
166pub fn interpolate_f32(from: f32, to: f32, progress: f32) -> f32 {
167    from + (to - from) * progress
168}
169
170/// Interpolate between two colors
171pub fn interpolate_color(from: [u8; 4], to: [u8; 4], progress: f32) -> [u8; 4] {
172    [
173        (from[0] as f32 + (to[0] as f32 - from[0] as f32) * progress) as u8,
174        (from[1] as f32 + (to[1] as f32 - from[1] as f32) * progress) as u8,
175        (from[2] as f32 + (to[2] as f32 - from[2] as f32) * progress) as u8,
176        (from[3] as f32 + (to[3] as f32 - from[3] as f32) * progress) as u8,
177    ]
178}
179
180/// Interpolate alpha value
181pub fn interpolate_alpha(from: u8, to: u8, progress: f32) -> u8 {
182    (from as f32 + (to as f32 - from as f32) * progress) as u8
183}