motion_canvas_rs/core/animation/
paint.rs1use crate::core::animation::tween::Tweenable;
2use kurbo::Point;
3use peniko::{Brush, Color, ColorStop, ColorStops, Gradient, GradientKind};
4
5pub const DEFAULT_GRADIENT_LENGTH: f64 = 100.0;
7
8#[derive(Clone, Debug, PartialEq)]
13pub enum Paint {
14 None,
16 Solid(Color),
18 Gradient(Gradient),
20}
21
22impl Paint {
23 pub fn to_brush(&self) -> Brush {
25 match self {
26 Paint::None => Brush::Solid(Color::TRANSPARENT),
27 Paint::Solid(color) => Brush::Solid(*color),
28 Paint::Gradient(grad) => Brush::Gradient(grad.clone()),
29 }
30 }
31
32 pub fn to_brush_with_opacity(&self, opacity: f32) -> Brush {
34 match self {
35 Paint::None => Brush::Solid(Color::TRANSPARENT),
36 Paint::Solid(color) => {
37 let mut c = *color;
38 c.a = (color.a as f32 * opacity).clamp(0.0, 255.0) as u8;
39 Brush::Solid(c)
40 }
41 Paint::Gradient(grad) => {
42 let mut stops = Vec::new();
43 for stop in grad.stops.iter() {
44 let mut c = stop.color;
45 c.a = (stop.color.a as f32 * opacity).clamp(0.0, 255.0) as u8;
46 stops.push(ColorStop {
47 offset: stop.offset,
48 color: c,
49 });
50 }
51 Brush::Gradient(Gradient {
52 kind: grad.kind.clone(),
53 extend: grad.extend,
54 stops: ColorStops::from(stops),
55 })
56 }
57 }
58 }
59}
60
61impl From<Color> for Paint {
62 fn from(c: Color) -> Self {
63 Paint::Solid(c)
64 }
65}
66
67impl From<Gradient> for Paint {
68 fn from(g: Gradient) -> Self {
69 Paint::Gradient(g)
70 }
71}
72
73fn lerp(a: f32, b: f32, t: f32) -> f32 {
74 a + (b - a) * t
75}
76
77fn interpolate_point(p1: Point, p2: Point, t: f32) -> Point {
78 let t = t as f64;
79 Point::new(p1.x + (p2.x - p1.x) * t, p1.y + (p2.y - p1.y) * t)
80}
81
82fn sample_color_at(stops: &[ColorStop], offset: f32) -> Color {
84 if stops.is_empty() {
85 return Color::TRANSPARENT;
86 }
87 if stops.len() == 1 || offset <= stops[0].offset {
88 return stops[0].color;
89 }
90 let last = stops.len() - 1;
91 if offset >= stops[last].offset {
92 return stops[last].color;
93 }
94 for i in 1..stops.len() {
95 if offset <= stops[i].offset {
96 let range = stops[i].offset - stops[i - 1].offset;
97 if range < 1e-6 {
98 return stops[i].color;
99 }
100 let local_t = (offset - stops[i - 1].offset) / range;
101 return Color::interpolate(&stops[i - 1].color, &stops[i].color, local_t);
102 }
103 }
104 stops[last].color
105}
106
107fn interpolate_stops(stops1: &[ColorStop], stops2: &[ColorStop], t: f32) -> ColorStops {
108 let mut offsets: Vec<f32> = stops1
110 .iter()
111 .map(|s| s.offset)
112 .chain(stops2.iter().map(|s| s.offset))
113 .collect();
114 offsets.sort_by(|a, b| a.partial_cmp(b).unwrap());
115 offsets.dedup_by(|a, b| (*a - *b).abs() < 1e-4);
116
117 let mut result = Vec::with_capacity(offsets.len());
118 for &offset in &offsets {
119 let c1 = sample_color_at(stops1, offset);
120 let c2 = sample_color_at(stops2, offset);
121 result.push(ColorStop {
122 offset,
123 color: Color::interpolate(&c1, &c2, t),
124 });
125 }
126 ColorStops::from(result)
127}
128
129fn promote_solid_to_gradient(color: Color, target: &Gradient) -> Gradient {
130 let mut stops = Vec::new();
131 for stop in target.stops.iter() {
132 stops.push(ColorStop {
133 offset: stop.offset,
134 color,
135 });
136 }
137 Gradient {
138 kind: target.kind.clone(),
139 extend: target.extend,
140 stops: ColorStops::from(stops),
141 }
142}
143
144impl Tweenable for Paint {
145 fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
146 let t = t.clamp(0.0, 1.0);
147 match (a, b) {
148 (Paint::None, Paint::None) => Paint::None,
149 (Paint::None, Paint::Solid(c)) => {
150 let mut start_c = *c;
151 start_c.a = 0;
152 Paint::Solid(Color::interpolate(&start_c, c, t))
153 }
154 (Paint::Solid(c), Paint::None) => {
155 let mut end_c = *c;
156 end_c.a = 0;
157 Paint::Solid(Color::interpolate(c, &end_c, t))
158 }
159 (Paint::None, Paint::Gradient(g)) => {
160 let mut transparent_g = g.clone();
161 let mut stops = Vec::new();
162 for stop in g.stops.iter() {
163 let mut c = stop.color;
164 c.a = 0;
165 stops.push(ColorStop {
166 offset: stop.offset,
167 color: c,
168 });
169 }
170 transparent_g.stops = ColorStops::from(stops);
171 Paint::interpolate(&Paint::Gradient(transparent_g), b, t)
172 }
173 (Paint::Gradient(g), Paint::None) => {
174 let mut transparent_g = g.clone();
175 let mut stops = Vec::new();
176 for stop in g.stops.iter() {
177 let mut c = stop.color;
178 c.a = 0;
179 stops.push(ColorStop {
180 offset: stop.offset,
181 color: c,
182 });
183 }
184 transparent_g.stops = ColorStops::from(stops);
185 Paint::interpolate(a, &Paint::Gradient(transparent_g), t)
186 }
187 (Paint::Solid(c1), Paint::Solid(c2)) => Paint::Solid(Color::interpolate(c1, c2, t)),
188 (Paint::Gradient(g1), Paint::Gradient(g2)) => {
189 let kind = match (&g1.kind, &g2.kind) {
190 (
191 GradientKind::Linear { start: s1, end: e1 },
192 GradientKind::Linear { start: s2, end: e2 },
193 ) => GradientKind::Linear {
194 start: interpolate_point(*s1, *s2, t),
195 end: interpolate_point(*e1, *e2, t),
196 },
197 (
198 GradientKind::Radial {
199 start_center: sc1,
200 start_radius: sr1,
201 end_center: ec1,
202 end_radius: er1,
203 },
204 GradientKind::Radial {
205 start_center: sc2,
206 start_radius: sr2,
207 end_center: ec2,
208 end_radius: er2,
209 },
210 ) => GradientKind::Radial {
211 start_center: interpolate_point(*sc1, *sc2, t),
212 start_radius: lerp(*sr1, *sr2, t),
213 end_center: interpolate_point(*ec1, *ec2, t),
214 end_radius: lerp(*er1, *er2, t),
215 },
216 (k1, k2) => {
217 if t < 0.5 {
218 k1.clone()
219 } else {
220 k2.clone()
221 }
222 }
223 };
224
225 let extend = if t < 0.5 { g1.extend } else { g2.extend };
226 let stops = interpolate_stops(&g1.stops, &g2.stops, t);
227
228 Paint::Gradient(Gradient {
229 kind,
230 extend,
231 stops,
232 })
233 }
234 (Paint::Solid(c), Paint::Gradient(g)) => {
235 let dummy_g = promote_solid_to_gradient(*c, g);
236 Paint::interpolate(&Paint::Gradient(dummy_g), b, t)
237 }
238 (Paint::Gradient(g), Paint::Solid(c)) => {
239 let dummy_g = promote_solid_to_gradient(*c, g);
240 Paint::interpolate(a, &Paint::Gradient(dummy_g), t)
241 }
242 }
243 }
244
245 fn state_hash(&self) -> u64 {
246 let mut h = crate::assets::hash::Hasher::new();
247 match self {
248 Paint::None => {
249 h.update_u64(0);
250 }
251 Paint::Solid(c) => {
252 h.update_u64(1);
253 h.update_u64(Color::state_hash(c));
254 }
255 Paint::Gradient(g) => {
256 h.update_u64(2);
257 match &g.kind {
258 GradientKind::Linear { start, end } => {
259 h.update_u64(0);
260 h.update_u64(crate::assets::hash::hash_f32(start.x as f32));
261 h.update_u64(crate::assets::hash::hash_f32(start.y as f32));
262 h.update_u64(crate::assets::hash::hash_f32(end.x as f32));
263 h.update_u64(crate::assets::hash::hash_f32(end.y as f32));
264 }
265 GradientKind::Radial {
266 start_center,
267 start_radius,
268 end_center,
269 end_radius,
270 } => {
271 h.update_u64(1);
272 h.update_u64(crate::assets::hash::hash_f32(start_center.x as f32));
273 h.update_u64(crate::assets::hash::hash_f32(start_center.y as f32));
274 h.update_u64(crate::assets::hash::hash_f32(*start_radius));
275 h.update_u64(crate::assets::hash::hash_f32(end_center.x as f32));
276 h.update_u64(crate::assets::hash::hash_f32(end_center.y as f32));
277 h.update_u64(crate::assets::hash::hash_f32(*end_radius));
278 }
279 _ => {
280 h.update_u64(2);
281 }
282 }
283 for stop in g.stops.iter() {
284 h.update_u64(crate::assets::hash::hash_f32(stop.offset));
285 h.update_u64(Color::state_hash(&stop.color));
286 }
287 }
288 }
289 h.finish()
290 }
291}
292
293#[macro_export]
303macro_rules! linear_gradient {
304 ($($color:expr),+ $(,)?) => {{
305 let colors = [$($color),+];
306 assert!(colors.len() >= 2, "Gradients require at least 2 colors");
307 let mut stops = Vec::with_capacity(colors.len());
308 let n = colors.len() as f32;
309 for (i, &color) in colors.iter().enumerate() {
310 stops.push($crate::prelude::ColorStop {
311 offset: (i as f32) / (n - 1.0),
312 color,
313 });
314 }
315 $crate::prelude::Gradient {
316 kind: $crate::prelude::GradientKind::Linear {
317 start: $crate::prelude::Point::new(-$crate::core::animation::paint::DEFAULT_GRADIENT_LENGTH, 0.0),
318 end: $crate::prelude::Point::new($crate::core::animation::paint::DEFAULT_GRADIENT_LENGTH, 0.0),
319 },
320 extend: $crate::prelude::Extend::Pad,
321 stops: $crate::prelude::ColorStops::from(stops),
322 }
323 }};
324}
325
326#[macro_export]
336macro_rules! radial_gradient {
337 ($($color:expr),+ $(,)?) => {{
338 let colors = [$($color),+];
339 assert!(colors.len() >= 2, "Gradients require at least 2 colors");
340 let mut stops = Vec::with_capacity(colors.len());
341 let n = colors.len() as f32;
342 for (i, &color) in colors.iter().enumerate() {
343 stops.push($crate::prelude::ColorStop {
344 offset: (i as f32) / (n - 1.0),
345 color,
346 });
347 }
348 $crate::prelude::Gradient {
349 kind: $crate::prelude::GradientKind::Radial {
350 start_center: $crate::prelude::Point::new(0.0, 0.0),
351 start_radius: 0.0,
352 end_center: $crate::prelude::Point::new(0.0, 0.0),
353 end_radius: $crate::core::animation::paint::DEFAULT_GRADIENT_LENGTH as f32,
354 },
355 extend: $crate::prelude::Extend::Pad,
356 stops: $crate::prelude::ColorStops::from(stops),
357 }
358 }};
359}