1use std::sync::Arc;
10
11#[derive(Debug, Clone, PartialEq, Eq, Default)]
13pub struct Mask {
14 pub width: u32,
15 pub height: u32,
16 pub data: Vec<u8>,
17}
18
19impl Mask {
20 pub fn new(width: u32, height: u32) -> Self {
21 Self {
22 width,
23 height,
24 data: vec![0; width as usize * height as usize],
25 }
26 }
27
28 pub fn solid() -> Self {
30 Self {
31 width: 1,
32 height: 1,
33 data: vec![255],
34 }
35 }
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Default)]
40pub struct OverlayPart {
41 pub pos: [f32; 2],
43 pub size: Option<[f32; 2]>,
45 pub src: Option<[f32; 4]>,
47 pub colour: [f32; 4],
49}
50
51#[derive(Debug, Clone, PartialEq, Default)]
54pub struct Overlay {
55 pub mask: Arc<Mask>,
56 pub parts: Vec<OverlayPart>,
57 pub from: i64,
58 pub to: i64,
59 pub fade_in: f64,
60 pub fade_out: f64,
61 pub motion: Option<Arc<Motion>>,
63 pub under: Option<usize>,
65}
66
67#[derive(Debug, Clone, PartialEq, Default)]
70pub struct Motion {
71 pub first: i64,
72 pub frames: Vec<Vec<OverlayPart>>,
73}
74
75impl OverlayPart {
76 pub fn rects(&self, mask: &Mask) -> ([f32; 4], [f32; 4]) {
78 #[allow(clippy::cast_precision_loss)]
79 let src = self
80 .src
81 .unwrap_or([0.0, 0.0, mask.width as f32, mask.height as f32]);
82 let [w, h] = self.size.unwrap_or([src[2] - src[0], src[3] - src[1]]);
83 let [x, y] = self.pos;
84 (src, [x, y, x + w, y + h])
85 }
86}
87
88impl Overlay {
89 pub fn parts_at(&self, frame: i64) -> &[OverlayPart] {
91 match &self.motion {
92 Some(m) if !m.frames.is_empty() => {
93 let last = m.frames.len() - 1;
94 let i = usize::try_from(frame - m.first).map_or(0, |i| i.min(last));
95 &m.frames[i]
96 }
97 _ => &self.parts,
98 }
99 }
100
101 #[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
104 pub fn opacity(&self, frame: i64) -> f32 {
105 if frame < self.from || frame >= self.to {
106 return 0.0;
107 }
108 let t = (frame - self.from) as f64 + 0.5;
109 let left = (self.to - frame) as f64 - 0.5;
110 let a = if self.fade_in > 0.0 {
111 (t / self.fade_in).min(1.0)
112 } else {
113 1.0
114 };
115 let b = if self.fade_out > 0.0 {
116 (left / self.fade_out).min(1.0)
117 } else {
118 1.0
119 };
120 a.min(b).clamp(0.0, 1.0) as f32
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn opacity_fades_and_range() {
130 let o = Overlay {
131 mask: Arc::new(Mask::solid()),
132 parts: Vec::new(),
133 from: 10,
134 to: 20,
135 fade_in: 2.0,
136 fade_out: 4.0,
137 motion: None,
138 under: None,
139 };
140 assert!(o.opacity(9).abs() < f32::EPSILON);
141 assert!((o.opacity(10) - 0.25).abs() < 1e-6);
142 assert!((o.opacity(12) - 1.0).abs() < 1e-6);
143 assert!((o.opacity(19) - 0.125).abs() < 1e-6);
144 assert!(o.opacity(20).abs() < f32::EPSILON);
145 }
146
147 #[test]
148 fn motion_parts_and_crops() {
149 let part = |x: f32| OverlayPart {
150 pos: [x, 0.0],
151 ..OverlayPart::default()
152 };
153 let o = Overlay {
154 mask: Arc::new(Mask::new(8, 4)),
155 parts: vec![part(-1.0)],
156 from: 0,
157 to: 10,
158 motion: Some(Arc::new(Motion {
159 first: 2,
160 frames: vec![vec![part(2.0)], vec![part(3.0)]],
161 })),
162 ..Overlay::default()
163 };
164 assert_eq!(o.parts_at(0)[0].pos[0], 2.0);
165 assert_eq!(o.parts_at(3)[0].pos[0], 3.0);
166 assert_eq!(o.parts_at(9)[0].pos[0], 3.0);
167 let p = OverlayPart {
168 pos: [10.0, 20.0],
169 src: Some([2.0, 0.0, 6.0, 4.0]),
170 ..OverlayPart::default()
171 };
172 assert_eq!(
173 p.rects(&o.mask),
174 ([2.0, 0.0, 6.0, 4.0], [10.0, 20.0, 14.0, 24.0])
175 );
176 }
177}